The pure-logic core of `banger update`. No CLI yet; this commit
ships the steps the next commit's command will orchestrate.
* download.go — DownloadRelease fetches SHA256SUMS, parses it,
looks up the tarball's basename, then streams the tarball
through download.FetchVerified so the hash is checked on the
fly. Returns the SHA256SUMS bytes alongside so a future
cosign-verification step can validate them against an embedded
public key before trusting the hashes inside.
Also: fetchBounded for small bounded GETs (manifest, sums file,
future signature), DefaultStagingDir, EnsureStagingDir,
PrepareCleanStaging.
* stage.go — StageTarball reads gzip+tar, validates the entry
set is exactly {banger, bangerd, banger-vsock-agent} (no
extras, no missing, no path traversal, no non-regular files),
extracts at mode 0755 regardless of what the tarball claims.
StagedRelease records the resulting paths.
* swap.go — InstallTargets pins the canonical install paths
(/usr/local/bin/banger, /usr/local/bin/bangerd,
/usr/local/lib/banger/banger-vsock-agent). Swap orders the
three replacements vsock → bangerd → banger so the most
impactful binary (the CLI) goes last; each step uses
system.AtomicReplace and accumulates a SwapResult so partial
failures can be rolled back cleanly. Rollback unwinds in
reverse, joining errors so a half-rolled-back state surfaces
enough info for an operator to fix manually. CleanupBackups
removes the .previous trail after `banger doctor` confirms
the new install is healthy.
* installmeta.UpdateBuildInfo — small helper that refreshes
Version/Commit/BuiltAt on /etc/banger/install.toml without
re-running the full system install. Preserves OwnerUser/UID/
GID/Home and the original InstalledAt timestamp.
Tests: stage rejects extra entries / missing entries / path
traversal / non-regular files; happy-path stages all three at 0755
with correct contents. Swap+Rollback covers the all-three-succeed
path (then verifies .previous backups exist + rollback restores
old contents) AND the partial-failure path (third swap blocked by
a non-dir parent → SwappedTargets = 2 → rollback unwinds those
two cleanly). DownloadRelease covers happy path, tarball-not-in-
SHA256SUMS, and propagated sha256 mismatch.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>