> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tofunmiadewuyi.com/custos/llms.txt
> Use this file to discover all available pages before exploring further.

# Daemon updates

> How an enrolled custosd fleet upgrades: controlled push, staged download, root swap.

How an enrolled `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

1. **Trigger.** `POST /hosts/{id}/upgrade` or `POST /upgrade` (admin, `{"version":"vX.Y.Z"}`).
   The control plane resolves the release's per-arch digests from
   `$CUSTOS_RELEASE_BASE/<version>/..._checksums.txt` (`resolveChecksums`), records
   `hosts.desired_version`, and pushes `TypeUpgrade{Version, SHA256: arch→digest, BaseURL}` to
   online hosts. Offline hosts get it on reconnect.
2. **Stage** (daemon, unprivileged). `client.handleUpgrade` → `stageUpgrade`: download the
   arch tarball, verify against the pushed digest, extract `custosd`, atomic-rename it to
   `/var/lib/custos/update/custosd.staged` (+ `staged.json`), then exit so systemd restarts.
3. **Apply** (root). The unit has `ExecStartPre=+custosd apply-update`. The `+` runs it as
   root though the service is `User=custos`. It re-verifies the staged binary's digest,
   smoke-tests `custosd version`, backs up `/usr/local/bin/custosd` → `.prev`, atomically
   swaps in the new binary, and clears the staged files. `ExecStart` then runs the new build.
4. **Converge.** The daemon reports `Version` in its `Auth` on 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 (grows `Upgrade` a `Sig` field without a redesign).

## Triggering an upgrade

```bash theme={null}
# one host
curl -sS -X POST "$CUSTOS_URL/hosts/$HOST_ID/upgrade" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"version":"v1.2.3"}'

# whole fleet
curl -sS -X POST "$CUSTOS_URL/upgrade" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"version":"v1.2.3"}'
```

`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.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.