diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..19db85a --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,61 @@ +# Contributing + +## Build from source + +```bash +make build +sudo ./build/bin/banger system install --owner "$USER" +``` + +`make build` produces three binaries under `./build/bin/`: + +- `banger` — the user-facing CLI +- `bangerd` — the owner-user daemon (exposes `/run/banger/bangerd.sock`) +- `banger-vsock-agent` — the in-guest companion + +`system install` copies them into `/usr/local`, writes install +metadata under `/etc/banger`, lays down `bangerd.service` and +`bangerd-root.service`, and starts both. After that, daily commands +like `banger vm run` are unprivileged. + +To inspect or refresh the services: + +```bash +banger system status +sudo banger system restart +``` + +The two-service split (owner daemon + privileged root helper) is +explained in [`docs/privileges.md`](docs/privileges.md), including +the exact capability set the root helper holds. + +## Tests + +```bash +make test # go test ./... +make coverage # per-package + total statement coverage +make lint # gofmt + go vet + shellcheck +``` + +The smoke suite (`make smoke`) builds coverage-instrumented binaries, +installs them as a temporary systemd service, and runs end-to-end +scenarios against real Firecracker. Requires a KVM-capable host and +`sudo`. `make smoke-list` prints scenario names; `make smoke-one +SCENARIO=` runs just one. See the smoke comments in the +`Makefile` for details. + +## Pre-commit hook + +```bash +make install-hooks +``` + +Points `core.hooksPath` at `.githooks/`, which runs lint + test + +build on every commit. Bypass with `git commit --no-verify`; revert +with `git config --unset core.hooksPath`. + +## Internals + +- [`docs/privileges.md`](docs/privileges.md) — daemon split, capability set, trust model. +- [`docs/release-process.md`](docs/release-process.md) — cutting and signing a release. +- [`AGENTS.md`](AGENTS.md) — repo-wide notes for code agents. diff --git a/README.md b/README.md index 7790b3c..a8b0c5d 100644 --- a/README.md +++ b/README.md @@ -2,347 +2,165 @@ One-command development sandboxes on Firecracker microVMs. -**Requirements:** Linux + KVM (`/dev/kvm`), `firecracker` on PATH (or `firecracker_bin` in config). banger is tested against [Firecracker v1.14.1](https://github.com/firecracker-microvm/firecracker/releases/tag/v1.14.1) and supports any Firecracker ≥ v1.5.0. `banger doctor` warns when the installed version sits outside the tested range, and prints a distro-aware install hint when it's missing. +Spin up a clean Linux VM with your repo and tooling preloaded, drop +into ssh, and tear it down — all from one command. banger is built +for the dev loop, not the server use case: guests are short-lived, +single-user, reachable at `.vm` from your host, and disposable. ## Quick start +**Requirements**: +- Linux x86_64 with KVM +- Systemd +- [Firecracker >= v1.5](https://github.com/firecracker-microvm/firecracker) + +Install: + ```bash curl -fsSL https://releases.thaloco.com/banger/install.sh | bash -banger vm run --name sandbox ``` -The installer runs as you, downloads + verifies the latest signed -release, then prompts before re-execing `sudo` for the system-install -step (writing `/usr/local/bin` + creating systemd units). If you'd -rather audit the script first: +The installer downloads the signed release, then prompts for sudo for install. +[Read more about how banger uses sudo](#Security) + +Verify host configuration: +```bash +banger doctor +``` + +First VM: +>The first run may take a couple minutes for the bundle download. +>Subsequent `vm run`s are expected to take from 1 to 3 seconds. ```bash -curl -fsSL https://releases.thaloco.com/banger/install.sh -o install.sh -less install.sh -bash install.sh +banger vm run --name my-vm ``` -Or build from source: - -```bash -make build -sudo ./build/bin/banger system install --owner "$USER" -banger vm run --name sandbox -``` - -That's it. `banger vm run` auto-pulls the default golden image (a pre-built -Debian rootfs with sshd, mise, and the usual dev tools: Debian bookworm with -systemd, sshd, Docker CE, git, jq, and mise) 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. - -## Supported host path - -banger's supported host/runtime path is: - -- Linux on `x86_64 / amd64` -- `systemd` as the host init/service manager -- `bangerd.service` running as the installed owner user -- `bangerd-root.service` running as the privileged host helper - -Other setups may work with manual adaptation, but they are not the -supported operating model for this repo. - -## 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. -- **systemd on the host** — this is the supported service-management - path. banger's supported install/run model is the owner-user - `bangerd.service` plus the privileged `bangerd-root.service` - installed by `banger system install`. -- `/dev/kvm` -- `sudo` for the install/admin commands (`system install`, - `system restart`, `system uninstall`) -- Firecracker on `PATH`, or `firecracker_bin` set in config -- host tools checked by `banger doctor` - -## Build + install - -```bash -make build -sudo ./build/bin/banger system install --owner "$USER" -``` - -This installs two systemd units, copies the current `banger`, -`bangerd`, and `banger-vsock-agent` binaries into `/usr/local`, writes -install metadata under `/etc/banger`, and starts both services: - -- `bangerd.service` runs as the configured owner user and exposes the - public CLI socket at `/run/banger/bangerd.sock`. -- `bangerd-root.service` runs as root and handles the narrow set of - privileged host operations over the private helper socket at - `/run/banger-root/bangerd-root.sock`. - -After that, normal daily commands such as `banger vm run` and -`banger image pull` are unprivileged. - -This `systemd` service flow is the supported path. If you're not on a -host that can run both services, you're outside the supported host -model even if some pieces happen to work. - -The split matters: - -- `bangerd.service` runs as the owner user, keeps its writable state in - `/var/lib/banger`, `/var/cache/banger`, and `/run/banger`, and sees - the owner home read-only. -- `bangerd-root.service` is the only process that keeps elevated host - capabilities, and that capability set is limited to the host-kernel - primitives banger actually uses (`CAP_CHOWN`, `CAP_DAC_OVERRIDE`, - `CAP_FOWNER`, `CAP_KILL`, `CAP_MKNOD`, `CAP_NET_ADMIN`, `CAP_NET_RAW`, - `CAP_SETGID`, `CAP_SETUID`, `CAP_SYS_ADMIN`, `CAP_SYS_CHROOT`). - -To inspect or refresh the services: - -```bash -banger system status -sudo banger system restart -``` - -To remove the system services: - -```bash -sudo banger system uninstall -``` - -Add `--purge` if you also want to remove system-owned VM/image/cache -state under `/var/lib/banger`, `/var/cache/banger`, `/run/banger`, and -`/run/banger-root`. User config stays in place under your home -directory: - -- `~/.config/banger/` — config, optional `ssh_config` -- `~/.local/state/banger/ssh/` — user SSH key + known_hosts - -### 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 installed -services. With the services 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. +This auto-pulls the default image and drops you into an interactive ssh session. +Disconnecting an interactive session leaves the VM running, +`--rm` auto-deletes the VM when the session or command exits. ## `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 ./my-repo # copy /my-repo into /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-.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** (`-- `) runs the command in the guest; exit - code propagates through `banger`. +If a repository is passed, banger copies your repo's git-tracked files +into `/root/repo` and runs a best-effort `mise` bootstrap from +`.mise.toml` / `.tool-versions`. Untracked files are skipped by +default — pass `--include-untracked` to ship them too, or +`--dry-run` to preview the file list. -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. +In **command mode** (`-- `), the exit code propagates through +`banger`. -`--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. +### Other VM verbs -## Hostnames: reaching `.vm` +The CLI tries to feel familiar — every command and subcommand has +`--help`. Beyond `vm run`: `vm list` shows running VMs (`--all` for +every state), `vm ssh ` reconnects to one, `vm exec -- +` runs a command without a shell, `vm stop` / `vm kill` shut a +VM down (graceful / hard), `vm delete` removes a stopped one, and +`vm prune` sweeps every non-running VM. -banger's owner 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 the owner daemon asks the root helper to -auto-wire this and that is the supported path. Everywhere else -there's a best-effort manual recipe. See -[`docs/dns-routing.md`](docs/dns-routing.md). +### `--nat`: outbound internet -### Optional: `ssh .vm` shortcut - -`banger vm ssh ` 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: +By default, a guest can't reach the internet. +Pass `--nat` to enable it (host-side MASQUERADE): ```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 vm run --nat ./repo -- npm install ``` -banger never touches `~/.ssh/config` on its own — the daemon keeps its -own known_hosts under `/var/lib/banger/ssh/known_hosts`, while -`banger ssh-config` keeps the user-facing file fresh at -`~/.config/banger/ssh_config`; whether and how it's -pulled into your SSH config is up to you. +`--nat` works on `vm run` and `vm create`. To toggle on an existing +VM: `banger vm set --nat ` (or `--no-nat` to remove it). -## Image catalog +## Hostnames: `.vm` -`banger image pull ` fetches a pre-built bundle from the -embedded catalog. `vm run` calls this for you on demand. +banger's daemon runs a DNS server for the `.vm` zone. With host-side +DNS routing, `curl http://sandbox.vm:3000` works from anywhere on +the host — no IP juggling. On systemd-resolved hosts, banger wires +this up automatically; everywhere else there's a manual recipe in +[`docs/dns-routing.md`](docs/dns-routing.md). -Today's catalog: +For `ssh sandbox.vm` (instead of `banger vm ssh sandbox`): -| Name | What it is | -|------|-----------| -| `debian-bookworm` | Debian 12 slim + sshd + docker + dev tools | +```bash +banger ssh-config --install +``` -See [`docs/image-catalog.md`](docs/image-catalog.md) for the bundle -format and how to publish a new entry. +That adds a marker-fenced `Include` line to `~/.ssh/config`. +`banger ssh-config --uninstall` reverses it. ## 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`. Accepts absolute paths or - `~/`-anchored paths; `~/foo` expands against `$HOME`. Relative - paths are rejected at config load. -- `firecracker_bin` — override the auto-resolved `PATH` lookup. - -Full key reference: [`docs/config.md`](docs/config.md). - -### `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. +`~/.config/banger/config.toml`. All keys optional; the two most +useful: ```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 +host = "~/.aws" 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/...`. Host paths must live under the -installed owner's home directory; `~/...` is the intended form, and -absolute paths are accepted only when they still point inside that -home. Default is no entries — add the ones you want. A top-level -symlink is followed only when its resolved target stays inside the -owner home. Symlinks encountered while recursing into a synced -directory are skipped with a warning — they'd otherwise leak files -from outside the named tree (e.g. a symlink inside `~/.aws` pointing -to an unrelated credential dir). +`vm_defaults` overrides banger's host-derived sizing. `file_sync` +copies host files into the VM's work disk at create time — handy +for credentials and dotfiles you want in every sandbox. Full +reference: [`docs/config.md`](docs/config.md). ## Updating ```bash banger update --check # is a newer release available? sudo banger update # download, verify, swap, restart, run doctor -sudo banger update --to v0.1.1 -sudo banger update --dry-run ``` -`banger update` pulls the release manifest from -`https://releases.thaloco.com/banger/manifest.json`, downloads the -release tarball + `SHA256SUMS` + `SHA256SUMS.sig`, verifies the -cosign signature against the public key embedded in the running -binary, hashes the tarball, atomically swaps the three banger -binaries, restarts both systemd services, and runs `banger doctor`. -On any failure post-swap, it auto-restores the previous install -from `.previous` backups before surfacing the original error. +The release tarball is cosign-verified against a public key embedded +in the running binary. On any post-swap failure, banger auto-restores +the previous install. See [`docs/privileges.md`](docs/privileges.md) +for the trust model. -Refuses to start while any banger operation is in flight. No -background update checks; updates only happen when you ask. See -[`docs/privileges.md`](docs/privileges.md) for the trust model. +## Uninstalling -## Advanced +```bash +sudo banger system uninstall # remove services + binaries; keep state +sudo banger system uninstall --purge # also wipe VMs, images, caches under /var/lib/banger +``` -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). +User config (`~/.config/banger/`) and SSH key +(`~/.local/state/banger/ssh/`) stay put either way — delete them by +hand if you want a full clean slate. ## Security -Guest VMs are single-user development sandboxes, not multi-tenant -servers. Each guest's sshd is configured with: +Guest VMs are single-user dev sandboxes, not multi-tenant servers. +sshd accepts only the host SSH key (no passwords, no +kbd-interactive), and guests are reachable only through the host +bridge (`172.16.0.0/24`). Don't expose the bridge or guest IPs to +an untrusted network. -``` -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. +The privileged surface lives entirely in `bangerd-root.service` and +is documented in [`docs/privileges.md`](docs/privileges.md). ## Further reading -- [`docs/config.md`](docs/config.md) — full config key reference. -- [`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. +- [`docs/config.md`](docs/config.md) — full config reference. +- [`docs/dns-routing.md`](docs/dns-routing.md) — `.vm` host-side resolution. +- [`docs/image-catalog.md`](docs/image-catalog.md) — image bundles and how to publish. +- [`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) — `vm create`, scripting, custom rootfs. +- [`docs/privileges.md`](docs/privileges.md) — trust model, capability set, daemon split. +- [`CONTRIBUTING.md`](CONTRIBUTING.md) — building from source, running tests.