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:
parent
02a1472dd4
commit
759fa20602
2 changed files with 190 additions and 0 deletions
|
|
@ -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
189
docs/release-process.md
Normal 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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue