vm exec defaulted execGuestPath to /root/repo whenever the VM had no
recorded workspace, so running it 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 auto-cd only fires when --guest-path is passed
or the VM actually has a workspace recorded; otherwise the command
runs from root's home. Mise wrapping unchanged — without a .mise.toml
it's a no-op.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The root helper's resolver-address validator only accepted bare IPs,
so `resolvectl dns <bridge> 127.0.0.1:42069` — banger's own auto-wire
call to point systemd-resolved at the in-process DNS server — was
rejected before it ever reached resolvectl. The auto-wire is
best-effort and only logs a warning on failure, so .vm resolution
silently broke on the NSS path: dig @127.0.0.1 worked, curl <vm>.vm
didn't.
Validator now allows both bare IPs and IP:port (matching what
`resolvectl dns` itself accepts), with new test coverage for the
port'd form.
Existing installs need a one-time `sudo banger system restart` after
updating to v0.1.8 so the daemon re-runs the auto-wire with the
fixed validator.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v0.1.4 fixed the binary-level reconcile path for jailer'd VMs but
left a hole at the systemd layer: bangerd.service and bangerd-root.service
both defaulted to RuntimeDirectoryPreserve=no, so /run/banger was
wiped on every daemon stop. The api-sock symlinks the helper creates
for live VMs (`/run/banger/fc-<id>.sock` → `<chroot>/firecracker.socket`)
went with it, and findByJailerPidfile — which derives the chroot
from the symlink target — couldn't resolve them. Reconcile then fell
through to "stale_vm" and tore down the surviving FC's dm-snapshot.
Add RuntimeDirectoryPreserve=yes to both unit templates so the
symlinks survive the restart window. Live-verified end-to-end on
the dev host: started a VM under v0.1.5, restarted helper +
daemon, confirmed the FC PID was unchanged and `banger vm ssh`
returned the same boot_id pre and post.
Daemon-lifecycle tests updated to assert the new directive is
present in both rendered units so future regressions show up at
test time.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
No code changes. Cuts a fresh release purely so a host on v0.1.4
can run `banger update` and confirm v0.1.4's running-VMs-survive
fix actually works when v0.1.4 is the code driving the update —
during the v0.1.3→v0.1.4 update the buggy v0.1.3 reconcile was
still in the driver seat and tore down the running VM as documented.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two coupled fixes that together make the daemon-restart path of
`banger update` non-destructive for running guests:
1. Unit templates set `KillMode=process` on bangerd.service and
bangerd-root.service. The default control-group behaviour sent
SIGKILL to every process in the cgroup on stop/restart — including
jailer-spawned firecracker children, since fork/exec doesn't
escape a systemd cgroup. With process mode only the unit's main
PID is signalled; FC children stay alive in the (unowned)
cgroup until the new helper instance starts up and re-claims them.
2. `fcproc.FindPID` falls back to the jailer-written pidfile at
`<chroot>/firecracker.pid` (sibling of the api-sock target) when
`pgrep -n -f <api-sock>` doesn't find a match. pgrep can't see
jailer'd FCs because their cmdline only carries the chroot-relative
`--api-sock /firecracker.socket`, not the host-side path. The
pidfile is jailer's actual record of the post-exec FC PID, so
reconcile can verify the surviving process is the right one
(comm == "firecracker") and re-seed handles.json without tearing
down the VM's dm-snapshot.
Verified live on the dev host: started a VM, restarted the helper
unit, restarted the daemon unit, and confirmed the FC PID was
unchanged, vm list still showed the guest as running, and
`banger vm ssh` returned the same boot_id pre and post restart.
The systemd journal now reports "firecracker remains running after
unit stopped" and "Found left-over process X (firecracker) in
control group while starting unit. Ignoring." — exactly the shape
`KillMode=process` is supposed to produce.
Tests cover both the parser (parseVersionOutput from the v0.1.2
fix) and the new pidfile lookup: happy path, missing pidfile,
stale pid, wrong comm, garbage content, non-symlink api-sock,
whitespace tolerance.
CHANGELOG corrects v0.1.0's misleading "daemon restarts do not
interrupt running guests" line and documents the unit-refresh
caveat: existing v0.1.0–v0.1.3 installs need a one-time
`sudo banger system install` after updating to v0.1.4 to pick up
the new KillMode directive (`banger update` swaps binaries, not
unit files).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
No code changes. Cuts a fresh release purely so a host on v0.1.2
can run `banger update` and confirm v0.1.2's install.toml-refresh
fix actually works when v0.1.2 is the code driving the update —
during the v0.1.1→v0.1.2 update the buggy v0.1.1 code was still
in the driver seat, so live verification of the fix needs one more
cycle.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After `banger update` swaps binaries, install.toml needs to reflect
the just-installed identity. The previous code passed
buildinfo.Current().{Commit,BuiltAt} into installmeta.UpdateBuildInfo
— but buildinfo.Current() in the running CLI is the OLD pre-swap
binary's identity (we're it), not the staged one. install.toml's
version field got refreshed to target.Version while commit and
built_at stayed pinned at the previous release. `banger doctor`
compares the running CLI's three fields against install.toml's
three fields and so raised a false-positive drift warning on
every update.
Fix: after the swap, exec /usr/local/bin/banger version, parse the
three-line output, and write all three fields to install.toml. If
the exec fails for any reason we fall back to the old behaviour
(version + stale commit/built_at) with a warning, since install.toml
drift is a doctor warning not a broken host — same posture as
before for the failure path.
The parser is split out (parseVersionOutput) and table-tested:
happy path, whitespace-tolerance, missing-field rejection, empty
input rejection, ignoring unrelated lines.
Caught by running v0.1.0 → v0.1.1 live as the first end-to-end
smoke test of the self-update flow, which was the whole point of
that exercise.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Captures the install.sh + BANGER_INSTALL_NONINTERACTIVE changes
that landed in 1004331 and 3c29af5. v0.1.1 is being cut now to
exercise the self-update path against a real released second
version — `banger update` has never run live before, only against
unit-test fixtures, so this release doubles as the smoke test of
the update flow.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
First-release changelog following the Keep a Changelog + SemVer
convention. The v0.1.0 section groups by capability area (sandbox
VMs, images, kernels, host networking, system install, self-update,
trust model, CLI surface) rather than by package, so it reads as
release notes for users deciding whether to install rather than as
a commit log. Includes a Compatibility section calling out the
informal vsock-protocol stability promise (stable across patches,
not minors) and the forward-only schema policy.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>