docs: add release-process runbook

Captures the cut-and-publish workflow currently encoded only in
scripts/publish-banger-release.sh and the CHANGELOG patterns. Covers:

- Release artefacts + R2 paths + the install.sh-at-bucket-root
  contract.
- Trust model recap (cosign pubkey pinned in both verify_signature.go
  and scripts/install.sh; drift check enforced by the publish script).
- Pre-flight checklist: green smoke, CHANGELOG entry with the right
  Keep-a-Changelog headings, link-table bump, explicit callout when
  unit files changed (banger update swaps binaries, not units).
- Cut order: publish first, tag after, verify from a clean machine.
- Verification-release rule: any fix to runUpdate / unit templates /
  helper-daemon restart sequencing requires an immediate no-op +1
  release so a host on the buggy version can update to it and observe
  the fix live with the new binary in the driver seat. v0.1.3 and
  v0.1.5 are the existing examples.
- Patch vs minor: minor = exposed API/contract change (vsock guest-
  agent protocol, CLI flag removal, RPC shape, non-forward-compatible
  store schema); everything else is patch.
- Sibling catalogs: kernel + golden-image entries are go:embed-ed,
  so they piggyback on the next banger release.
- Mid-release recovery for signature drift, partial rclone, re-cut,
  and bad-tag cleanup (never reuse a version).

AGENTS.md gets a one-liner pointer so the maintainer guide surfaces
the runbook without duplicating it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Thales Maciel 2026-05-01 12:25:36 -03:00
parent 02a1472dd4
commit 759fa20602
No known key found for this signature in database
GPG key ID: 33112E6833C34679
2 changed files with 190 additions and 0 deletions

View file

@ -25,6 +25,7 @@ Always run `make build` before commit.
- `./build/bin/banger image promote <image>` copies an unmanaged image into daemon-owned managed artifacts.
- `scripts/make-generic-kernel.sh` builds a Firecracker-optimized vmlinux from upstream sources. `scripts/publish-kernel.sh <name>` publishes it to the kernel catalog.
- `scripts/publish-golden-image.sh` rebuilds + publishes the golden image bundle and patches the image catalog.
- `scripts/publish-banger-release.sh <vX.Y.Z>` cuts a banger release. Full runbook in `docs/release-process.md`.
## Image Model

189
docs/release-process.md Normal file
View file

@ -0,0 +1,189 @@
# Release process
Maintainer-facing runbook for cutting and publishing a new banger
release. End users don't need any of this — they pick up new releases
through `banger update` or the curl-piped `install.sh`.
## What ships in a release
Each release publishes four objects to the R2 bucket served at
`https://releases.thaloco.com/banger/`:
| Object | Path | Notes |
|---|---|---|
| Tarball | `<version>/banger-<version>-linux-amd64.tar.gz` | `banger`, `bangerd`, `banger-vsock-agent` at the root, no subdirs |
| Hashes | `<version>/SHA256SUMS` | One line for the tarball, GNU `sha256sum` format |
| Signature | `<version>/SHA256SUMS.sig` | base64-encoded ASN.1 ECDSA cosign-blob signature over `SHA256SUMS` |
| Manifest | `manifest.json` (bucket root) | Describes every published release; `latest_stable` points at the most recent |
`install.sh` lives at the bucket root too (unversioned) so the
`curl … | bash` URL stays stable across releases.
## Trust model recap
Every release is cosign-signed. The public key is pinned in two places
that MUST stay in sync:
- `internal/updater/verify_signature.go``BangerReleasePublicKey`
used by `banger update`.
- `scripts/install.sh` — embedded copy used by the curl-piped installer
before any banger binary is on disk.
`scripts/publish-banger-release.sh` aborts the upload if the two copies
diverge — that's the only mechanism keeping them coupled, so don't
edit either alone.
The signed payload is `SHA256SUMS`, which in turn covers the tarball.
Verification uses the Go standard library (`crypto/ecdsa.VerifyASN1`)
on the update path and `openssl dgst -verify` on the install-script
path. cosign is needed only for **signing**.
## Pre-flight checklist
Run these before tagging or publishing:
1. **`make smoke`** — the full systemd-driven scenario suite must be
green. The smoke harness exercises the real install + update path
end to end; if it's red, do not cut.
2. **CHANGELOG entry.** Add a `## [vX.Y.Z] - YYYY-MM-DD` section under
`## [Unreleased]` describing what changed. Use the
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) sub-headings
(`### Added`, `### Fixed`, `### Notes`).
3. **Bump the link table** at the bottom of `CHANGELOG.md`:
```markdown
[Unreleased]: …/compare/vX.Y.Z...HEAD
[vX.Y.Z]: …/releases/tag/vX.Y.Z
```
4. **Note unit-file changes loudly** in the CHANGELOG entry. `banger
update` swaps binaries only — it does NOT rewrite
`/etc/systemd/system/bangerd*.service`. If this release changed
`renderSystemdUnit` / `renderRootHelperSystemdUnit`, the entry must
tell existing-install users to run `sudo banger system install`
once after updating to pick up the new units. v0.1.4 and v0.1.6
are reference examples.
Commit the CHANGELOG change, push to `main`, and confirm CI is green.
## Cutting the release
Order matters: publish first, then tag.
1. **Run the publish script:**
```sh
scripts/publish-banger-release.sh vX.Y.Z
```
The script:
- Builds `banger`, `bangerd`, `banger-vsock-agent` with `-ldflags`
baking the version, the current commit SHA, and a UTC build
timestamp into `internal/buildinfo`.
- Tarballs the three binaries (bare basenames at the tar root —
`internal/updater/StageTarball` rejects anything else).
- Computes `SHA256SUMS`, signs it with `cosign sign-blob` (no
transparency log, no bundle format — banger verifies the bare
ASN.1 DER signature directly).
- Verifies the signature against the public key extracted from
`internal/updater/verify_signature.go`, then diffs that against
the public key embedded in `scripts/install.sh`. Either failure
aborts before upload.
- Pulls the existing `manifest.json` from the bucket, appends the
new release entry, points `latest_stable` at it, and uploads
everything via rclone.
- Uploads `scripts/install.sh` to the bucket root so the curl-piped
installer stays current.
2. **Tag and push:**
```sh
git tag vX.Y.Z
git push --tags
```
Tagging happens AFTER publishing so the tag only exists if the
release actually shipped.
3. **Verify from a clean machine:**
```sh
curl -fsSL https://releases.thaloco.com/banger/manifest.json | jq .latest_stable
curl -fsSL https://releases.thaloco.com/banger/install.sh | head -20
banger update --check # on an existing install
```
## Verification releases
If a release fixes anything in the update flow itself —
`runUpdate` (`internal/cli/commands_update.go`), the systemd unit
templates, or the helper/daemon restart sequencing — cut a follow-up
no-op verification release immediately. The reason: `banger update`
runs the OLD binary as the driver of the swap. A fix in vN can't be
observed end-to-end on a vN-1 host updating to vN, because vN-1 is
still in the driver seat. vN+1 with no functional changes lets a host
on vN update to it and observe the fix live with vN as the driver.
Examples in CHANGELOG.md: v0.1.3 follows v0.1.2's update-flow fix;
v0.1.5 follows v0.1.4's daemon-restart fix.
The verification-release CHANGELOG section is short and explicit:
> No functional changes. Verification release for vN: …
## Patch vs minor
banger follows [SemVer](https://semver.org/spec/v2.0.0.html). For
v0.1.x, the practical contract:
- **Patch (v0.1.x):** bug fixes, internal refactors, anything that
doesn't change the exposed API/CLI behavior.
- **Minor (v0.2.x):** any change to the **exposed API behavior or
contract**. The vsock guest-agent protocol is the canonical example —
a minor bump means existing VMs created against the older minor need
to be re-pulled. Other minor-trigger changes: removing a CLI flag,
changing a stable RPC method's request/response shape, breaking the
on-disk store schema in a non-forward-compatible way.
If in doubt, prefer the higher bump. Patch releases that turn out to
have broken a contract are the worst-of-both: users update without
warning, then break.
## Sibling catalogs
Kernel and golden-image releases ship through the same gate. The
`internal/kernelcat/catalog.json` and `internal/imagecat/catalog.json`
manifests are `go:embed`-ed at build time, so a new entry only
reaches users when banger itself is re-released. In practice:
1. Run `scripts/publish-kernel.sh <name>` or
`scripts/publish-golden-image.sh …` to upload the artefact and
patch the appropriate `catalog.json` in the working tree.
2. Commit the catalog change with whatever banger fix or feature it's
landing alongside.
3. Cut a banger release the normal way; the new catalog entry ships
with the next `banger` binary.
The kernel and image catalogs each have their own R2 bucket
(`kernels.thaloco.com`, `images.thaloco.com`) so versioning of the
artefacts is independent of banger's release cadence — but
**discoverability** is gated by the banger release that embeds the
catalog pointer.
## When something goes wrong mid-release
- **Signature verification fails locally** in
`publish-banger-release.sh`: confirm `internal/updater/verify_signature.go`
contains the same public key as `cosign.pub` in the repo root. If
the script reports drift between `verify_signature.go` and
`install.sh`, run `diff` between the two `BEGIN PUBLIC KEY` blocks
and resolve before rerunning.
- **rclone upload fails partway through:** the script uploads tarball,
hashes, signature, and manifest in that order. Re-running is safe;
rclone will overwrite. Until the manifest is uploaded, no client
sees the new release — so a partial upload is invisible.
- **Manifest already names the version** (re-cutting): the publish
script's `jq` filter dedupes by `version`, so re-running with the
same `vX.Y.Z` cleanly replaces the entry.
- **Already tagged but the release is bad:** delete the tag locally
AND on the remote (`git push --delete origin vX.Y.Z`), revert the
CHANGELOG entry, fix the bug, and start the cycle over with a fresh
patch number. Do NOT re-use the version — installed clients have
already cached its `SHA256SUMS` against the manifest.