Two lanes
mvm has two halves of its test suite: the half that builds + runs unit tests on
plain Linux, and the half that needs /dev/kvm to exercise Firecracker, the
seccomp functional probe, and the live-KVM smoke target. mvm-deployment ships
one lane for each.
ops/hetzner/ — cheap no-KVM
Section titled “ops/hetzner/ — cheap no-KVM”Targets Hetzner Cloud (any tier). Runs cargo fmt, clippy with
-D warnings, cargo test --workspace, cargo deny check, and
cargo audit. Verified empirically May 2026 that no Hetzner Cloud
tier exposes nested virt — the seccomp functional probe and any
Firecracker-touching test is silently skipped.
Use this lane for fast iteration on logic that doesn’t depend on KVM.
ops/ephemeral/ — live-KVM
Section titled “ops/ephemeral/ — live-KVM”Provider-agnostic. Targets anything exposing /dev/kvm:
- GCP — nested virt on
n2-standard-4(and other N2 / C3 / M-series), via--enable-nested-virtualization. - AWS —
*.metalSKUs only. Standard EC2 with Nitro nested virt does not surface KVM to Firecracker. - Azure — Dv3+ tiers expose nested virt.
- Vultr — Bare Metal SKUs (
vbm-*). - Latitude.sh / Equinix Metal / OVH Bare Metal Cloud — true bare metal, hourly billing.
The cloud-init.yaml shipped here is provider-agnostic. The provider-specific
quirks (SSH key injection, root volume sizing, AMI/image selection, region
naming) live in providers/<name>.sh.
Two-stage deploy flow
Section titled “Two-stage deploy flow”- Stage 1 — provision + cloud-init.
up.shinvokes the provider adapter to create the instance, attachescloud-init.yamlas user-data, then waits on SSH forcloud-init status: done. cloud-init is system-only: rustup, Nix, Firecracker, cargo-tools, KVM gate (hard-fail if/dev/kvmis absent), state dirs, SSH propagation. No clone, no build, no service install. - Stage 2 — rsync + post-deploy. rsync local mvm + mvmd onto the box,
ssh in, run
post-deploy.sh. That builds mvmd in release mode, installs/usr/local/bin/{mvmd,mvm-hostd}, renders the TOML configs from in-repo templates with a fresh HMAC secret, copies the four mvmd systemd units, enables them, and writes/etc/mvm-deploy-status = ready.
The split exists because mvmd is private — the box can’t git clone it, so we
rsync from the operator’s local checkout.
Adapter contract
Section titled “Adapter contract”Every providers/<name>.sh must define exactly four functions:
provider_default_instance_type() # echoed defaultprovider_default_region() # echoed defaultprovider_up <label> <cloud-init> # creates instance, prints IP on stdout's last lineprovider_down <label-or-empty> # destroys instance(s) tagged mvm-ephemeral=trueup.sh enforces the contract via declare -F checks at startup, so a broken
adapter fails fast with a clear error before any cloud calls.