New `--rm` flag deletes the VM once the ssh session or `-- cmd` exits, making `vm run` one-shot. Exit code from command mode still propagates correctly. Semantics: - Create fails → no VM to delete, nothing to do. - SSH-wait timeout → VM intentionally kept alive so `vm logs <name>` shows why; the timeout error already pointed users at that. Even with --rm, this path skips delete — a wedged sshd is exactly when you want post-mortem access. - Session/command ends (any exit code, any reason) → VM is deleted via `vm.delete` RPC. Uses a fresh 10s context so Ctrl-C during the session doesn't abort the cleanup. New vmDeleteFunc seam at the top of banger.go alongside the other RPC seams. Two tests cover the happy path (session ends cleanly → delete fires with correct ref) and the skip-on-timeout path (ssh wait errors → delete does NOT fire). README updated with an ephemeral example and a note about the timeout-skip behaviour. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
196 lines
5.9 KiB
Markdown
196 lines
5.9 KiB
Markdown
# banger
|
|
|
|
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 on `PATH`, or `firecracker_bin` set in config
|
|
- host tools checked by `banger doctor`
|
|
|
|
## Build + install
|
|
|
|
```bash
|
|
make install
|
|
```
|
|
|
|
Installs:
|
|
|
|
- `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
|
|
banger vm run --rm -- script.sh # ephemeral: VM is deleted on exit
|
|
```
|
|
|
|
- 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 — or pass `--rm` so the VM
|
|
auto-deletes once the session / command exits.
|
|
|
|
`--branch` and `--from` apply only to workspace mode.
|
|
|
|
`--rm` delete is skipped when the initial ssh wait times out, so a
|
|
wedged sshd leaves the VM alive for `banger vm logs` inspection.
|
|
|
|
## 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
|
|
```
|
|
|
|
For custom images, write a Dockerfile and either publish to the
|
|
catalog (see `docs/image-catalog.md`) or pull it via the OCI path.
|
|
|
|
### 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`. All keys optional.
|
|
|
|
Commonly set:
|
|
|
|
- `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`.
|
|
|
|
Full key list in `internal/config/config.go`.
|
|
|
|
## Credential sync
|
|
|
|
If these host auth files exist, banger syncs them into the guest at
|
|
VM start:
|
|
|
|
| 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` |
|
|
|
|
Host-side changes take effect after the VM restarts. Session/history
|
|
directories are not copied.
|
|
|
|
## Web UI (experimental)
|
|
|
|
`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:
|
|
|
|
```
|
|
PermitRootLogin yes
|
|
StrictModes no
|
|
```
|
|
|
|
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
|
|
|
|
- Managed image delete removes the daemon-owned artifact dir.
|
|
- 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.
|