docs: promote vm run + image catalog as the happy path

Lead the README with `banger vm run` (one command, auto-pull default
image + kernel from the catalogs), move `image register` / `image
build` / OCI-pull to a "power-user flows" section. Golden-image
content from customize.sh moves to the golden-image Dockerfile story.

New `docs/image-catalog.md` mirrors `docs/kernel-catalog.md` — the
bundle format, content-addressed filenames, publish flow, trust
model, R2 hosting. Cross-links with oci-import.md.

`docs/oci-import.md` refactored to document the OCI-pull path as the
fallthrough for arbitrary registry refs (it's the secondary path now
that the catalog covers the headline debian-bookworm case). Phase A
caveats removed — ownership fixup, agent injection, and first-boot
sshd install all landed.

AGENTS.md: promotes `vm run` as the smoke-test primitive, notes the
default-image auto-pull behaviour, and points at both catalog docs.

README shrinks 330 → 198 lines, mostly by removing the experimental
void/alpine sections (those flows still work as advanced scripts but
the README no longer advertises them).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Thales Maciel 2026-04-18 15:33:30 -03:00
parent 75baf2e415
commit 8029b2e1bc
No known key found for this signature in database
GPG key ID: 33112E6833C34679
4 changed files with 404 additions and 450 deletions

448
README.md
View file

@ -1,330 +1,198 @@
# banger
`banger` manages Firecracker development VMs with a local daemon, managed image artifacts, and an experimental localhost web UI.
One-command development sandboxes on Firecracker microVMs.
## Quick start
```bash
make install
banger vm run --name sandbox
```
`banger vm run` auto-pulls the default golden image (Debian bookworm
with systemd, sshd, Docker CE, git, jq, mise, and the usual dev tools)
and kernel from the embedded catalog if they aren't already local,
creates a VM, starts it, and drops you into an interactive ssh
session. First run takes a couple minutes (bundle download);
subsequent `vm run`s are seconds.
## Requirements
- Linux with `/dev/kvm`
- `sudo`
- Firecracker installed on `PATH`, or `firecracker_bin` set in config
- The usual host tools checked by `./build/bin/banger doctor`
- Firecracker on `PATH`, or `firecracker_bin` set in config
- host tools checked by `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
```bash
make build
```
This writes:
- `./build/bin/banger`
- `./build/bin/bangerd`
- `./build/bin/banger-vsock-agent`
## Install
## Build + install
```bash
make install
```
That installs:
Installs:
- `banger`
- `bangerd`
- the `banger-vsock-agent` companion helper under `../lib/banger/`
- `banger` (CLI)
- `bangerd` (daemon, auto-starts on first CLI call)
- `banger-vsock-agent` (companion, under `$PREFIX/lib/banger/`)
## `vm run`
One command, three modes:
```bash
banger vm run # bare sandbox — drops into ssh
banger vm run ./repo # workspace at /root/repo — drops into ssh
banger vm run ./repo -- make test # workspace + run command, exit with its status
```
- Bare mode gives you a clean shell.
- Workspace mode (with a path) copies the repo's tracked + untracked
non-ignored files into `/root/repo` and kicks off a best-effort
mise tooling bootstrap from the repo's `.mise.toml` /
`.tool-versions`. Log: `/root/.cache/banger/vm-run-tooling-<repo>.log`.
- Command mode (`-- <cmd>`) runs the command in the guest; exit code
propagates through `banger`.
Disconnecting from an interactive session leaves the VM running. Use
`vm stop` / `vm delete` to clean up.
`--branch` and `--from` apply only to workspace mode.
## Image catalog
`banger image pull <name>` resolves `<name>` in the embedded catalog
and fetches a pre-built bundle (rootfs.ext4 + manifest, tar+zstd). The
kernel referenced by the manifest auto-pulls too. `vm run` calls this
for you on demand.
Today's catalog:
| Name | Distro | Kernel |
|------|--------|--------|
| `debian-bookworm` | Debian 12 slim + sshd + docker + dev tools | `generic-6.12` |
The catalog ships embedded in the banger binary. See
[`docs/image-catalog.md`](docs/image-catalog.md) for maintenance.
## Power-user flows
Skip this section if `vm run` is enough.
### `vm create` — low-level primitive
For scripting or `--no-start` provisioning:
```bash
banger vm create --image debian-bookworm --name testbox --no-start
banger vm start testbox
banger vm ssh testbox
banger vm stop testbox
```
### `image pull <oci-ref>` — arbitrary container images
For images outside the catalog, pull from any OCI registry:
```bash
banger image pull docker.io/library/alpine:3.20 --kernel-ref generic-6.12
```
Layers are flattened, ownership is fixed, banger's guest agents are
injected, and a first-boot service installs `openssh-server` via the
guest's package manager. See [`docs/oci-import.md`](docs/oci-import.md)
for supported distros and caveats.
### `image register` — existing host-side stack
```bash
banger image register --name base \
--rootfs /abs/path/rootfs.ext4 \
--kernel-ref generic-6.12
```
### `image build --from-image` — derived images
```bash
banger image build --name devbox --from-image debian-bookworm --docker
```
Spins up a transient VM from a base image, applies opinionated
customisation (mise, claude, pi, tmux plugins), saves a new managed
image.
### Workspace + session primitives
Long-lived guest commands managed by the daemon, attachable over a
local Unix socket bridge:
```bash
banger vm workspace prepare <vm> ./other-repo --guest-path /root/repo
banger vm session start <vm> --name planner --cwd /root/repo --stdin-mode pipe -- pi --mode rpc
banger vm session attach <vm> planner
banger vm session logs <vm> planner --stream stderr
banger vm session stop <vm> planner
```
For ACP-aware host tooling: `banger vm acp <vm>` bridges stdio to
guest `opencode acp` over SSH.
## Config
Config lives at `~/.config/banger/config.toml`.
Config lives at `~/.config/banger/config.toml`. All keys optional.
Supported keys:
Commonly set:
- `log_level`
- `web_listen_addr`
- `firecracker_bin`
- `ssh_key_path`
- `default_image_name`
- `auto_stop_stale_after`
- `stats_poll_interval`
- `metrics_poll_interval`
- `bridge_name`
- `bridge_ip`
- `cidr`
- `tap_pool_size`
- `default_dns`
- `default_image_name` — image to use when `--image` is omitted
(defaults to `debian-bookworm`, auto-pulled from the catalog if not
local).
- `ssh_key_path` — host SSH key; if unset banger creates
`~/.config/banger/ssh/id_ed25519`.
- `firecracker_bin` — override the auto-resolved `PATH` lookup.
- `web_listen_addr` — experimental web UI (default
`127.0.0.1:7777`; set to `""` to disable).
- Network: `bridge_name`, `bridge_ip`, `cidr`, `tap_pool_size`,
`default_dns`.
If `ssh_key_path` is unset, banger creates and uses:
Full key list in `internal/config/config.go`.
- `~/.config/banger/ssh/id_ed25519`
## Credential sync
`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.
If these host auth files exist, banger syncs them into the guest at
VM start:
## Core Workflow
| Host | Guest |
|------|-------|
| `~/.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` |
Check the host:
```bash
./build/bin/banger doctor
```
Register an existing host-side image stack:
```bash
./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:
```bash
./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`](docs/kernel-catalog.md) for catalog
maintenance.
Or pull a rootfs directly from any OCI registry (Docker Hub, GHCR, …):
```bash
./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`](docs/oci-import.md) for
supported distros and current limitations.
Build a managed image from an existing registered image:
```bash
./build/bin/banger image build \
--name devbox \
--from-image base \
--docker
```
Promote an unmanaged image into daemon-owned managed artifacts:
```bash
./build/bin/banger image promote base
```
Spin up a sandbox VM and drop straight into it:
```bash
./build/bin/banger vm run # bare sandbox, interactive ssh
./build/bin/banger vm run ../some-repo # workspace at /root/repo, interactive ssh
./build/bin/banger vm run ../some-repo -- make test # workspace, run command, exit with its status
```
`vm run` creates a VM, prepares a workspace if you pass a path, and then either drops you into an interactive ssh session or runs the `--`-delimited command to completion. The command's exit code propagates through `banger`. Disconnecting from the interactive session leaves the VM running; use `vm stop` / `vm delete` to clean up.
When you pass a path, `vm run` copies a git checkout plus tracked and untracked non-ignored files into `/root/repo`, then kicks off a best-effort `mise` tooling bootstrap that runs asynchronously inside the guest (log at `/root/.cache/banger/vm-run-tooling-<repo>.log`). The bootstrap is skipped in bare and command modes. Flags like `--branch` and `--from` require a path.
For scripting or lower-level control, `vm create` remains available as a primitive (use `--no-start` when you just want to provision):
```bash
./build/bin/banger vm create --image devbox --name testbox --no-start
./build/bin/banger vm start 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.
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:
```bash
./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.
Host-side changes take effect after the VM restarts. Session/history
directories are not copied.
## 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:
```bash
./build/bin/banger daemon status
```
Disable it with:
```toml
web_listen_addr = ""
```
## Guest Services
Provisioned glibc-backed images include:
- `banger-vsock-agent`
- guest networking bootstrap
- `mise`
- `opencode`
- `claude`
- `pi`
- a default guest `opencode` service on `0.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:
```bash
./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:
```bash
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:
```bash
make void-kernel
```
Build the experimental Void rootfs:
```bash
make rootfs-void
```
Register it:
```bash
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:
```bash
make alpine-kernel
```
Build the experimental Alpine rootfs:
```bash
make rootfs-alpine
```
Register it:
```bash
make alpine-register
```
Create a VM from it:
```bash
./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.
`bangerd` serves a local web UI at `http://127.0.0.1:7777` by default.
Convenient for local observability, **not a stable interface**. Do
not expose the listen address to a shared network.
## Security
Guest VMs are single-user development sandboxes, not multi-tenant servers.
Every provisioned image is configured with:
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.
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 guest IPs to an untrusted network.
## Notes
- Firecracker is resolved from `PATH` by 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.
- Layer blob cache for OCI pulls lives under `~/.cache/banger/oci/`.
- Image bundle cache doesn't exist — bundles are extracted directly
into the image store; re-pulls download fresh.