c276f7b0b1
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
44 lines
1.4 KiB
Python
44 lines
1.4 KiB
Python
"""Active-agent enumeration for the Firecracker backend.
|
|
|
|
The agent runs in a VM (no container to list), so a live bottle is
|
|
identified by its running sidecar container `bot-bottle-sidecars-<slug>`
|
|
— the same discovery-by-prefix the other backends use.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import subprocess
|
|
|
|
from ...bottle_state import read_metadata
|
|
from .. import ActiveAgent
|
|
|
|
_SIDECAR_PREFIX = "bot-bottle-sidecars-"
|
|
|
|
|
|
def enumerate_active() -> list[ActiveAgent]:
|
|
result = subprocess.run(
|
|
["docker", "ps", "--format", "{{.Names}}",
|
|
"--filter", f"name={_SIDECAR_PREFIX}"],
|
|
capture_output=True, text=True, check=False,
|
|
)
|
|
if result.returncode != 0:
|
|
return []
|
|
out: list[ActiveAgent] = []
|
|
for name in sorted(n.strip() for n in result.stdout.splitlines() if n.strip()):
|
|
slug = name[len(_SIDECAR_PREFIX):]
|
|
metadata = read_metadata(slug)
|
|
if metadata is None or metadata.backend != "firecracker":
|
|
# Skip sidecars owned by another backend (docker/smolmachines
|
|
# share the container-name prefix).
|
|
continue
|
|
out.append(ActiveAgent(
|
|
backend_name="firecracker",
|
|
slug=slug,
|
|
agent_name=metadata.agent_name,
|
|
started_at=metadata.started_at,
|
|
services=(),
|
|
label=metadata.label,
|
|
color=metadata.color,
|
|
))
|
|
return out
|