326 lines
15 KiB
Markdown
326 lines
15 KiB
Markdown
# Changelog
|
||
|
||
All notable changes to banger are documented here. The format is based
|
||
on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this
|
||
project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||
|
||
The version line printed by `banger version` is the canonical reference
|
||
for what's installed; this file is the canonical reference for what
|
||
changed between versions.
|
||
|
||
## [Unreleased]
|
||
|
||
## [v0.1.10] - 2026-05-03
|
||
|
||
### Added
|
||
|
||
- README now includes an animated demo GIF showing the typical
|
||
sandbox lifecycle (`vm run`, host-side `ssh demo.vm`, stop/start
|
||
with file persistence, `vm exec`, `curl http://demo.vm`). The
|
||
recording script lives at `assets/demo.tape` and is rendered with
|
||
[VHS](https://github.com/charmbracelet/vhs).
|
||
|
||
## [v0.1.9] - 2026-05-01
|
||
|
||
### Fixed
|
||
|
||
- `vm exec` no longer falls back to `cd /root/repo` on VMs that have
|
||
no recorded workspace. Previously, running `vm exec` against a plain
|
||
VM (one that never had `vm workspace prepare` / `vm run ./repo`)
|
||
blew up with `cd: /root/repo: No such file or directory` — surfaced
|
||
via the login shell's mise activate hook because `bash -lc` sources
|
||
profile.d before the explicit cd. Now the auto-cd only fires when
|
||
the user passes `--guest-path` or the VM actually has a workspace
|
||
recorded; otherwise the command runs from root's home. Mise wrapping
|
||
is unchanged — without a `.mise.toml` it's a no-op.
|
||
|
||
### Changed
|
||
|
||
- `vm exec --guest-path` default in `--help` now reads "from last
|
||
workspace prepare; otherwise root's home" (was "or /root/repo").
|
||
Anyone who relied on the implicit `/root/repo` default for a VM that
|
||
has a repo there but no workspace record must now pass
|
||
`--guest-path /root/repo` explicitly.
|
||
|
||
### Notes
|
||
|
||
- Internal: smoke-test harness ported from `scripts/smoke.sh` to a
|
||
Go test suite under `internal/smoketest`. `make smoke` is unchanged
|
||
for maintainers; no user-visible effect.
|
||
|
||
## [v0.1.8] - 2026-05-01
|
||
|
||
### Fixed
|
||
|
||
- `<vm>.vm` resolution from the host (NSS path: curl, ssh hostname,
|
||
etc.) now works on systemd-resolved hosts. The root helper's
|
||
`validateResolverAddr` was rejecting the `host:port` form
|
||
(`127.0.0.1:42069`) that banger constructs to point resolved at the
|
||
in-process DNS server, so the auto-wire silently failed at every
|
||
daemon startup. `dig @127.0.0.1` worked because that bypasses NSS;
|
||
any tool going through glibc's resolver chain didn't.
|
||
- Validator now accepts both bare IPs and `IP:port` (matching what
|
||
`resolvectl dns` itself accepts) with new test coverage for the
|
||
port'd form.
|
||
|
||
### Notes
|
||
|
||
- Existing v0.1.x installs that already booted with the broken
|
||
validator have stale per-link resolved state. After updating to
|
||
v0.1.8, run `sudo banger system restart` once to re-trigger the
|
||
auto-wire, or restart the host. systemd-resolved restarts also
|
||
wipe per-link state — banger restores it on its own daemon
|
||
startup but won't re-run for an already-running daemon.
|
||
|
||
## [v0.1.7] - 2026-05-01
|
||
|
||
### Added
|
||
|
||
- `vm run -d` / `--detach` creates the VM, runs workspace prep + tooling
|
||
bootstrap, then exits without attaching to ssh. Reconnect later with
|
||
`banger vm ssh <name>`. The combos `-d --rm` and `-d -- <cmd>` are
|
||
rejected before VM creation.
|
||
- `vm run --no-bootstrap` skips the mise tooling install entirely; useful
|
||
when a workspace has a `.mise.toml` you don't want banger to act on.
|
||
- `banger doctor --verbose` / `-v` prints every check with details.
|
||
Without it, doctor's default output now collapses (see Changed).
|
||
|
||
### Changed
|
||
|
||
- **`vm run` refuses early when bootstrap can't succeed.** Previously, a
|
||
workspace containing `.mise.toml` or `.tool-versions` without `--nat`
|
||
set silently failed the bootstrap into a log file and dropped you into
|
||
ssh with tools missing. It now refuses before VM creation with
|
||
`tooling bootstrap requires --nat (or pass --no-bootstrap to skip)`.
|
||
Existing scripts that relied on the silent-failure path will need to
|
||
add `--nat` or `--no-bootstrap`.
|
||
- **`banger doctor` default output is now compact.** A healthy host
|
||
collapses to a single line (`all N checks passed`); failing or warning
|
||
checks print only the affected entries plus a summary footer
|
||
(`N passed, M warnings, K failures`). Pass `--verbose` for the full
|
||
per-check output. Anything parsing the previous always-verbose output
|
||
needs to switch to `doctor --verbose`.
|
||
|
||
### Fixed
|
||
|
||
- The detached bootstrap path runs synchronously (foreground, tee'd to
|
||
the existing log file) so the CLI only returns once installs finish.
|
||
Interactive mode keeps today's nohup'd background behaviour so the ssh
|
||
session starts promptly.
|
||
|
||
## [v0.1.6] - 2026-04-29
|
||
|
||
### Fixed
|
||
|
||
- v0.1.4's "running VMs survive daemon restart" fix was incomplete:
|
||
the binary-level reconcile path was correct, but `/run/banger` (the
|
||
daemon's runtime dir) was being wiped on every daemon stop because
|
||
systemd defaults to `RuntimeDirectoryPreserve=no`. The api-sock
|
||
symlinks the helper had created for live VMs vanished with it,
|
||
and `findByJailerPidfile` couldn't resolve them to find the chroot
|
||
+ pidfile. v0.1.6 sets `RuntimeDirectoryPreserve=yes` on both
|
||
unit templates so the symlinks (and helper RPC sock) survive
|
||
the restart window. Live-verified: FC PID and guest boot_id both
|
||
unchanged across a full helper+daemon restart cycle with a VM
|
||
running.
|
||
- v0.1.4's CHANGELOG correction stands: existing v0.1.x installs
|
||
(where x < 6) need a one-time `sudo banger system install` after
|
||
updating to v0.1.6 to pick up both the new `KillMode=process` and
|
||
the new `RuntimeDirectoryPreserve=yes` directives. `banger update`
|
||
swaps binaries, not unit files.
|
||
|
||
## [v0.1.5] - 2026-04-29
|
||
|
||
No functional changes. Verification release for v0.1.4: the previous
|
||
release shipped the running-VMs-survive-update fix, but updating
|
||
*to* v0.1.4 from v0.1.3 used v0.1.3's buggy driver, so the fix
|
||
couldn't be verified live in that direction. v0.1.5 exists so a
|
||
host on v0.1.4 can update to it and observe a running VM survive
|
||
end-to-end with v0.1.4 in the driver seat.
|
||
|
||
## [v0.1.4] - 2026-04-29
|
||
|
||
### Fixed
|
||
|
||
- Daemon restarts no longer kill running VMs. Two changes together:
|
||
- The `bangerd-root.service` and `bangerd.service` unit templates
|
||
now set `KillMode=process`. The default (`control-group`) sent
|
||
SIGKILL to every process in the unit's cgroup on stop/restart,
|
||
including the jailer-spawned firecracker children — fork/exec
|
||
doesn't escape a systemd cgroup. With `KillMode=process` only
|
||
the unit's main PID is signalled; firecracker children survive.
|
||
- `fcproc.FindPID` now also looks up jailer'd firecracker
|
||
processes via the pidfile jailer writes at
|
||
`<chroot>/firecracker.pid` (sibling of the api-sock target).
|
||
Previously the only lookup path was `pgrep -n -f <api-sock>`,
|
||
which can't see jailer'd processes because their cmdline only
|
||
carries the chroot-relative `--api-sock /firecracker.socket`.
|
||
Reconcile after a daemon restart now correctly re-attaches to
|
||
surviving guests instead of mistaking them for stale and tearing
|
||
down their dm-snapshot.
|
||
|
||
### Notes
|
||
|
||
- v0.1.0's CHANGELOG line "daemon restarts do not interrupt running
|
||
guests" was wrong: it was true at the systemd cgroup layer in
|
||
theory but the default `KillMode` defeated it, and even with
|
||
`KillMode=process` the daemon's reconcile would mistake
|
||
surviving FCs for stale and tear them down. v0.1.4 is the version
|
||
where this actually works end-to-end.
|
||
- Updating from v0.1.0–v0.1.3 to v0.1.4 still kills running VMs
|
||
because the *driver* of the update is the buggy older binary.
|
||
Updates from v0.1.4 onward preserve running VMs across the
|
||
helper+daemon restart that `banger update` performs.
|
||
- Existing v0.1.0–v0.1.3 installs that update to v0.1.4 do NOT
|
||
automatically pick up the new unit files — `banger update` swaps
|
||
binaries, not systemd units. Run `sudo banger system install` once
|
||
on those hosts after updating to refresh the units. New v0.1.4+
|
||
installs get the correct units from the start.
|
||
|
||
## [v0.1.3] - 2026-04-29
|
||
|
||
No functional changes. Verification release: v0.1.2 fixed
|
||
`banger update`'s install.toml handling, but the fix only takes
|
||
effect when v0.1.2 (or later) is the driver of an update. v0.1.3
|
||
exists so a host running v0.1.2 can update to it and confirm the
|
||
fix works end-to-end with the new code in the driver seat.
|
||
|
||
## [v0.1.2] - 2026-04-29
|
||
|
||
### Fixed
|
||
|
||
- `banger update` now writes the freshly-installed binary's commit
|
||
and built_at fields to `/etc/banger/install.toml`, not the running
|
||
CLI's. Previously install.toml's `version` was correct after an
|
||
update but `commit` + `built_at` still pointed at the pre-update
|
||
binary's identity, which made `banger doctor` raise a false-positive
|
||
"CLI/install drift" warning on every update. Caught by the v0.1.0
|
||
→ v0.1.1 live update smoke-test.
|
||
|
||
## [v0.1.1] - 2026-04-29
|
||
|
||
### Added
|
||
|
||
- `install.sh` — one-command installer published at
|
||
`https://releases.thaloco.com/banger/install.sh`. Runs as the
|
||
invoking user, downloads + verifies the latest signed release with
|
||
the embedded cosign public key, and re-execs `sudo` only for the
|
||
actual system-install step. Pre-sudo summary explains in plain
|
||
language why elevation is needed.
|
||
- `BANGER_INSTALL_NONINTERACTIVE=1` env var on `install.sh` for
|
||
non-interactive use through `curl | bash` (CI, automated provisioning).
|
||
|
||
## [v0.1.0] - 2026-04-29
|
||
|
||
First public release. banger runs disposable development sandboxes as
|
||
Firecracker microVMs: each sandbox boots in a few seconds, gets its own
|
||
root filesystem and network, and exits on demand.
|
||
|
||
### Added
|
||
|
||
**Sandbox VMs**
|
||
- `banger vm run` boots a microVM, drops you into ssh, and tears it down
|
||
on exit. Optional positional path ships a host repo into the guest;
|
||
`-- cmd args` runs a command non-interactively and exits with its
|
||
status.
|
||
- Long-lived VMs via `vm create` / `vm start` / `vm stop` /
|
||
`vm restart` / `vm ssh` / `vm exec` / `vm logs` / `vm stats` /
|
||
`vm ports` / `vm kill`. `vm list` and `ps` enumerate state;
|
||
`vm prune` deletes every non-running VM.
|
||
- `vm workspace` ships a host repo into a guest and pulls diffs back.
|
||
- Per-VM cgroup-isolated firecracker process under jailer chroot;
|
||
daemon restarts do not interrupt running guests.
|
||
|
||
**Images**
|
||
- `banger image pull <name>` pulls a curated rootfs+kernel bundle from
|
||
the banger image catalog. `image pull <oci-ref>` pulls any OCI image.
|
||
- `image list` / `image show` / `image delete` / `image promote` /
|
||
`image register` round out the lifecycle.
|
||
- `image cache` manages the OCI layer-blob cache.
|
||
- Concurrent pulls of the same image are coalesced; the first pull
|
||
wins, the rest wait.
|
||
|
||
**Kernels**
|
||
- `banger kernel pull <name>` pulls a Firecracker-compatible kernel
|
||
from the banger kernel catalog. `kernel list` / `kernel show` /
|
||
`kernel rm` manage the local store.
|
||
|
||
**Host networking**
|
||
- Per-host bridge with NAT; per-VM tap device; deterministic IPv4
|
||
assignment; iptables rules installed/removed with VM lifecycle.
|
||
- DNS routing: local resolver on `127.0.0.1:42069` answers queries
|
||
for `<vm>.vm` so plain `ssh <vm>.vm` reaches the guest.
|
||
- `banger ssh-config` writes a one-time `~/.ssh/config` include so
|
||
ssh, scp, and rsync resolve `<vm>.vm` from any terminal.
|
||
|
||
**System install**
|
||
- `sudo banger system install` installs an owner-mode daemon
|
||
(`bangerd.service`) and a root-helper (`bangerd-root.service`) as
|
||
systemd units. The owner daemon runs as the invoking user; only the
|
||
root helper holds privilege, and only for a vetted set of operations.
|
||
- `system status` / `system restart` / `system uninstall` round out
|
||
the lifecycle. `daemon` is a thin alias.
|
||
- `banger doctor` audits host readiness: architecture, CLI/install
|
||
version drift, state store, host runtime, vm lifecycle prerequisites,
|
||
vsock guest agent, vm defaults, ssh shortcut, /root work disk, DNS,
|
||
NAT, firecracker binary version, systemd units, socket permissions,
|
||
helper unit hardening directives.
|
||
|
||
**Self-update**
|
||
- `banger update` downloads, verifies, and installs newer releases
|
||
from the public manifest. Flow: fetch manifest, refuse if any VM
|
||
operation is in flight, download tarball + `SHA256SUMS` +
|
||
`SHA256SUMS.sig`, verify the cosign signature against the embedded
|
||
public key, verify the tarball hash, stage to a scratch dir, run
|
||
`bangerd --check-migrations` against the staged binary, atomically
|
||
swap the three banger binaries, restart the systemd units, run
|
||
`banger doctor`, finalise the install record.
|
||
- Pre-restart abort and post-restart auto-rollback both restore the
|
||
previous install on failure.
|
||
- `banger update --check` reports whether a newer release is
|
||
available without applying it; `--to vX.Y.Z` pins a specific
|
||
version; `--dry-run` prints the plan; `--force` skips the
|
||
in-flight-op refusal.
|
||
|
||
**Trust model**
|
||
- Every release is cosign-signed. The public key is embedded in the
|
||
banger binary at build time; the signed payload is `SHA256SUMS`,
|
||
which in turn covers the release tarball. Verification uses the
|
||
Go standard library (`crypto/ecdsa.VerifyASN1`); cosign is needed
|
||
only for *signing*, not for verification.
|
||
- The release manifest URL is hardcoded into the binary so a
|
||
compromised daemon config cannot redirect the updater to a different
|
||
bucket.
|
||
|
||
**CLI surface**
|
||
- Top-level: `vm`, `ps`, `image`, `kernel`, `ssh-config`, `system`,
|
||
`daemon`, `doctor`, `update`, `version`, `completion`.
|
||
- `banger version` reports the version, commit SHA, and build
|
||
timestamp baked in via ldflags at release-build time.
|
||
|
||
### Compatibility
|
||
|
||
- The host-side and guest-side vsock agent protocol is informally
|
||
stable across **patch** versions (v0.1.x). Minor-version bumps
|
||
(v0.2.x) may change it; existing VMs created against an older
|
||
minor will need to be re-pulled. `banger doctor` warns when a
|
||
running VM's agent is older than the daemon expects but does not
|
||
block lifecycle operations.
|
||
- The on-disk store schema is forward-only. Downgrading the binary
|
||
against a database written by a newer binary is unsupported; the
|
||
updater detects this via `bangerd --check-migrations` and refuses
|
||
the swap rather than starting up against an incompatible store.
|
||
- Linux only. amd64 only. KVM required.
|
||
|
||
[Unreleased]: https://git.thaloco.com/thaloco/banger/compare/v0.1.10...HEAD
|
||
[v0.1.10]: https://git.thaloco.com/thaloco/banger/releases/tag/v0.1.10
|
||
[v0.1.9]: https://git.thaloco.com/thaloco/banger/releases/tag/v0.1.9
|
||
[v0.1.8]: https://git.thaloco.com/thaloco/banger/releases/tag/v0.1.8
|
||
[v0.1.7]: https://git.thaloco.com/thaloco/banger/releases/tag/v0.1.7
|
||
[v0.1.6]: https://git.thaloco.com/thaloco/banger/releases/tag/v0.1.6
|
||
[v0.1.5]: https://git.thaloco.com/thaloco/banger/releases/tag/v0.1.5
|
||
[v0.1.4]: https://git.thaloco.com/thaloco/banger/releases/tag/v0.1.4
|
||
[v0.1.3]: https://git.thaloco.com/thaloco/banger/releases/tag/v0.1.3
|
||
[v0.1.2]: https://git.thaloco.com/thaloco/banger/releases/tag/v0.1.2
|
||
[v0.1.1]: https://git.thaloco.com/thaloco/banger/releases/tag/v0.1.1
|
||
[v0.1.0]: https://git.thaloco.com/thaloco/banger/releases/tag/v0.1.0
|