docs: DNS routing guide; README aimed at common users

Adds docs/dns-routing.md covering how `<vm>.vm` resolution works:
auto-configuration on systemd-resolved hosts (what the daemon
already does), and per-resolver recipes for dnsmasq /
NetworkManager+dnsmasq / /etc/resolv.conf / macOS `/etc/resolver/`
/ WSL. Plus verification via `dig @127.0.0.1 -p 42069` and
troubleshooting for the common failure modes.

README reshape: lead with the three things a common user needs —
quick start, what `vm run` does, where to put hostnames + image +
config — and push the rest to docs. `vm create` / OCI `image pull`
/ `image register` / workspace-and-session primitives are all still
documented, just under docs/advanced.md where they're not in the
first-time reader's way. Web UI and unnecessary implementation
notes dropped; the "further reading" section at the bottom
enumerates the five docs pages so nothing becomes hard to find.

README shrinks from 208 → 158 lines.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Thales Maciel 2026-04-18 17:24:50 -03:00
parent 2584f94828
commit 88425fb857
No known key found for this signature in database
GPG key ID: 33112E6833C34679
3 changed files with 296 additions and 109 deletions

169
README.md
View file

@ -9,12 +9,11 @@ make install
banger vm run --name sandbox banger vm run --name sandbox
``` ```
`banger vm run` auto-pulls the default golden image (Debian bookworm That's it. `banger vm run` auto-pulls the default golden image (Debian
with systemd, sshd, Docker CE, git, jq, mise, and the usual dev tools) bookworm with systemd, sshd, Docker CE, git, jq, mise, and the usual
and kernel from the embedded catalog if they aren't already local, dev tools) and kernel, creates a VM, starts it, and drops you into
creates a VM, starts it, and drops you into an interactive ssh an interactive ssh session. First run takes a couple minutes (bundle
session. First run takes a couple minutes (bundle download); download); subsequent `vm run`s are seconds.
subsequent `vm run`s are seconds.
## Requirements ## Requirements
@ -29,131 +28,78 @@ subsequent `vm run`s are seconds.
make install make install
``` ```
Installs: Installs `banger` (CLI), `bangerd` (daemon, auto-starts on first
CLI call), and `banger-vsock-agent` (companion, under
- `banger` (CLI) `$PREFIX/lib/banger/`).
- `bangerd` (daemon, auto-starts on first CLI call)
- `banger-vsock-agent` (companion, under `$PREFIX/lib/banger/`)
## `vm run` ## `vm run`
One command, three modes: One command, four common shapes:
```bash ```bash
banger vm run # bare sandbox — drops into ssh banger vm run # bare sandbox — drops into ssh
banger vm run ./repo # workspace at /root/repo — 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 ./repo -- make test # workspace + run command, exits with its status
banger vm run --rm -- script.sh # ephemeral: VM is deleted on exit banger vm run --rm -- script.sh # ephemeral: VM is deleted on exit
``` ```
- Bare mode gives you a clean shell. - **Bare mode** gives you a clean shell.
- Workspace mode (with a path) copies the repo's tracked + untracked - **Workspace mode** (path given) copies the repo's tracked + untracked
non-ignored files into `/root/repo` and kicks off a best-effort non-ignored files into `/root/repo` and kicks off a best-effort
mise tooling bootstrap from the repo's `.mise.toml` / `mise` tooling bootstrap from the repo's `.mise.toml` /
`.tool-versions`. Log: `/root/.cache/banger/vm-run-tooling-<repo>.log`. `.tool-versions`. Log: `/root/.cache/banger/vm-run-tooling-<repo>.log`.
- Command mode (`-- <cmd>`) runs the command in the guest; exit code - **Command mode** (`-- <cmd>`) runs the command in the guest; exit
propagates through `banger`. code propagates through `banger`.
Disconnecting from an interactive session leaves the VM running. Use Disconnecting from an interactive session leaves the VM running. Use
`vm stop` / `vm delete` to clean up — or pass `--rm` so the VM `vm stop` / `vm delete` to clean up — or pass `--rm` so the VM
auto-deletes once the session / command exits. auto-deletes once the session / command exits.
`--branch` and `--from` apply only to workspace mode. `--branch` and `--from` 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.
`--rm` delete is skipped when the initial ssh wait times out, so a ## Hostnames: reaching `<vm>.vm`
wedged sshd leaves the VM alive for `banger vm logs` inspection.
banger's daemon runs a DNS server for the `.vm` zone. With host-side
DNS routing you can `ssh root@sandbox.vm` or `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).
## Image catalog ## Image catalog
`banger image pull <name>` resolves `<name>` in the embedded catalog `banger image pull <name>` fetches a pre-built bundle from the
and fetches a pre-built bundle (rootfs.ext4 + manifest, tar+zstd). The embedded catalog. `vm run` calls this for you on demand.
kernel referenced by the manifest auto-pulls too. `vm run` calls this
for you on demand.
Today's catalog: Today's catalog:
| Name | Distro | Kernel | | Name | What it is |
|------|--------|--------| |------|-----------|
| `debian-bookworm` | Debian 12 slim + sshd + docker + dev tools | `generic-6.12` | | `debian-bookworm` | Debian 12 slim + sshd + docker + dev tools |
The catalog ships embedded in the banger binary. See See [`docs/image-catalog.md`](docs/image-catalog.md) for the bundle
[`docs/image-catalog.md`](docs/image-catalog.md) for maintenance. format and how to publish a new entry.
## 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
Config lives at `~/.config/banger/config.toml`. All keys optional. Config lives at `~/.config/banger/config.toml`. All keys optional.
Commonly set: Most commonly set:
- `default_image_name` — image to use when `--image` is omitted - `default_image_name` — image used when `--image` is omitted
(defaults to `debian-bookworm`, auto-pulled from the catalog if not (default `debian-bookworm`, auto-pulled from the catalog if not
local). local).
- `ssh_key_path` — host SSH key; if unset banger creates - `ssh_key_path` — host SSH key. If unset, banger creates
`~/.config/banger/ssh/id_ed25519`. `~/.config/banger/ssh/id_ed25519`.
- `firecracker_bin` — override the auto-resolved `PATH` lookup. - `firecracker_bin` — override the auto-resolved `PATH` lookup.
- `web_listen_addr` — experimental web UI (default - `web_listen_addr` — experimental web UI (default `127.0.0.1:7777`;
`127.0.0.1:7777`; set to `""` to disable). set to `""` to disable).
- Network: `bridge_name`, `bridge_ip`, `cidr`, `tap_pool_size`,
`default_dns`.
Full key list in `internal/config/config.go`. Full key list in `internal/config/config.go`.
## File sync ### `file_sync` — host → guest file copies
Host → guest file/directory copies, declared per-user in
`~/.config/banger/config.toml`:
```toml ```toml
[[file_sync]] [[file_sync]]
@ -167,22 +113,19 @@ guest = "~/.config/gh/hosts.yml"
[[file_sync]] [[file_sync]]
host = "~/bin/my-script" host = "~/bin/my-script"
guest = "~/bin/my-script" guest = "~/bin/my-script"
mode = "0755" # optional; defaults to 0600 for files mode = "0755" # optional; default 0600 for files
``` ```
Runs at `vm create` time. Each entry copies `host``guest` onto Runs at `vm create` time. Each entry copies `host``guest` onto
the VM's work disk (mounted at `/root` in the guest). Guest paths the VM's work disk (mounted at `/root` in the guest). Guest paths
must live under `~/` or `/root/...`. Host-side changes take effect must live under `~/` or `/root/...`. Default is no entries — add the
after the next `vm create`. Missing host paths are a soft skip with ones you want.
a warning in the daemon log.
Default is no entries — add the ones you want. ## Advanced
## Web UI (experimental) The common path is `vm run`. Power-user flows (`vm create`, OCI pull
for arbitrary images, `image register`, long-lived sessions) are
`bangerd` serves a local web UI at `http://127.0.0.1:7777` by default. documented in [`docs/advanced.md`](docs/advanced.md).
Convenient for local observability, **not a stable interface**. Do
not expose the listen address to a shared network.
## Security ## Security
@ -199,9 +142,17 @@ 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 network (`172.16.0.0/24` by default). Do not expose the bridge
interface or guest IPs to an untrusted network. interface or guest IPs to an untrusted network.
## Notes The web UI (when enabled) binds `127.0.0.1` by default. Do not
expose it to a shared network.
- Managed image delete removes the daemon-owned artifact dir. ## Further reading
- Layer blob cache for OCI pulls lives under `~/.cache/banger/oci/`.
- Image bundle cache doesn't exist — bundles are extracted directly - [`docs/dns-routing.md`](docs/dns-routing.md) — resolving
into the image store; re-pulls download fresh. `<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.

98
docs/advanced.md Normal file
View file

@ -0,0 +1,98 @@
# Advanced flows
`banger vm run` covers the common sandbox case. This doc is for the
rest: scripting, arbitrary images, custom rootfs stacks, long-lived
guest processes.
## `vm create` — the low-level primitive
Use when you want to provision without starting, or when you need to
script VM creation piecewise.
```bash
banger vm create --image debian-bookworm --name testbox --no-start
banger vm start testbox
banger vm ssh testbox
banger vm stop testbox
banger vm delete testbox
```
`vm create` is synchronous by default, but on a TTY it shows live
progress until the VM is fully ready.
## `image pull <oci-ref>` — arbitrary container images
For images outside banger's 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 (setuid binaries, root-owned
config preserved), banger's guest agents are injected, and a first-boot
systemd service installs `openssh-server` via the guest's package
manager so the VM is reachable on first boot.
See [`docs/oci-import.md`](oci-import.md) for supported distros,
caveats, and the `internal/imagepull` design.
## `image register` — existing host-side stack
If you already have an ext4 rootfs, a kernel, optional initrd, and
optional modules as files on disk:
```bash
banger image register --name base \
--rootfs /abs/path/rootfs.ext4 \
--kernel-ref generic-6.12
```
You can mix `--kernel-ref` (a cataloged kernel) with `--rootfs` from
disk, or pass `--kernel /abs/path/vmlinux` for a one-off kernel.
For reproducible custom images, write a Dockerfile and publish it to
an image catalog. See [`docs/image-catalog.md`](image-catalog.md).
## Workspace + session primitives
Long-lived guest commands managed by the daemon, attachable over a
local Unix socket bridge. Useful for agent/background processes that
need to survive SSH disconnects.
```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
```
Details:
- `vm workspace prepare` materialises a local git checkout into a
running VM. Default guest path `/root/repo`; default mode is a
shallow metadata copy plus tracked and untracked non-ignored
overlay.
- `vm session start` launches a daemon-managed long-lived guest
command. The daemon preflights that the guest `cwd` exists and the
command is on guest `PATH` before launch. Use `--stdin-mode pipe`
when you need live `attach`.
- `vm session attach` is exclusive and same-host only. Pipe-mode
sessions survive daemon restarts.
## Inspecting boot failures
When a VM's create flow errors ("ssh did not come up within 90s" or
similar), the VM is kept alive for inspection:
- `banger vm logs <name>` — the firecracker serial console output,
the best window into a stuck boot (systemd unit failures, kernel
panics, missing modules).
- `banger vm ports <name>` — what's listening in the guest. Works as
long as banger's vsock agent has come up, even if SSH is wedged.
- `banger vm show <name>` — daemon-side state (IP, PID, overlay
paths).
`--rm` on `vm run` intentionally does NOT fire when the initial ssh
wait times out, so the VM stays around for post-mortem.

138
docs/dns-routing.md Normal file
View file

@ -0,0 +1,138 @@
# DNS routing — resolving `<vm>.vm` hostnames from the host
banger's daemon runs a local DNS server on `127.0.0.1:42069` that
answers queries under the `.vm` zone. Every VM you create gets a
record:
```
devbox.vm → 172.16.0.9 (whatever guest IP it was assigned)
```
With that plus host-side DNS routing, you can:
```bash
ssh root@devbox.vm
curl http://devbox.vm:3000
```
from anywhere on the host without copy-pasting guest IPs.
## systemd-resolved hosts — nothing to configure
If your host uses `systemd-resolved` (most modern Linux desktops —
Ubuntu ≥18.04, Fedora, Arch with the service enabled), banger
auto-wires it. On daemon start it runs:
```
sudo resolvectl dns <bridge> 127.0.0.1:42069
sudo resolvectl domain <bridge> ~vm
sudo resolvectl default-route <bridge> no
```
against the banger bridge (`br-fc` by default). systemd-resolved
routes only `.vm` lookups to banger's DNS; everything else goes to
your normal upstream. No other changes needed.
Verify: `resolvectl status br-fc` should list `127.0.0.1:42069` under
**Current DNS Server** and `~vm` under **DNS Domain**.
`banger daemon stop` reverts the bridge's resolvectl state on shutdown.
## Non-systemd-resolved hosts
banger detects `resolvectl`'s absence and skips the auto-wire. You
configure your own resolver. Below are recipes for the common cases.
In every case the goal is the same: **route `.vm` queries to
`127.0.0.1` port `42069`, leave everything else alone**.
### dnsmasq
Add a stanza to your dnsmasq config (e.g.
`/etc/dnsmasq.d/banger-vm.conf`):
```
server=/vm/127.0.0.1#42069
```
Reload dnsmasq (`sudo systemctl reload dnsmasq` or equivalent) and
test:
```
dig devbox.vm
```
### NetworkManager with dnsmasq plugin
Same file as above; NetworkManager picks it up automatically if it's
configured to use the dnsmasq plugin (`dns=dnsmasq` in
`/etc/NetworkManager/NetworkManager.conf`). Restart NetworkManager
after editing.
### Raw `/etc/resolv.conf`
If you edit `resolv.conf` directly, there's no per-domain routing —
you'd have to point ALL DNS through banger, which you probably don't
want. Install `dnsmasq` instead and use the stanza above.
### macOS (if you ever run banger on a Linux VM hosted on macOS)
macOS supports per-TLD resolvers out of the box. Create
`/etc/resolver/vm` (as root):
```
nameserver 127.0.0.1
port 42069
```
No daemon reload needed — `scutil --dns` should list `.vm` under
"Resolver configurations" immediately.
### Windows/WSL
WSL2 inherits the Windows resolver by default and cannot be told to
route `.vm` anywhere. Options:
1. Run banger inside WSL but resolve manually: `ssh root@172.16.0.9`.
2. Set up `dnsmasq` on the WSL distro and point its resolv.conf at
it; then follow the dnsmasq recipe above.
## Verifying the DNS server
Regardless of host-side routing, you can always query banger's DNS
server directly:
```bash
dig @127.0.0.1 -p 42069 devbox.vm
```
Returns the guest IP if the VM is running. If it returns NXDOMAIN,
the VM either doesn't exist under that name or isn't running yet.
`banger vm list` shows the VM names banger knows about.
## Troubleshooting
- **`resolvectl` errors about "system has not been booted with systemd
as init system"** — you're probably inside a container. banger's
DNS still works; set up your resolver manually.
- **Port 42069 already in use** — another daemon is bound there
(previous banger instance not shut down cleanly, or an unrelated
app). `ss -ulpn | grep 42069` shows who. `banger daemon stop`
cleans up banger's own listener.
- **`devbox.vm` resolves but SSH hangs** — DNS is fine; the VM
might not be up yet or the bridge NAT is misconfigured.
`banger vm ssh devbox` uses the guest IP directly and bypasses
DNS — try that to isolate.
- **Changes to `default_dns` don't affect `.vm` resolution**
`default_dns` is the upstream the GUEST uses; it's unrelated to
host-side `.vm` routing.
## Port and bridge tuning
| Setting | Default | Notes |
|---|---|---|
| DNS listen addr | `127.0.0.1:42069` | Not configurable in v1. Edit `internal/vmdns/server.go` if you really need to change it. |
| Bridge name | `br-fc` | Configurable via `bridge_name` in `~/.config/banger/config.toml`. |
| Bridge IP | `172.16.0.1` | Configurable via `bridge_ip`. |
| Resolver route domain | `~vm` | Not configurable. |