custosd fleet upgrades. Distribution (install.sh plus release
tarballs under CUSTOS_RELEASE_BASE) handles first install; this is the upgrade of
already-running hosts.
Controlled push: an admin selects a published version for one host or the whole fleet. The
release workflow maintains releases.json beside the artifacts, and the control plane exposes
that catalog at GET /daemon/releases so clients never need a free-text version. The control
plane pushes the selected release over the existing authenticated daemon websocket; the daemon
downloads, verifies, and swaps its own binary via a root systemd step. Idempotent at every layer
— a host already on the target is a no-op.
Flow
- Trigger.
POST /hosts/{id}/upgradeorPOST /upgrade(admin,{"version":"vX.Y.Z"}). The control plane resolves the release’s per-arch digests from$CUSTOS_RELEASE_BASE/<version>/..._checksums.txt(resolveChecksums), recordshosts.desired_version, and pushesTypeUpgrade{Version, SHA256: arch→digest, BaseURL}to online hosts. Offline hosts get it on reconnect. - Stage (daemon, unprivileged).
client.handleUpgrade→stageUpgrade: download the arch tarball, verify against the pushed digest, extractcustosd, atomic-rename it to/var/lib/custos/update/custosd.staged(+staged.json), then exit so systemd restarts. - Apply (root). The unit has
ExecStartPre=+custosd apply-update. The+runs it as root though the service isUser=custos. It re-verifies the staged binary’s digest, smoke-testscustosd version, backs up/usr/local/bin/custosd→.prev, atomically swaps in the new binary, and clears the staged files.ExecStartthen runs the new build. - Converge. The daemon reports
Versionin itsAuthon reconnect →hosts.agent_version.
Trust
The digest comes from the control plane over the authenticated link, so it, not the release host, is the trust anchor. Deferred hardening: an Ed25519 signature over each release, pinned into the daemon (growsUpgrade a Sig field without a redesign).
Triggering an upgrade
GET /daemon/releases returns the published release catalog, so clients never need to supply a
free-text version. Every layer is idempotent — a host already on the target version is a no-op, and
an offline host receives the upgrade on its next connect.