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
33 lines
992 B
Python
33 lines
992 B
Python
"""Cleanup plan for the Firecracker backend."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
|
|
from ...log import info
|
|
from .. import BottleCleanupPlan
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class FirecrackerBottleCleanupPlan(BottleCleanupPlan):
|
|
# PIDs of orphaned firecracker VMM processes and the sidecar
|
|
# containers left behind by previous bottles.
|
|
vm_pids: tuple[int, ...] = ()
|
|
containers: tuple[str, ...] = ()
|
|
run_dirs: tuple[str, ...] = ()
|
|
|
|
def print(self) -> None:
|
|
if self.empty:
|
|
info("firecracker cleanup: nothing to remove")
|
|
return
|
|
for pid in self.vm_pids:
|
|
info(f"firecracker VM process: pid {pid}")
|
|
for name in self.containers:
|
|
info(f"firecracker sidecar container: {name}")
|
|
for path in self.run_dirs:
|
|
info(f"firecracker run dir: {path}")
|
|
|
|
@property
|
|
def empty(self) -> bool:
|
|
return not (self.vm_pids or self.containers or self.run_dirs)
|