updater: download/stage/swap/rollback flow steps
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>
This commit is contained in:
parent
fb6d2b1dae
commit
91af367208
5 changed files with 746 additions and 0 deletions
|
|
@ -97,6 +97,30 @@ func Save(path string, meta Metadata) error {
|
|||
return os.WriteFile(path, data, 0o644)
|
||||
}
|
||||
|
||||
// UpdateBuildInfo refreshes only the Version / Commit / BuiltAt
|
||||
// fields on the install metadata, preserving everything else
|
||||
// (OwnerUser/UID/GID/Home and the original InstalledAt timestamp).
|
||||
// Used by `banger update` to record what's running after a
|
||||
// successful binary swap; the install identity is unchanged so
|
||||
// re-running `banger system install` is not required.
|
||||
//
|
||||
// Errors when path doesn't exist or can't be parsed — `banger
|
||||
// update` runs in system mode where install.toml IS the source of
|
||||
// truth; a missing file means we shouldn't be updating at all.
|
||||
func UpdateBuildInfo(path, version, commit, builtAt string) error {
|
||||
if strings.TrimSpace(path) == "" {
|
||||
path = DefaultPath
|
||||
}
|
||||
meta, err := Load(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
meta.Version = strings.TrimSpace(version)
|
||||
meta.Commit = strings.TrimSpace(commit)
|
||||
meta.BuiltAt = strings.TrimSpace(builtAt)
|
||||
return Save(path, meta)
|
||||
}
|
||||
|
||||
func (m Metadata) Validate() error {
|
||||
if strings.TrimSpace(m.OwnerUser) == "" {
|
||||
return fmt.Errorf("install metadata missing owner_user")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue