banger/docs/release-process.md
Thales Maciel 759fa20602
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>
2026-05-01 12:25:36 -03:00

7.9 KiB

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.goBangerReleasePublicKey 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 sub-headings (### Added, ### Fixed, ### Notes).
  3. Bump the link table at the bottom of CHANGELOG.md:
    [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:

    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:

    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:

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