The `internal/opencode` package and the `opencodeCapability` that
consumed it were hard-wired to wait for opencode on guest port 4096
when an image shipped an initrd. After the prune commits (void /
alpine / customize.sh / image build all removed), nothing banger
produces today carries an initrd, so the capability's wait path was
unreachable: every startup short-circuited to the "direct-boot, skip
opencode" branch.
Same logic for `banger vm acp`: it SSHes to `opencode acp --cwd
<path>`, a binary the golden image no longer ships. Users who run
their own image with opencode can still invoke
`ssh vm -- opencode acp --cwd /root/repo` directly — no banger
scaffolding required.
Removed:
- internal/opencode/ (whole package, 255 LOC incl. tests)
- internal/daemon/opencode.go (opencodeCapability)
- cli `vm acp` command + its helpers (runVMACP, sshACPCommandArgs,
vmACPRemoteCommand) + their tests
- The opencodeCapability{} entry in registeredCapabilities() plus
the test that pinned its presence
- `wait_opencode` progress-stage label from the vm-create renderer
- Stale mentions in daemon/doc.go, README, and webui test fixtures
~480 lines gone, 12 added. `banger/internal` is now 25 packages
instead of 26.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
207 lines
6.1 KiB
Markdown
207 lines
6.1 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
|
|
```
|
|
|
|
## 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`.
|
|
|
|
## File sync
|
|
|
|
Host → guest file/directory copies, declared per-user in
|
|
`~/.config/banger/config.toml`:
|
|
|
|
```toml
|
|
[[file_sync]]
|
|
host = "~/.aws" # whole directory, recursive
|
|
guest = "~/.aws"
|
|
|
|
[[file_sync]]
|
|
host = "~/.config/gh/hosts.yml"
|
|
guest = "~/.config/gh/hosts.yml"
|
|
|
|
[[file_sync]]
|
|
host = "~/bin/my-script"
|
|
guest = "~/bin/my-script"
|
|
mode = "0755" # optional; defaults to 0600 for files
|
|
```
|
|
|
|
Runs at `vm create` time. Each entry copies `host` → `guest` onto
|
|
the VM's work disk (mounted at `/root` in the guest). Guest paths
|
|
must live under `~/` or `/root/...`. Host-side changes take effect
|
|
after the next `vm create`. Missing host paths are a soft skip with
|
|
a warning in the daemon log.
|
|
|
|
Default is no entries — add the ones you want.
|
|
|
|
## 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.
|