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>
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.go—BangerReleasePublicKeyused bybanger 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:
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.- CHANGELOG entry. Add a
## [vX.Y.Z] - YYYY-MM-DDsection under## [Unreleased]describing what changed. Use the Keep a Changelog sub-headings (### Added,### Fixed,### Notes). - Bump the link table at the bottom of
CHANGELOG.md:[Unreleased]: …/compare/vX.Y.Z...HEAD [vX.Y.Z]: …/releases/tag/vX.Y.Z - Note unit-file changes loudly in the CHANGELOG entry.
banger updateswaps binaries only — it does NOT rewrite/etc/systemd/system/bangerd*.service. If this release changedrenderSystemdUnit/renderRootHelperSystemdUnit, the entry must tell existing-install users to runsudo banger system installonce 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.
-
Run the publish script:
scripts/publish-banger-release.sh vX.Y.ZThe script:
- Builds
banger,bangerd,banger-vsock-agentwith-ldflagsbaking the version, the current commit SHA, and a UTC build timestamp intointernal/buildinfo. - Tarballs the three binaries (bare basenames at the tar root —
internal/updater/StageTarballrejects anything else). - Computes
SHA256SUMS, signs it withcosign 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 inscripts/install.sh. Either failure aborts before upload. - Pulls the existing
manifest.jsonfrom the bucket, appends the new release entry, pointslatest_stableat it, and uploads everything via rclone. - Uploads
scripts/install.shto the bucket root so the curl-piped installer stays current.
- Builds
-
Tag and push:
git tag vX.Y.Z git push --tagsTagging happens AFTER publishing so the tag only exists if the release actually shipped.
-
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:
- Run
scripts/publish-kernel.sh <name>orscripts/publish-golden-image.sh …to upload the artefact and patch the appropriatecatalog.jsonin the working tree. - Commit the catalog change with whatever banger fix or feature it's landing alongside.
- Cut a banger release the normal way; the new catalog entry ships
with the next
bangerbinary.
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: confirminternal/updater/verify_signature.gocontains the same public key ascosign.pubin the repo root. If the script reports drift betweenverify_signature.goandinstall.sh, rundiffbetween the twoBEGIN PUBLIC KEYblocks 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
jqfilter dedupes byversion, so re-running with the samevX.Y.Zcleanly 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 itsSHA256SUMSagainst the manifest.