# Releases

`arbe` ships as standalone binaries built with `bun build --compile` for `darwin-arm64`, `linux-x64` (Linux ARM and Intel Mac deferred — `scripts/release-build.ts` `TARGETS` is canonical). This page is the durable contract — installer scripts, Quickstart, and release tooling all reference it. Change here first, then update consumers.

Versioning is the semver in `apps/cli/package.json`; commit SHA + UTC build date are baked in via `--define process.env.ARBE_BUILD_COMMIT` / `process.env.ARBE_BUILD_DATE`. `arbe --version` prints `arbe <version> (<short-commit>, <yyyy-mm-dd>)` (or `arbe dev` in dev). `arbe upgrade` compares only the version string against `latest.json` — commit SHA and date don't affect the upgrade decision.

```
dist/release/latest/
  arbe-<target>.tar.gz       # single `arbe` at archive root
  SHA256SUMS                 # one per line, sorted
  release-latest.json        # → arbe/latest.json (mutable, 60s cache, must-revalidate)
  release-<version>.json     # → arbe/<version>.json (immutable, 1y)
```

R2 holds two pointers to the same bytes: `arbe/latest/` (mutable — newest published build) and `arbe/<version>/` (pinned, never overwritten in practice). Manifests at `arbe/latest.json` / `arbe/<version>.json` follow `LatestReleaseManifest` (`apps/cli/src/update/shared.ts`) — `version`, `commit`, `published_at`, `artifacts.<target>.{url,sha256,size}` all required. Always fetch the tarball and `SHA256SUMS` from the same prefix together — `latest/` is mutable so they can mismatch mid-publish; `latest.json` is the only stable metadata pointer; the commit SHA baked into the binary is authoritative for "what build do I have."

Installer at `apps/www/static/install.sh` (one-liner: `curl -fsSL https://arbe.0sk.ar/install.sh | sh`) reads the manifest, picks target by `uname`, downloads + verifies sha256, drops `arbe` in `$ARBE_INSTALL_DIR` (default `~/.local/bin`). POSIX `sh`, no jq/python — only `curl`/`wget`, `tar`, `sha256sum`/`shasum`. Knobs: `ARBE_VERSION=latest|<x.y.z>`, `ARBE_TARGET=<target>` (cross-arch testing), `ARBE_INSTALL_DIR=<path>`. Drift is bounded by the manifest schema.

Cut a release: `bun run release:bump [patch|minor|major|x.y.z]` bumps `apps/cli/package.json`, jj-commits only that file, advances the `release` bookmark, and pushes (`--dry-run` writes the version but stops, printing manual jj steps). The [`cli-release`](../../.github/workflows/cli-release.yml) GitHub Action runs `release:build` + `release:upload` against R2 on pushes to the `release` branch. `CLOUDFLARE_ACCOUNT_ID` + `CLOUDFLARE_API_TOKEN` repo secrets do the auth — wrangler picks them up automatically; one-time setup in [system/deployment](./deployment.md).

Break-glass: `bun run release:build` then `bun run release:upload` mirror manually; local invocation needs `bunx wrangler whoami` authed against the right Cloudflare account. Always overwrites. `Cache-Control` is set per-key on upload — `latest/*` and `latest.json` get `max-age=60, must-revalidate`; `<version>/*` and `<version>.json` get `max-age=31536000, immutable`. Releases land on the edge within ~60s.

Code: `scripts/release-build.ts`, `scripts/release-upload.ts`, `scripts/release-bump.ts`, `apps/cli/src/update/shared.ts` (`LatestReleaseManifest`), `.github/workflows/cli-release.yml`.<br>
See [cli](../cli.md#install), [system/deployment](./deployment.md).
