Replace the per-bottle Docker sidecar bundle with the shared per-host
orchestrator + gateway, mirroring what the Docker backend already has.
- Add `bot_bottle/backend/firecracker/consolidated_launch.py`:
`_FirecrackerOrchestratorService` (subclasses `OrchestratorService`,
overrides `_gateway()` to return a `DockerGateway` with host port
bindings so Firecracker VMs can reach it via their TAP link);
`launch_consolidated()` registers the bottle by guest IP (attribution
key), provisions git-gate into the shared gateway, and returns the
shared CA + orchestrator URL for teardown; `teardown_consolidated()`
deregisters and cleans up.
- Rewrite `bot_bottle/backend/firecracker/launch.py`: removes the
per-bottle sidecar bundle (`_start_sidecar_bundle`, `_stage_git_gate`,
etc.) and `_mint_certs`; wires `launch_consolidated()` instead. The VM
still sends to `host_tap_ip:PORT` — Docker's PREROUTING DNAT + the nft
`ct status dnat accept` rule in the forward chain route the traffic to
the shared gateway container.
- Extend `DockerGateway` with `host_port_bindings` so the Firecracker
gateway publishes its ports on the host (`0.0.0.0:PORT`).
- Parameterise `OrchestratorService` with `orchestrator_name` /
`orchestrator_label` so Docker and Firecracker orchestrators can
coexist on the same host (`bot-bottle-orchestrator` vs
`bot-bottle-fc-orchestrator`).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Closes the gap left in #354: docker and macos-container already
smoke-test a freshly built agent image before launch; firecracker
built the image via its own docker_mod-based path but never ran the
check. Same one-liner as the other two backends now that _build_agent_image
uses docker_mod.build_image.
firecracker/launch.py reimplemented docker build/image-exists/rm/exec/cp
as private functions instead of the shared docker_mod used by the
docker and macos-container backends. Switching to docker_mod dedupes
the logic and gets --no-cache support for free (docker_mod.build_image
already reads BOT_BOTTLE_NO_CACHE); docker_mod gains docker_exec/
docker_cp general-purpose helpers to cover what the private versions did.
Adds a Firecracker-based backend for Linux, providing mature KVM-based
microVM isolation to replace smolmachines/libkrun (issue #342, closes
the dead-end tracked in #332).
Architecture:
- Guest control over SSH (dropbear injected into the rootfs) on a
point-to-point TAP link. `ssh -t` forwards SIGWINCH natively, so no
resize bridge is needed.
- Networking: a one-time, root-provisioned pool of user-owned TAP
devices (no shared bridge → no docker0/virbr0/cni0 collisions) plus a
dedicated `table inet bot_bottle_fc` nftables table (independent of
Docker/ufw/firewalld rules). `./cli.py firecracker setup` prints the
host-appropriate config (NixOS module or sudo script).
- Rootfs: `docker export` → ext4 via `mke2fs -d` (rootless, no mount),
cached by image digest; per-bottle SSH pubkey + IP passed via the
kernel cmdline.
- Sidecar: reuses the Docker bundle, published on the slot's host TAP IP.
- Fail-closed isolation: TAP pool verified at preflight; the egress
boundary is proven empirically post-boot (before the agent runs) by a
canary probe — the VM must fail to reach the host directly, or launch
is refused.
Linux hosts with Firecracker + KVM now default to this backend;
macOS stays on macos-container.
Not yet validated end-to-end on live hardware (requires the one-time
network pool). Unit tests + pyright pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G8p32HJgPoS1hLPWubbftM