diff --git a/README.md b/README.md index edd0b84..db83879 100644 --- a/README.md +++ b/README.md @@ -9,12 +9,11 @@ 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. +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 @@ -29,131 +28,78 @@ subsequent `vm run`s are seconds. make install ``` -Installs: - -- `banger` (CLI) -- `bangerd` (daemon, auto-starts on first CLI call) -- `banger-vsock-agent` (companion, under `$PREFIX/lib/banger/`) +Installs `banger` (CLI), `bangerd` (daemon, auto-starts on first +CLI call), and `banger-vsock-agent` (companion, under +`$PREFIX/lib/banger/`). ## `vm run` -One command, three modes: +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, 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 ``` -- Bare mode gives you a clean shell. -- Workspace mode (with a path) copies the repo's tracked + untracked +- **Bare mode** gives you a clean shell. +- **Workspace mode** (path given) 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` / + `mise` tooling bootstrap from the repo's `.mise.toml` / `.tool-versions`. Log: `/root/.cache/banger/vm-run-tooling-.log`. -- Command mode (`-- `) runs the command in the guest; exit code - propagates through `banger`. +- **Command mode** (`-- `) 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. +`--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 -wedged sshd leaves the VM alive for `banger vm logs` inspection. +## Hostnames: reaching `.vm` + +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 -`banger image pull ` resolves `` 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. +`banger image pull ` fetches a pre-built bundle from the +embedded catalog. `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` | +| Name | What it is | +|------|-----------| +| `debian-bookworm` | Debian 12 slim + sshd + docker + dev tools | -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 ` — 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 ./other-repo --guest-path /root/repo -banger vm session start --name planner --cwd /root/repo --stdin-mode pipe -- pi --mode rpc -banger vm session attach planner -banger vm session logs planner --stream stderr -banger vm session stop planner -``` +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. -Commonly set: +Most commonly set: -- `default_image_name` — image to use when `--image` is omitted - (defaults to `debian-bookworm`, auto-pulled from the catalog if not +- `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 +- `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`. +- `web_listen_addr` — experimental web UI (default `127.0.0.1:7777`; + set to `""` to disable). Full key list in `internal/config/config.go`. -## File sync - -Host → guest file/directory copies, declared per-user in -`~/.config/banger/config.toml`: +### `file_sync` — host → guest file copies ```toml [[file_sync]] @@ -167,22 +113,19 @@ guest = "~/.config/gh/hosts.yml" [[file_sync]] host = "~/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 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. +must live under `~/` or `/root/...`. Default is no entries — add the +ones you want. -Default is no entries — add the ones you want. +## Advanced -## 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. +The common path is `vm run`. Power-user flows (`vm create`, OCI pull +for arbitrary images, `image register`, long-lived sessions) are +documented in [`docs/advanced.md`](docs/advanced.md). ## 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 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. -- 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. +## Further reading + +- [`docs/dns-routing.md`](docs/dns-routing.md) — resolving + `.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. diff --git a/docs/advanced.md b/docs/advanced.md new file mode 100644 index 0000000..d416b77 --- /dev/null +++ b/docs/advanced.md @@ -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 ` — 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 ./other-repo --guest-path /root/repo +banger vm session start --name planner --cwd /root/repo \ + --stdin-mode pipe -- pi --mode rpc +banger vm session attach planner +banger vm session logs planner --stream stderr +banger vm session stop 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 ` — the firecracker serial console output, + the best window into a stuck boot (systemd unit failures, kernel + panics, missing modules). +- `banger vm ports ` — 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 ` — 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. diff --git a/docs/dns-routing.md b/docs/dns-routing.md new file mode 100644 index 0000000..45f8d09 --- /dev/null +++ b/docs/dns-routing.md @@ -0,0 +1,138 @@ +# DNS routing — resolving `.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 127.0.0.1:42069 +sudo resolvectl domain ~vm +sudo resolvectl default-route 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. |