arcbox-vm 0.6.7

Guest-side sandbox orchestration over nested Firecracker microVMs; ships the in-sandbox vm-agent init.
# arcbox-vm

Guest-side sandbox orchestration: runs **inside** the ArcBox System VM and
manages nested [Firecracker](https://firecracker-microvm.github.io/)
microVMs — one per sandbox — through
[`fc-sdk`](https://crates.io/crates/fc-sdk).

Do not confuse it with `arcbox-vmm`, which is the **host** VMM that boots
the System VM itself on Virtualization.framework or KVM. Different layer,
different machine.

```
host (macOS)              System VM (Linux)            sandbox (Firecracker)
arcbox-daemon    ──vsock──►  arcbox-agent      ──vsock──►  vm-agent (PID 1)
  arcbox-vmm                   arcbox-vm                     workload
                                 fc-sdk
```

`arcbox-agent` is this crate's only consumer: it owns the `sandbox.v1`
surface and the vsock transport, and calls `SandboxManager` underneath.
There are no service implementations, no tonic, and no daemon here.

The crate also ships the **`vm-agent`** binary — a separate, much smaller
program that becomes PID 1 *inside* each sandbox. It imports only the
protocol leaves (`boot_proto`, `file_io::proto`, `file_watch`, `vsock`
constants, `listen_table`, `user_spec`), never the manager;
`arcbox-agent`'s `rootfs_builder` stages it into every sandbox rootfs.

## Usage

```rust
use std::sync::Arc;
use arcbox_vm::{SandboxManager, SandboxSpec, VmmConfig};

let manager = Arc::new(SandboxManager::new(VmmConfig::default())?);

let (id, ip) = manager
    .create_sandbox(SandboxSpec {
        vcpus: 1,
        memory_mib: 512,
        ..Default::default()
    })
    .await?;
```

`VmmConfig` loads from TOML (`VmmConfig::from_file`) or is built
programmatically; `[firecracker]`, `[network]`, and `[defaults]` are the
sections that matter, and `[firecracker.jailer]` opts into jailer mode.
See `config.rs` for the fields.

## Build and test

```bash
cargo test -p arcbox-vm                 # unit + integration, no Firecracker needed
cargo clippy -p arcbox-vm -- -D warnings

# vm-agent, as the release ships it (aarch64 musl; brew install FiloSottile/musl-cross/musl-cross)
cargo build -p arcbox-vm --bin vm-agent --target aarch64-unknown-linux-musl --release
```

Everything real — TAP creation, boot, checkpoint — needs Linux with
`CAP_NET_ADMIN`, a `firecracker` binary, and (for jailer mode) root.
`examples/sandbox-smoke.rs` is the end-to-end walkthrough;
`.github/workflows/test-vm-linux.yml` runs the whole ladder on real KVM.

## Jailer mode: every path the FC API sees is chroot-relative

Firecracker `pivot_root`s before it processes any API request, so a
host-absolute path handed to the API resolves inside the chroot and
returns `ENOENT`. Kernel and rootfs are therefore *staged into* the
chroot before boot (`{chroot}/vmlinux`, `{chroot}/rootfs.ext4`) and the
API is given `/vmlinux` and `/rootfs.ext4`. Checkpoints write to
`{chroot}/snapshots/{id}/` and are moved out to the catalog afterwards;
restore copies them back into the *new* sandbox's chroot first.

The same rule is what keeps vsock sockets from colliding: the vmstate
records the UDS as `/run/firecracker.vsock`, which is a different host
path in every sandbox's chroot.

## Data layout

```
{data_dir}/                       # default /var/lib/firecracker-vmm
├── kernels/vmlinux
├── images/*.ext4
├── sandboxes/{id}/               # firecracker.sock, .vsock, .log, .metrics
└── snapshots/{sandbox-id}/{snapshot-id}/
                                  # vmstate, mem, meta.json
```

Under jailer mode the live files instead sit beneath
`{chroot_base_dir}/firecracker/{sandbox-id}/root/` (default
`/srv/jailer`), and the snapshot catalog above is still the durable home.

## License

MIT OR Apache-2.0, inherited from the workspace root.