README
eitri
A control plane for running virtual machines on your own hardware.
eitri turns a pool of machines into a small VM cloud. You describe the guests you want; each host runs an agent that makes reality match that description and reports back. Guests boot as real VMs under UEFI, own their own kernel, and get a sticky IP: on Linux under cloud-hypervisor on a per-host bridge, on macOS under Apple's Virtualization.framework via vfkit.
How it works
eitri is built around a single desired-state loop, the same shape as a kubelet:
eitri-server ──Snapshot──▶ eitri-agent ──▶ guests (the host's VMM)
(control plane) (one per host)
▲ │
└───────────Report───────────┘ (also the heartbeat)
- The control plane (
eitri-server) holds the desired fleet—which VMs should exist, on which host, with what resources—and streams it to each host over a persistent QUIC connection. - The agent (
eitri-agent) reconciles: it gives every VM its own worker goroutine that creates, converges, or tears down that one guest, so a slow operation on one VM never stalls the others or the host's heartbeat. It reports the actual state back on the same stream; that report doubles as the heartbeat, and the control plane marks a host offline after ~30s of silence. Each agent reports its binary version and can be upgraded per host from the fleet console, which also signals when a newer eitri release is available. - State is desired-state, not RPC. The loop is level-triggered: a failed step is retried on the next tick, and a host that reconnects re-derives everything from persisted records plus what it observes on the box.
The agent owns everything host-local: resource admission (vCPU / memory / disk / address are admitted through one serialized gate), addressing (on Linux an embedded DHCP server hands each VM a sticky, deterministic address and reserves it at create; on macOS the OS's own NAT assigns it and the agent reads the lease), and a content-addressed image cache (each base image is downloaded and decoded to raw once, then reflink-copied per guest).
Components
| Binary | Role |
|---|---|
eitri-server | Control plane: HTTP API, the MCP endpoint at /mcp, QUIC sync stream, and the SSH-CA jump gate. |
eitri-agent | Host agent: enrolls a host, reconciles its VMs, drives the host's VMM. |
eitri | Client CLI: signs an ephemeral cert with a tenant CA and reaches a guest through the gate. |
eitri-shape (regenerate the architecture graph) rounds out the binaries.
Access model
eitri is multi-tenant. A fleet of hosts is partitioned into tenants, each its own isolated namespace with its own SSH user CA—eitri holds no tenant user signing key. You reach a guest by name:
eitri ssh <vm-name>
eitri ssh self-signs a short-lived certificate with your tenant's user CA and
jumps through the server's gate, which authorizes the connection against the
tenant derived from the signing CA. Guest host certificates are namespaced the
same way, so names never collide across tenants.
Getting started
Build everything:
make build # binaries into ./bin
Enrolling a real host. The server mints a single-paste join blob; on the host,
eitri-agent join <blob> enrolls it (posting to /api/v1/enroll), pins the
server certificate from the blob, and persists its identity. From then on the
agent runs the reconcile + sync loop against the fleet. Run it under systemd
with scripts/eitri-agent.service—Restart=on-failure revives a crashed
agent, and its KillMode=process keeps running VMs alive across agent stops.
Guests boot from cloud images (the default is Ubuntu resolute) under UEFI, so
the guest owns its kernel and any disk-only image boots unmodified. On Linux
that is the CLOUDHV.fd firmware shipped to each host; on macOS it is the
framework's own EFI bootloader, and each guest keeps its NVRAM beside its disk.
Documentation
docs/ is indexed by what you're trying to do—understand it (architecture, ethos), run a fleet (cert rotation, revocation), use a fleet (connecting, mcp), or see why it's built this way (decisions). What ships next is in ROADMAP.md.
Repository layout
cmd/ entrypoints (eitri-server, eitri-agent, eitri, tooling)
internal/
agent/ reconcile loop, VMM drivers (cloudhv, vfkit), DHCP, image cache, netenv
server/ API, QUIC sync service, SSH gate/CA, store, registry, hub
transport/ QUIC transport shared by both sides
pb/ generated protobuf (proto/eitri/v1)
web/ SvelteKit fleet console, embedded into eitri-server
proto/ the wire contract
docs/shape.* the generated, explorable architecture graph
Development
make ci is the gate—everything a change must pass before it lands:
make ci
It runs go vet, the build for linux and darwin, the architecture fitness
functions, golangci-lint, gofmt, the test suite under -race, per-package
coverage floors, the drift checks (go mod tidy, protobuf, API contract,
shape graph), the deadcode sweep, a site render, and the console's tests and
typecheck. The .githooks/pre-push hook—installed by make hooks—runs it and
blocks a red push to main.
Common loops:
make test # go test -race
make deploy # roll HEAD to the fleet (local server + remote agents) — runs a real-VM boot-gate
make shape # regenerate docs/shape.{json,html} after a package change
Open Patches
No open patches.
Open Issues
Recent Commits
| Commit | Summary | Author | Date |
|---|---|---|---|
| e4d75234 | smoke: a host nobody could converge cannot fail a release | a73x | 2026-09-05 |
| 331baaef | backup: a job that moved registries needs the credential to match | a73x | 2026-09-05 |
| a34381fd | backup: the image takes its fixes from the distro too | a73x | 2026-09-05 |
| 6b45c813 | site: the image takes its fixes from the distro, not the base tag | a73x | 2026-09-05 |
| 0bebb556 | retro: what the volumes release cost | a73x | 2026-09-05 |
| ea6a7dcc | ship: the tag probe asks the package that now owns ordering | a73x | 2026-09-05 |
| 3113fc99 | docs: v0.0.8 release notes, and the volumes page joins the index | a73x | 2026-09-05 |
| 39c407f2 | volumes: durable block storage a VM attaches at create | a73x | 2026-09-05 |
| 7bf37937 | release: one Feature shape for every version floor | a73x | 2026-09-05 |
| 1ca7b3d5 | vms: a host that says nothing cannot vouch for its guests | a73x | 2026-09-05 |