`vm run` now covers bare sandbox (no args), workspace sandbox (path), and workspace+command (path -- cmd) in a single entry point. Replaces the old print-next-steps-and-exit behaviour: bare and workspace modes drop into interactive ssh, command mode execs via ssh and propagates the remote exit code through banger's own exit status. - path argument is optional; --branch / --from still require a path. - workspace prep and mise tooling bootstrap only run when a path is given; command mode skips the bootstrap. - remote command exit status is wrapped as exitCodeError so main() can propagate it instead of collapsing every failure to 1. - README: promote vm run with three-mode examples; demote vm create to a scripting primitive. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
330 lines
9.7 KiB
Markdown
330 lines
9.7 KiB
Markdown
# 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`, or `firecracker_bin` set 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
|
|
|
|
```bash
|
|
make build
|
|
```
|
|
|
|
This writes:
|
|
|
|
- `./build/bin/banger`
|
|
- `./build/bin/bangerd`
|
|
- `./build/bin/banger-vsock-agent`
|
|
|
|
## Install
|
|
|
|
```bash
|
|
make install
|
|
```
|
|
|
|
That installs:
|
|
|
|
- `banger`
|
|
- `bangerd`
|
|
- the `banger-vsock-agent` companion helper under `../lib/banger/`
|
|
|
|
## Config
|
|
|
|
Config lives at `~/.config/banger/config.toml`.
|
|
|
|
Supported keys:
|
|
|
|
- `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`
|
|
|
|
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:
|
|
|
|
```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.
|
|
|
|
## 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.
|
|
|
|
## 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 `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.
|