Bug: syncVMSSHClientConfig did os.RemoveAll on $ConfigDir/ssh every daemon Open. The intent was to migrate off the pre-opt-in layout, where banger used to write $ConfigDir/ssh/ssh_config. But a user who sets ssh_key_path = "~/.config/banger/ssh/id_ed25519" in config.toml has their key live exactly in that dir — and the scrub deletes it along with every other file in the tree. This is the same class of bug that cost the default key untilebe6517moved it to StateDir, but that fix was scoped to the default path. A configured ssh_key_path pointed under the legacy dir still dies. Fix: replace os.RemoveAll with a narrow two-step cleanup: 1. Skip the cleanup entirely when the configured ssh_key_path resolves under the legacy dir. A user who pointed banger at a key there must keep the enclosing directory. 2. Otherwise, os.Remove the specific legacy file ($ConfigDir/ssh/ ssh_config) and then os.Remove the directory. The second os.Remove fails with ENOTEMPTY if the dir still holds anything (e.g. a user-managed sibling file we don't own). Both errors are swallowed — this is best-effort migration, not a hard failure. Tests pin all three paths: user key under legacy dir survives, legacy dir empties and is removed when the user moved on, and a user-managed sibling file in the legacy dir is preserved. Also fix stale doc claims in README.md and AGENTS.md — both still pointed at the old ~/.config/banger/ssh/id_ed25519 default, which moved to ~/.local/state/banger/ssh/id_ed25519 inebe6517. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
241 lines
7.6 KiB
Markdown
241 lines
7.6 KiB
Markdown
# banger
|
|
|
|
One-command development sandboxes on Firecracker microVMs.
|
|
|
|
## Quick start
|
|
|
|
```bash
|
|
make install
|
|
banger vm run --name sandbox
|
|
```
|
|
|
|
That's it. `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, 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
|
|
|
|
- **x86_64 / amd64 Linux** — arm64 is not supported today. The companion
|
|
binaries, the published kernel catalog, and the OCI import path all
|
|
assume `linux/amd64`. `banger doctor` surfaces this as a failing
|
|
check on other architectures.
|
|
- `/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), and `banger-vsock-agent` (companion, under
|
|
`$PREFIX/lib/banger/`).
|
|
|
|
To remove the binaries (and stop the daemon):
|
|
|
|
```bash
|
|
make uninstall
|
|
```
|
|
|
|
User data stays in place — the target prints the paths so you can
|
|
`rm -rf` them if you want a full purge:
|
|
|
|
- `~/.config/banger/` — config, managed SSH keys
|
|
- `~/.local/state/banger/` — VM records, rootfs images, kernels, daemon DB/log
|
|
- `~/.cache/banger/` — OCI layer cache
|
|
|
|
### Shell completion
|
|
|
|
`banger` ships completion scripts for bash, zsh, fish, and
|
|
powershell. Tab-completion covers subcommands, flags, and live
|
|
resource names (VM, image, kernel) looked up from the
|
|
daemon. With the daemon down, resource completion silently
|
|
returns nothing — no file-completion fallback.
|
|
|
|
```bash
|
|
# bash (system-wide)
|
|
banger completion bash | sudo tee /etc/bash_completion.d/banger
|
|
|
|
# zsh (user-local; ~/.zfunc must be on fpath)
|
|
banger completion zsh > ~/.zfunc/_banger
|
|
|
|
# fish
|
|
banger completion fish > ~/.config/fish/completions/banger.fish
|
|
```
|
|
|
|
`banger completion --help` shows the shell-specific loading
|
|
recipes.
|
|
|
|
## `vm run`
|
|
|
|
One command, four common shapes:
|
|
|
|
```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, exits with its status
|
|
banger vm run --rm -- script.sh # ephemeral: VM is deleted on exit
|
|
```
|
|
|
|
- **Bare mode** gives you a clean shell.
|
|
- **Workspace mode** (path given) copies the repo's git-tracked 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`. Untracked files
|
|
(including local `.env`, scratch notes, credentials that aren't
|
|
gitignored) are skipped by default — pass `--include-untracked` to
|
|
also ship them. Pass `--dry-run` to print the exact file list and
|
|
exit without creating a VM.
|
|
- **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`, `--from`, `--include-untracked`, and `--dry-run` apply
|
|
only to workspace mode. `--rm` skips the delete when the initial ssh
|
|
wait times out, so a wedged sshd leaves the VM alive for `banger vm
|
|
logs` inspection.
|
|
|
|
## Hostnames: reaching `<vm>.vm`
|
|
|
|
banger's daemon runs a DNS server for the `.vm` zone. With host-side
|
|
DNS routing you can `curl http://sandbox.vm:3000` from anywhere on
|
|
the host — no copy-pasting guest IPs. On systemd-resolved hosts this
|
|
is auto-wired; everywhere else there's a short recipe. See
|
|
[`docs/dns-routing.md`](docs/dns-routing.md).
|
|
|
|
### Optional: `ssh <name>.vm` shortcut
|
|
|
|
`banger vm ssh <name>` works out of the box. If you'd also like plain
|
|
`ssh sandbox.vm` from any terminal (using banger's key + known_hosts),
|
|
opt in:
|
|
|
|
```bash
|
|
banger ssh-config --install # adds `Include ~/.config/banger/ssh_config`
|
|
# to ~/.ssh/config in a marker-fenced block
|
|
banger ssh-config --uninstall # reverse it
|
|
banger ssh-config # show the include line to paste manually
|
|
```
|
|
|
|
banger never touches `~/.ssh/config` on its own — the daemon keeps its
|
|
file fresh at `~/.config/banger/ssh_config`; whether and how it's
|
|
pulled into your SSH config is up to you.
|
|
|
|
## Image catalog
|
|
|
|
`banger image pull <name>` fetches a pre-built bundle from the
|
|
embedded catalog. `vm run` calls this for you on demand.
|
|
|
|
Today's catalog:
|
|
|
|
| Name | What it is |
|
|
|------|-----------|
|
|
| `debian-bookworm` | Debian 12 slim + sshd + docker + dev tools |
|
|
|
|
See [`docs/image-catalog.md`](docs/image-catalog.md) for the bundle
|
|
format and how to publish a new entry.
|
|
|
|
## Config
|
|
|
|
Config lives at `~/.config/banger/config.toml`. All keys optional.
|
|
|
|
Most commonly set:
|
|
|
|
- `default_image_name` — image used when `--image` is omitted
|
|
(default `debian-bookworm`, auto-pulled from the catalog if not
|
|
local).
|
|
- `ssh_key_path` — host SSH key. If unset, banger creates
|
|
`~/.local/state/banger/ssh/id_ed25519`.
|
|
- `firecracker_bin` — override the auto-resolved `PATH` lookup.
|
|
|
|
Full key list in `internal/config/config.go`.
|
|
|
|
### `vm_defaults` — sizing for new VMs
|
|
|
|
Every `vm run` / `vm create` prints a `spec:` line up front showing
|
|
the vCPU, RAM, and disk the VM will get. When the flags aren't set,
|
|
those values come from:
|
|
|
|
1. `[vm_defaults]` in config (if present, wins).
|
|
2. Host-derived heuristics (roughly: `cpus/4` capped at 4, `ram/8`
|
|
capped at 8 GiB, 8 GiB disk).
|
|
3. Built-in constants (floor).
|
|
|
|
`banger doctor` prints the effective defaults with provenance.
|
|
|
|
```toml
|
|
[vm_defaults]
|
|
vcpu = 4
|
|
memory_mib = 4096
|
|
disk_size = "16G"
|
|
```
|
|
|
|
All keys optional — omit whichever you want banger to decide.
|
|
|
|
### `file_sync` — host → guest file copies
|
|
|
|
```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; default 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/...`. Default is no entries — add the
|
|
ones you want.
|
|
|
|
## Advanced
|
|
|
|
The common path is `vm run`. Power-user flows (`vm create`, OCI pull
|
|
for arbitrary images, `image register`, manual workspace prepare) are
|
|
documented in [`docs/advanced.md`](docs/advanced.md).
|
|
|
|
## Security
|
|
|
|
Guest VMs are single-user development sandboxes, not multi-tenant
|
|
servers. Each guest's sshd is configured with:
|
|
|
|
```
|
|
PermitRootLogin prohibit-password
|
|
PubkeyAuthentication yes
|
|
PasswordAuthentication no
|
|
KbdInteractiveAuthentication no
|
|
AuthorizedKeysFile /root/.ssh/authorized_keys
|
|
```
|
|
|
|
The host SSH key is the only authentication mechanism. `StrictModes`
|
|
is on (sshd's default); banger normalises `/root`, `/root/.ssh`, and
|
|
`authorized_keys` perms at provisioning time so the default passes.
|
|
|
|
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.
|
|
|
|
## Further reading
|
|
|
|
- [`docs/dns-routing.md`](docs/dns-routing.md) — resolving
|
|
`<vm>.vm` hostnames from the host.
|
|
- [`docs/image-catalog.md`](docs/image-catalog.md) — bundle format
|
|
and publishing.
|
|
- [`docs/kernel-catalog.md`](docs/kernel-catalog.md) — kernel
|
|
bundles.
|
|
- [`docs/oci-import.md`](docs/oci-import.md) — pulling arbitrary
|
|
OCI images.
|
|
- [`docs/advanced.md`](docs/advanced.md) — power-user flows.
|