Three load-bearing fixes that together let `banger update` (and its
auto-rollback path) restart the helper + daemon without killing
every running VM. New smoke scenarios prove the property end-to-end.
Bug fixes:
1. Disable the firecracker SDK's signal-forwarding goroutine. The
default ForwardSignals = [SIGINT, SIGQUIT, SIGTERM, SIGHUP,
SIGABRT] installs a handler in the helper that propagates the
helper's SIGTERM (sent by systemd on `systemctl stop bangerd-
root.service`) to every running firecracker child. Set
ForwardSignals to an empty (non-nil) slice so setupSignals
short-circuits at len()==0.
2. Add SendSIGKILL=no to bangerd-root.service. KillMode=process
limits the initial SIGTERM to the helper main, but systemd
still SIGKILLs leftover cgroup processes during the
FinalKillSignal stage unless SendSIGKILL=no.
3. Route restart-helper / restart-daemon / wait-daemon-ready
failures through rollbackAndRestart instead of rollbackAndWrap.
rollbackAndWrap restored .previous binaries but didn't re-
restart the failed unit, leaving the helper dead with the
rolled-back binary on disk after a failed update.
Testing infrastructure (production binaries unaffected):
- Hidden --manifest-url and --pubkey-file flags on `banger update`
let the smoke harness redirect the updater at locally-built
release artefacts. Marked Hidden in cobra; not advertised in
--help.
- FetchManifestFrom / VerifyBlobSignatureWithKey /
FetchAndVerifySignatureWithKey export the existing logic against
caller-supplied URL / pubkey. The default entry points still
call them with the embedded canonical values.
Smoke scenarios:
- update_check: --check against fake manifest reports update
available
- update_to_unknown: --to v9.9.9 fails before any host mutation
- update_no_root: refuses without sudo, install untouched
- update_dry_run: stages + verifies, no swap, version unchanged
- update_keeps_vm_alive: real swap to v0.smoke.0; same VM (same
boot_id) answers SSH after the daemon restart
- update_rollback_keeps_vm_alive: v0.smoke.broken-bangerd ships a
bangerd that passes --check-migrations but exits 1 as the
daemon. The post-swap `systemctl restart bangerd` fails,
rollbackAndRestart fires, the .previous binaries are restored
and re-restarted; the same VM still answers SSH afterwards
- daemon_admin (separate prep): covers `banger daemon socket`,
`bangerd --check-migrations --system`, `sudo banger daemon
stop`
The smoke release builder generates a fresh ECDSA P-256 keypair
with openssl, signs SHA256SUMS cosign-compatibly, and serves
artefacts from a backgrounded python http.server.
verify_smoke_check_test.go pins the openssl/cosign signature
equivalence so the smoke release builder can't silently drift.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The placeholder in BangerReleasePublicKey is replaced with the
production cosign public key (P-256 ECDSA). The matching private
key is stored offline by the maintainer; this is the public half
that every banger CLI baked from this commit forward will use to
verify SHA256SUMS signatures.
cosign.pub is also committed at the repo root so external auditors
can re-verify a release without parsing the Go source.
The placeholder-refuses test now swaps the embedded key for a
synthetic placeholder for the duration of the test, since the
default value is no longer a placeholder.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
Closes the v0.1.0 cosign requirement. Every banger update download
now goes through ECDSA-P256 verification before any binary is
trusted: SHA256SUMS.sig is fetched, base64-decoded, and verified
against the embedded BangerReleasePublicKey.
* BangerReleasePublicKey: PEM-encoded ECDSA public key embedded
at compile time. The current value is a sentinel PLACEHOLDER —
the maintainer must replace it with the output of
`cosign generate-key-pair`'s cosign.pub before cutting v0.1.0,
and re-cut. Until they do, every `banger update` refuses with
ErrSignatureRequired ("the maintainer must replace it and
re-cut a release before update can proceed"). Loud refusal
beats silent acceptance.
* VerifyBlobSignature: parses the embedded public key, base64-
decodes the signature, computes SHA256(body), runs ecdsa
.VerifyASN1. cosign sign-blob produces the format
VerifyASN1 verifies natively (ASN.1-DER encoded ECDSA over
a SHA256 digest), so no third-party crypto deps needed.
* FetchAndVerifySignature: pulls the signature URL from the
release manifest entry, fetches it (1 KiB cap), and verifies
against sumsBody. Refuses outright when sha256sums_sig_url is
empty — v0.1.0 contract requires every release to be signed,
and an unsigned release is a manifest publishing bug we'd
rather catch loudly than silently accept.
* Wired into banger update: sumsBody captured from
DownloadRelease, immediately fed into FetchAndVerifySignature.
A failed verification removes the staged tarball before
returning so it can't be reused.
* BangerReleasePublicKey is var (not const) only to support tests
that swap in a generated keypair; production sets it at compile
time and never mutates it.
Tests: placeholder-key path returns ErrSignatureRequired; happy
path with a fresh in-test ECDSA keypair verifies a real
sign-then-verify; tampered body, wrong key, and three malformed
signature shapes (not-base64, empty, garbage-DER) all reject.
Maintainer-cut workflow documented in BangerReleasePublicKey's
comment: cosign generate-key-pair → paste cosign.pub into the
constant → at release time, cosign sign-blob --key cosign.key
SHA256SUMS > SHA256SUMS.sig and publish.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>