Closes the full arc: banger kernel pull + image pull + vm create + vm ssh now works end-to-end against docker.io/library/debian:bookworm with zero manual image building. Generic kernel: - New scripts/make-generic-kernel.sh builds vmlinux from upstream kernel.org sources using Firecracker's official minimal config (configs/firecracker-x86_64-6.1.config). All critical drivers (virtio_blk, virtio_net, ext4, vsock) compiled in — no modules, no initramfs needed. - Published as generic-6.12 in the catalog (kernels.thaloco.com). - catalog.json updated with the new entry. Direct-boot init= override (vm_lifecycle.go): - For images without an initrd (direct-boot / OCI-pulled), banger now passes init=/usr/local/libexec/banger-first-boot on the kernel cmdline. The script runs as PID 1, mounts /proc /sys /dev /run, checks for systemd — if present execs it immediately; if not (container images), installs systemd-sysv + openssh-server via the guest's package manager, then execs systemd. - Also passes kernel-level ip= parameter via BuildBootArgsWithKernelIP so the kernel configures the network interface before init runs (container images don't ship iproute2, so the userspace bootstrap script can't call ip(8)). - Masks dev-ttyS0.device and dev-vdb.device systemd units that otherwise wait 90s for udev events that never fire in Firecracker guests started from container rootfses. first-boot.sh rewritten as universal init wrapper: - Works as PID 1 (mounts essential filesystems) OR as a systemd oneshot (existing behavior). - Installs both systemd-sysv AND openssh-server (container images have neither). - Dispatch updated: debian, alpine, fedora, arch, opensuse families + ID_LIKE fallback. All tests updated. Opencode capability skip for direct-boot images: - The opencode readiness check (WaitReady on vsock port 4096) now returns nil for images without an initrd, since pulled container images don't ship the opencode service. Without this, the VM would be marked as error for lacking an opinionated add-on. Docs: README and kernel-catalog.md updated to recommend generic-6.12 as the default kernel for OCI-pulled images. AGENTS.md notes the new build script. Verified live: - banger kernel pull generic-6.12 - banger image pull docker.io/library/debian:bookworm --kernel-ref generic-6.12 - banger vm create --image debian-bookworm --name testbox --nat - banger vm ssh testbox -- "id; uname -r; systemctl is-active banger-vsock-agent" → uid=0(root), kernel 6.12.8, Debian bookworm, vsock-agent active, sshd running, SSH working. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
9.3 KiB
banger
banger manages Firecracker development VMs with a local daemon, managed image artifacts, and an experimental localhost web UI.
Requirements
- Linux with
/dev/kvm sudo- Firecracker installed on
PATH, orfirecracker_binset in config - The usual host tools checked by
./build/bin/banger doctor
banger now owns complete managed image sets. A managed image includes:
rootfs- optional
work-seed kernel- optional
initrd - optional
modules
There is no runtime bundle anymore.
Build
make build
This writes:
./build/bin/banger./build/bin/bangerd./build/bin/banger-vsock-agent
Install
make install
That installs:
bangerbangerd- the
banger-vsock-agentcompanion helper under../lib/banger/
Config
Config lives at ~/.config/banger/config.toml.
Supported keys:
log_levelweb_listen_addrfirecracker_binssh_key_pathdefault_image_nameauto_stop_stale_afterstats_poll_intervalmetrics_poll_intervalbridge_namebridge_ipcidrtap_pool_sizedefault_dns
If ssh_key_path is unset, banger creates and uses:
~/.config/banger/ssh/id_ed25519
default_image_name now only means “use this registered image when vm create omits --image”. The daemon does not auto-register images from host paths.
Core Workflow
Check the host:
./build/bin/banger doctor
Register an existing host-side image stack:
./build/bin/banger image register \
--name base \
--rootfs /abs/path/rootfs.ext4 \
--kernel /abs/path/vmlinux \
--initrd /abs/path/initrd.img \
--modules /abs/path/modules
Or pull a pre-built kernel from the catalog and reference it by name:
./build/bin/banger kernel list --available
./build/bin/banger kernel pull generic-6.12
./build/bin/banger image register \
--name base \
--rootfs /abs/path/rootfs.ext4 \
--kernel-ref generic-6.12
See docs/kernel-catalog.md for catalog
maintenance.
Or pull a rootfs directly from any OCI registry (Docker Hub, GHCR, …):
./build/bin/banger image pull docker.io/library/debian:bookworm \
--kernel-ref generic-6.12
image pull downloads the image, flattens its layers into an ext4
rootfs, applies tar-header ownership via debugfs, and pre-injects
banger's guest agents (vsock agent + network bootstrap + a first-boot
unit that installs openssh-server via the guest's native package
manager). Boots as a banger VM directly, no image build step
required. See docs/oci-import.md for
supported distros and current limitations.
Build a managed image from an existing registered image:
./build/bin/banger image build \
--name devbox \
--from-image base \
--docker
Promote an unmanaged image into daemon-owned managed artifacts:
./build/bin/banger image promote base
Create and use a VM:
./build/bin/banger vm create --image devbox --name testbox
./build/bin/banger vm ssh testbox
./build/bin/banger vm stop testbox
vm create stays synchronous by default, but on a TTY it now shows live progress until the VM is fully ready.
Start a repo-backed VM session:
./build/bin/banger vm run
./build/bin/banger vm run ../some-repo --branch feature/alpine --from HEAD
vm run resolves the enclosing git repository, creates a VM, copies a git checkout plus current tracked and untracked non-ignored files into /root/repo, starts a best-effort guest tooling bootstrap that only uses mise, prints next-step commands, and exits. It does not auto-attach opencode anymore. The bootstrap runs asynchronously and logs its output inside the guest.
After vm run, use one of:
./build/bin/banger vm ssh <vm-name>
opencode attach http://<vm-name>.vm:4096 --dir /root/repo
./build/bin/banger vm acp <vm-name>
./build/bin/banger vm ssh <vm-name> -- "cd /root/repo && claude"
./build/bin/banger vm ssh <vm-name> -- "cd /root/repo && pi"
For ACP-aware host tools, ./build/bin/banger vm acp <vm-name> bridges stdio to guest opencode acp over SSH. It uses /root/repo when that checkout exists, otherwise /root, and --cwd lets you override the guest working directory explicitly.
If you want reusable orchestration primitives instead of the vm run convenience flow, use the daemon-backed workspace and session commands directly:
./build/bin/banger vm workspace prepare <vm-name>
./build/bin/banger vm workspace prepare <vm-name> ../other-repo --guest-path /root/repo --readonly
./build/bin/banger vm session start <vm-name> --name planner --cwd /root/repo --stdin-mode pipe -- pi --mode rpc --no-session
./build/bin/banger vm session list <vm-name>
./build/bin/banger vm session attach <vm-name> planner
./build/bin/banger vm session logs <vm-name> planner --stream stderr
./build/bin/banger vm session stop <vm-name> planner
vm workspace prepare materializes a local git checkout into a running VM. The default guest path is /root/repo and the default mode is a shallow metadata copy plus tracked and untracked non-ignored overlay. Repositories with git submodules must use --mode full_copy; the metadata-based modes still reject them.
vm session start creates a daemon-managed long-lived guest command. The daemon preflights that the requested guest cwd exists and that the main command, plus any repeated --require-command entries, exist in guest PATH before launch. Use --stdin-mode pipe when you need live attach; otherwise use the default detached mode and inspect sessions with list, show, logs, stop, and kill.
vm session attach is currently exclusive and same-host only. The daemon exposes a local Unix socket bridge using stdio_mux_v1, so only one active attach is allowed at a time. Pipe-mode sessions keep enough guest-side state for the daemon to rebuild that bridge after a daemon restart.
Web UI (experimental)
bangerd serves an experimental local web UI by default at:
http://127.0.0.1:7777
The UI is convenient for local observability but is not a stable or supported interface. Its endpoints, layout, and behaviour may change without notice, and it has not been hardened for anything beyond single-user localhost use. Do not expose the listen address to a shared network.
See the effective URL with:
./build/bin/banger daemon status
Disable it with:
web_listen_addr = ""
Guest Services
Provisioned glibc-backed images include:
banger-vsock-agent- guest networking bootstrap
miseopencodeclaudepi- a default guest
opencodeservice on0.0.0.0:4096
Alpine currently remains opencode-only.
If these host auth files exist, banger syncs them into the guest on VM start:
~/.local/share/opencode/auth.json->/root/.local/share/opencode/auth.json~/.claude/.credentials.json->/root/.claude/.credentials.json~/.pi/agent/auth.json->/root/.pi/agent/auth.json
Changes on the host take effect after the VM is restarted. Session/history directories are not copied.
From the host:
./build/bin/banger vm ports testbox
opencode attach http://<guest-ip>:4096
Manual Helpers
The shell helpers are now explicit manual workflows under ./build/manual.
Rebuild a Debian-style manual rootfs:
make rootfs ARGS='--base-rootfs /abs/path/rootfs.ext4 --kernel /abs/path/vmlinux --initrd /abs/path/initrd.img --modules /abs/path/modules'
The output lands in:
./build/manual/rootfs-docker.ext4./build/manual/rootfs-docker.work-seed.ext4
Experimental Void Flow
Stage a Void kernel:
make void-kernel
Build the experimental Void rootfs:
make rootfs-void
Register it:
make void-register
That flow uses:
./build/manual/void-kernel/./build/manual/rootfs-void.ext4./build/manual/rootfs-void.work-seed.ext4
Experimental Alpine Flow
Stage an Alpine virt kernel:
make alpine-kernel
Build the experimental Alpine rootfs:
make rootfs-alpine
Register it:
make alpine-register
Create a VM from it:
./build/bin/banger vm create --image alpine --name alpine-dev
That flow uses:
./build/manual/alpine-kernel/./build/manual/rootfs-alpine.ext4./build/manual/rootfs-alpine.work-seed.ext4
The experimental Alpine flow stages a pinned Alpine release by default. Override
that pin with ALPINE_RELEASE=... when running the make alpine-kernel and
make rootfs-alpine helpers if you need a different patch release.
Alpine support currently applies to the explicit register-and-run flow above.
The generic banger image build --from-image ... path remains Debian/systemd-
oriented and should not be treated as an Alpine image builder.
Security
Guest VMs are single-user development sandboxes, not multi-tenant servers. Every provisioned image is configured with:
PermitRootLogin yes
StrictModes no
This is intentional. The host SSH key is the only authentication mechanism,
no password auth is enabled, and VMs are reachable only through the host
bridge network (172.16.0.0/24 by default). Do not expose the bridge
interface or the VM guest IPs to an untrusted network.
Notes
- Firecracker is resolved from
PATHby default. - Managed image delete removes the daemon-owned artifact dir.
- The companion vsock helper is internal to the install/build layout, not a user-configured runtime path.