docs/update-channel.md
Rama main · réplica de lectura del repositorio original.
# Update channel
How installed Crux daemons learn that a newer release exists, without ever
phoning home by default.
## Posture (non-negotiable)
- **Notification, never automatic action.** Nothing updates in the background.
The upgrade is always an explicit operator command that re-verifies artifacts
(`install.sh --version vX.Y.Z`, `brew upgrade crux`, `dpkg -i`, image tag
bump).
- **Opt-in check.** The daemon makes no outbound connections by default
(`scripts/assert-no-phone-home.sh`). An update *check* is an explicit
command (or an explicitly enabled config), not a background default for
binary installs.
- **No tracking.** The manifest is a static file served by GitHub Releases —
a plain GET with no query parameters, no instance id, no version-in-URL.
We cannot distinguish a check from a download, and that is the point.
## The manifest
Each release attaches `update-manifest.json` (generated by
`scripts/generate-update-manifest.sh`, signed like every other artifact).
The stable URL always points at the newest release:
```
https://github.com/CueCrux/Crux/releases/latest/download/update-manifest.json
```
Shape (`crux.update_manifest.v2`):
```json
{
"schema": "crux.update_manifest.v2",
"tag": "v0.5.0",
"version": "0.5.0",
"published_at": "2026-06-20T00:00:00Z",
"notes_url": "https://github.com/CueCrux/Crux/releases/tag/v0.5.0",
"verify_doc": "https://github.com/CueCrux/Crux/blob/v0.5.0/docs/verify-release.md",
"artifacts": [
{"name": "standalone-crux-linux-amd64", "asset_name": "crux-linux-amd64", "sha256": "…"},
{"name": "standalone-crux-darwin-arm64", "asset_name": "crux-darwin-arm64", "sha256": "…"},
{"name": "standalone-crux-darwin-amd64", "asset_name": "crux-darwin-amd64", "sha256": "…"}
]
}
```
Consumers compare `version` against their own `crux --version` and print the
upgrade hint. `name` is the standalone-updater selector; `asset_name` is the
actual GitHub Release file. The distinct logical name is a compatibility fence:
pre-M5 updaters search only for `crux-<target>`, find no match, and fail before
mutation. That prevents packaged installs from crossing without the new
hook/CLI; an old standalone install also takes the verified installer for this
one transition. The sha256 list lets a current standalone consumer pre-pin what
it expects before downloading; full verification remains the cosign/SLSA flow.
## Today's checking surfaces
- **Repo-checkout deploys** (host crux pattern): the daemon's git-posture
probe (`update.rs`, `CORECRUXD_UPDATE_CHECK_ENABLED`) compares the checkout
to its tracked branch and reports via `/v1/version` `.update` and the MCP
`update_status` tool. Egress is to *your* configured git remote, on a
checkout you deployed from. This probe is inert for binary installs (no
repo to compare) and installers pin it `=0`.
- **Binary installs**: check manually against the manifest URL above, or
run `crux self update --check`. Re-run the installer with a pinned
`--version` when you decide to move.
## `crux self update` (implemented, explicit-only)
`crux self update --check` performs the manifest GET, compares versions, and
prints the result without writing. `crux self update` can download, sha256-pin,
and atomically replace a **standalone daemon binary**. It is never invoked by
the daemon in the background.
Packaged installs deliberately refuse that daemon-only replacement when
`crux-hook` or `corecruxctl` is installed beside the daemon. A partial update
would create version skew, and replacing several executables cannot be one
filesystem-atomic operation. Upgrade the complete verified set instead:
- installer: re-run `install.sh --version vX.Y.Z`
- Homebrew: `brew upgrade crux`
- Debian: install the new signed `.deb`
This complete-bundle route is also required for the first upgrade into an M5
release: the v2 logical artifact name deliberately fences pre-M5 self-updaters
before they can perform a partial transition.
The in-process standalone path verifies the downloaded daemon against the hash
in the HTTPS-fetched manifest. The signed installer remains the stronger
cosign/SLSA path and the required route for managed bundles.