update: docs + publish script for the self-update feature

README gets a top-level Updating section; docs/privileges.md gains
a step-by-step trust-model writeup of `banger update`. The new
scripts/publish-banger-release.sh drives the manual release cut:
build, tar, sha256sum, cosign sign-blob, verify against the embedded
public key, jq-merge into manifest.json, rclone upload to the R2
bucket. Refuses outright if the embedded key is still the placeholder
so we can't accidentally publish an unverifiable release. Also folds
in gofmt drift accumulated across the updater package and a few
sibling files.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Thales Maciel 2026-04-29 12:43:46 -03:00
parent 8ed351ea47
commit fae28e3d8b
No known key found for this signature in database
GPG key ID: 33112E6833C34679
10 changed files with 310 additions and 33 deletions

View file

@ -198,6 +198,84 @@ What `uninstall` does NOT do automatically:
- It does not remove the owner user, the owner's home, or anything
the user wrote into a guest from inside the guest.
## Updating banger
`banger update` is a user-triggered, manually-invoked operation. It
never runs in the background and never auto-checks for new releases.
The flow:
1. **Discover.** GET `https://releases.thaloco.com/banger/manifest.json`
over HTTPS. The URL is hardcoded in the binary at compile time —
a compromised daemon config can't redirect the updater. Manifest
schema_version gates forward compat: a CLI that doesn't recognise
the server's schema_version refuses to update.
2. **In-flight gate.** `daemon.operations.list` RPC. If any operation
is not Done, refuse with the operation list. `--force` overrides.
3. **Download.** Capped GET on the tarball + `SHA256SUMS` (≤ 256 MiB
and ≤ 16 KiB respectively). Tarball is sha256-verified on the fly
against the digest published in `SHA256SUMS`; partial files are
removed on any verification failure.
4. **Cosign signature.** `SHA256SUMS.sig` is fetched (≤ 1 KiB) and
verified against the `BangerReleasePublicKey` embedded in the
running banger binary. The signature is an ECDSA P-256 / SHA-256
blob signature produced by `cosign sign-blob` — verified by Go's
stdlib `crypto/ecdsa.VerifyASN1`, no third-party crypto deps. A
missing signature URL or a verification failure aborts the update
before any binary is touched.
5. **Sanity-run.** Staged `banger --version` must mention the
expected version; staged `bangerd --check-migrations --system`
must exit 0 (compatible) or 1 (will auto-migrate). Exit 2
(incompatible — DB has migrations the new binary doesn't know)
aborts the swap; the running install is untouched.
6. **Swap.** Atomic `os.Rename` for each of the three binaries
(banger-vsock-agent → bangerd → banger), with `.previous` backups.
7. **Restart.** `systemctl restart bangerd-root.service` then
`bangerd.service`. Wait for the new daemon socket to answer
`ping`. Running VMs survive the daemon restart — they're each
their own firecracker process and live in `bangerd-root.service`'s
cgroup; restart's `KillMode=control-group` doesn't reach them.
The new daemon's `reconcile` step re-attaches by reading the
per-VM `handles.json` scratch file and verifying the firecracker
process is still alive.
8. **Verify.** Run `banger doctor` against the just-installed CLI.
FAIL triggers auto-rollback: restore `.previous` backups, restart
services again so the OLD binaries take over. The original error
bubbles to the operator; `--force` skips this step.
9. **Finalise.** Update `/etc/banger/install.toml`'s Version /
Commit / BuiltAt. Remove `.previous` backups. Wipe the staging
directory under `/var/cache/banger/updates/`.
What you're trusting in this flow:
- The cosign **public key** baked into the binary you're updating
FROM. The maintainer rotates it by cutting a new release with a
new key embedded; from then on, only signatures made with the
new private key are accepted. v0.1.x predates a clean rotation
story.
- TLS to `releases.thaloco.com` for transport. The cosign signature
is the actual integrity check; TLS just gets us the bytes faster.
- The systemd unit owners (root for the helper, owner for the
daemon). `banger update` requires root because it writes
`/usr/local/bin` and talks to systemctl; it does NOT run via the
helper RPC interface.
What `banger update` deliberately does NOT do:
- No background check timers. Operators run `banger update --check`
on a schedule themselves if they want.
- No update across MINOR boundaries without an explicit `--to`
flag. v0.x is pre-stable; we don't promise that v0.1.5 → v0.2.0
is automatic.
- No state-DB downgrade. Schema migrations are forward-only;
`--check-migrations` refuses to swap a binary that's older than
the running schema.
- No agent re-injection into existing VMs. The vsock agent inside
each VM is the version banger had at image-pull time, not the
current install. v0.1.x doesn't enforce or detect skew here; the
agent's HTTP API is small enough that compat across MINORs is
expected.
## Running outside the system install
Everything above describes the supported deployment: `banger system