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>
Prerequisite for `banger update`. Before swapping a staged binary
into place, the updater needs to confirm the new bangerd recognises
the running install's DB schema. Without this, an operator could end
up with a service that won't open its store after the binary swap +
restart.
* store.InspectSchemaState(path): opens the DB read-only (reusing
OpenReadOnly's mode=ro DSN), reads the schema_migrations table,
and classifies the relationship between applied and known IDs:
SchemaCompatible (lockstep), SchemaMigrationsNeeded (binary
newer, will auto-migrate on first Open), or SchemaIncompatible
(DB has applied IDs the binary doesn't know about).
Missing schema_migrations table is treated as "all migrations
pending" rather than an error — matches the fresh-install case.
* bangerd --check-migrations: opens the configured DB read-only,
prints a one-line classification, and exits 0/1/2. The exit
code is the contract:
0 — compatible
1 — migrations needed (binary newer; safe to swap)
2 — incompatible (binary older than DB; abort the swap)
Honours --system to pick between system StateDir and user mode.
* bangerdExit indirection so future tests can capture the exit
code without terminating the test process. Production points
at os.Exit.
Tests cover the four classifications: compatible (fully migrated
DB), migrations-needed (only baseline applied), incompatible
(synthetic id=99 inserted), and missing-table (fresh DB). Live
exercise on this dev host returned `migrations needed: pending [3]
(binary will apply on first Open)` and exit 1, matching the
contract.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two pre-release polish items on the version-display surface.
* --version on both binaries: cobra's Version field on the banger
and bangerd roots renders a one-line summary (banger v0.1.0
(commit abcd1234, built 2026-04-28T20:45:50Z)). The
SetVersionTemplate override drops cobra's "{{.Name}} version"
prefix — our string is already a complete sentence. The
multi-line `banger version` subcommand is unchanged for callers
that want the full SHA / built_at on separate lines.
* Doctor "banger version" row: prints the running CLI's version +
short commit + built-at, plus what /etc/banger/install.toml
recorded at install time. Disagreement is the most common
version-skew pitfall (stale CLI against fresh daemon, or vice
versa) and a one-line warn is friendlier than tracking that down
from a launch failure.
Drift detection is suppressed when either side is dev/unknown
(untagged build) — comparing a dev CLI against a tagged install
is the developer-machine case, not a real problem.
formatVersionLine is in internal/cli (banger.go) and reused by
bangerd.go via a strings.Replace because bangerd's version line
should say "bangerd" not "banger". Slightly tilt-feeling but cheaper
than parameterising the helper for one caller.
Tests: TestVersionsDriftToleratesDevAndUnknown pins the four
branches (match, version diff, commit diff, dev-suppression). The
existing version-format test already runs through formatVersionLine
indirectly.
Live exercise:
$ banger --version
banger dev (commit 1c1ca7d6, built 2026-04-28T20:52:33Z)
$ bangerd --version
bangerd dev (commit 1c1ca7d6, built 2026-04-28T20:52:33Z)
$ banger doctor | head
...
PASS banger version
- CLI dev (commit 1c1ca7d6, built 2026-04-28T20:52:33Z)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move the supported systemd path to two services: an owner-user bangerd for
orchestration and a narrow root helper for bridge/tap, NAT/resolver, dm/loop,
and Firecracker ownership. This removes repeated sudo from daily vm and image
flows without leaving the general daemon running as root.
Add install metadata, system install/status/restart/uninstall commands, and a
system-owned runtime layout. Keep user SSH/config material in the owner home,
lock file_sync to the owner home, and move daemon known_hosts handling out of
the old root-owned control path.
Route privileged lifecycle steps through typed privilegedOps calls, harden the
two systemd units, and rewrite smoke plus docs around the supported service
model.
Verified with make build, make test, make lint, and make smoke on the
supported systemd host path.
Replace the shell-only user workflow with `banger` and `bangerd`: Cobra commands, XDG/SQLite-backed state, managed VM and image lifecycle, and a Bubble Tea TUI for browsing and operating VMs.\n\nKeep Firecracker orchestration behind the daemon so VM specs become persistent objects, and add repo entrypoints for building, installing, and documenting the new flow while still delegating rootfs customization to the existing shell tooling.\n\nHarden the control plane around real usage by reclaiming Firecracker API sockets for the user, restarting stale daemons after rebuilds, and returning the correct `vm.create` payload so the CLI and TUI creation flow work reliably.\n\nValidation: `go test ./...`, `make build`, and a host-side smoke test with `./banger vm create --name codex-smoke`.