docs/releases.md
Ref: Size: 20.1 KiB History
# Releases Tarballs and checksums for every release live at [eitri.sh/dl](https://eitri.sh/dl/), with `/dl/latest/` pointing at the newest. The [quickstart](quickstart.md) takes a release from download to a running VM. ## v0.0.8 Storage that outlives the VM. A volume claim is a request for durable bytes; the first VM that names the claim puts those bytes on a host and leaves them there. Delete that VM, create another naming the same claim, and the data is waiting — a checked-out repo, a database, a build cache, anything you would have lost with the root disk. The guest sees a raw `/dev/vdc` and owns everything above it: partition it, format it, mount it, exactly as you would a disk you bought. **A claim is yours; where it lands is the fleet's.** You ask for a name and a size and get a claim back, pending. The first create that names it decides which host materializes the bytes, and every later VM naming that claim is placed on that host — data does not move between hosts, so the guests that use it do not either. A create that names a different host is refused, and the refusal says which host holds the claim. **Nothing deletes a volume by accident.** A claim a VM still holds cannot be deleted. A volume file the fleet does not recognize is reported and kept, never reclaimed. A tombstoned claim's bytes go only after the grace period, and a host that cannot answer whether a file exists is treated as a host that has not answered — not as one whose disk is empty. Force-removing a host is the single path that destroys volumes on purpose, and the audit record says how many it took. **A host that says nothing cannot vouch for its guests.** A VM whose host has stopped reporting now reads `unreachable` rather than holding its last known `running` or `creating` forever. The status you see is what a host actually told the plane, and a host that went quiet says so instead of leaving stale optimism on the page. **Your cloud-init and eitri's both land.** A tenant document that writes files, runs commands or sets users no longer displaces the CA trust file, the sshd drop-in, the clock fix or the root grow that eitri needs — both parts merge, your scalars win, and the boot gate proves it on every run rather than assuming it. Under it: the toolchain moved to Go 1.27, mutation testing is one make target away and runs on every push, and a round of hardening tightened what an unauthenticated stranger can learn from the plane, what a misconfigured agent will start with, and which refusals name the fix. **Known limits.** - Volumes are attached at create and only at create. There is no detach, no reattach to a running VM, and no way to move one between hosts. - The surface is the HTTP API. Volumes have no console page, no CLI command and no MCP tool yet; `POST /api/v1/volume-claims` and the `volume_claims` field on create are the whole of it. - A volume is exactly as durable as one host's disk. There is no replication and no snapshot; a host that loses its disk loses every volume on it. - Force-removing a host destroys its volumes. Their claims return to pending rather than staying bound to a host that is gone. - Rolling a server back below v0.0.8 leaves a host that holds volumes stuck in decommissioning, with nothing said about why. - A volume file that disappears from the host is re-created empty on the next attach, and nothing alerts on it. - A host running an agent older than v0.0.8 refuses volume-bearing creates rather than booting the guest without its disk. ## v0.0.7 A VM can be a machine on your network. Declare a bridge on a Linux host, name it to the agent, pick that name at create — the guest gets a second NIC on your LAN, addressed by your own DHCP server, while keeping everything it already had: NAT egress, the gate, published ports, its neighbours. The thing that made you reach for multipass — a VM your other machines can just talk to, running a service that advertises its own address — is now one field on create. **A named network is a second life, not a different one.** The guest's private NIC stays: eitri still reaches it there, other guests on the host still reach it there, and outbound still leaves through the host. The second NIC is where your network meets it. Which address a service advertises is that service's own business. **The address is discovered, not invented.** Your DHCP server owns that network, so eitri watches the guest's own lease exchange and reports what was granted — the console's **Network** row carries your name for the network and the address the rest of it knows the guest by. A guest configured static there is reachable all the same; eitri just has nothing to report. **Your network is protected from its guests.** A guest cannot forge its own reported address — eitri believes only frames that arrived from your side of the wire — and it cannot answer DHCP onto your network at all: lease offers from a guest are dropped at the tap, before the bridge can carry them to a sibling, your real machines, or the switch. **Refusals name the fix, wherever you ask from.** A create naming a network its host does not advertise says what that host does serve; a Mac is told its OS cannot serve named networks rather than told to set a flag its agent would refuse; an agent given `--host-network` for a bridge that is missing refuses to start, naming it. Over MCP, `vm_create` takes the same `network` field, reports the discovered address, and default placement picks a host that actually serves the network you asked for. **Known limits.** - Named networks are Linux-host only. A Mac agent given `--host-network` refuses to start; place bridged guests on a Linux host. - A host serving named networks needs the kernel's nftables bridge family; an agent that cannot program it refuses to start and names the command. Hosts with no named networks are untouched. - A bridged guest cannot serve DHCPv4 onto that network — deliberate, so a guest can never hand out leases to your real machines. DHCPv6 and router advertisements are not filtered: a bridged guest is a full peer, and some of yours may be routers on purpose. - The private NIC keeps the default route via route metrics the default Ubuntu image honours; an image whose distro renders cloud-init's network config without netplan may order its own routes. ## v0.0.6 The distance from a token to a running VM. `eitri init` is one guided command that takes you from a fresh access token to a first shell — it proves your token, settles your SSH CA (uses the one you have, never blindly makes a second), and writes the config every later command reads. After it, `eitri ssh <vm>` needs no environment at all. **One guided setup.** `eitri init` walks the steps and confirms each before it acts: which plane, which tenant, which signing key. It never generates a CA when you already have one, and it tells you plainly when a key it finds is one no guest will trust — the failure that used to read as a bare "permission denied." **One MCP surface.** eitri speaks MCP in exactly one place: `/mcp` on the control plane, with a bearer token. The local stdio binary is gone — a self-hoster gets an MCP server by hosting the plane, and there is one credential story to keep honest instead of three. **The console uses the width it is given.** Wide viewports get two real columns — the facts a page reports on the left, the actions you can take on the right — instead of an 80-column measure with a field of empty space beside it. A guest names the CAs it trusts, baked in when it was created. An offered agent upgrade shows itself as pending rather than a button that ignores you. A VM being created says what it is doing — "downloading image 1.2/3.7 GiB" — instead of a silent "creating," and a published port counts the sessions it carries. **Refusals that tell you the fix.** A tenant with no registered CA is told the three ways to add one, not just told no. A full host names what is holding its capacity — and if it's a VM you just deleted, it says to wait for the teardown rather than to delete another. A gate bound to every interface with no name to advertise refuses to boot and says so, instead of booting into certificates no client can verify. A sign-in that fails names the origin that broke it — usually a browser at the wrong address for the console. **Known limits.** - The local stdio MCP binary is retired. If you registered it with `claude mcp add eitri`, point your client at `/mcp` on your plane with a personal access token instead. - Self-hosters: a gate bound to a wildcard (`:2222`) now requires `ssh_gate_domain` and refuses to start without it — a gate has to know the name clients dial. Set it, or bind one concrete address. - The first v0.0.6 plane freezes each existing guest's trusted-CA set at upgrade, so guests that never had a record now do — and can, for the first time, be flagged when a tenant's CA set moves on. A VM created before this shows its frozen set rather than "unrecorded." - A mixed fleet: publishing a UDP port on a host still running a pre-v0.0.5 agent is refused, because that agent would bind it as TCP and report it healthy while it carried nothing. Upgrade the host's agent, then publish the port. ## v0.0.5 A published port can carry datagrams. Expose a VM's UDP port the same way you expose a TCP one — DNS, game servers, anything that doesn't wait for a handshake — and eitri carries the datagrams to the guest and the replies back. Around it, VMs got sturdier: they persist by default, they recover their clock after a host sleeps, and a host that can't certify a guest stops being somewhere to put one. **UDP exposures.** A published port is TCP or UDP now, chosen at create. Traffic on a published port rides its own path so a busy port can't starve the fleet management the same tunnel carries. **Every VM is persistent.** A guest that outlives a reboot is the default and the only mode — there is no per-VM knob to lose track of. A powered-off guest is a stopped machine you can start again, not a thing that quietly vanished. **A guest keeps its clock through a host's sleep.** Close a laptop that's running guests, open it hours later, and the guests step their clocks back into line instead of sitting in the past — the state that used to make a gate admit you and the guest deny you. **A host that cannot certify a guest is not a place to put one.** The plane won't schedule a VM onto a host whose agent can't sign the guest's host key — the VM would boot unreachable. It refuses at create, naming the fix. **Known limits.** - Fielded hosts: the exposure session caps rest on the agent's file-descriptor limit, which the shipped unit raises. A host whose unit predates that line needs the unit reinstalled to get the higher ceiling. - macOS hosts: every agent upgrade is a new binary, so macOS re-asks its Local Network / privacy permission each time. Approve it in System Settings after an upgrade, or the agent looks enrolled but silently can't reach its guests. ## v0.0.4 A PAT is enough. Point an LLM client anywhere on the internet at `https://api.eitri.sh/mcp` with a personal access token and it gets the whole eitri toolset: create a VM, run commands in it, publish a port, hand back the address. No local install, no config file, no CA of your own. Around it, the console is drawn in the site's own language. **MCP.** eitri-server serves MCP at `/mcp` over streamable HTTP, authenticated with the same bearer token the API takes; a caller sees exactly the fleet its token can see. `api.eitri.sh` carries that one route and nothing else. **Delegated access, not a held key.** eitri never holds a signing key for your guests. When a token-only caller needs SSH access, you delegate it: sign the public key eitri shows you with your own CA, on your own TTL (`ssh-keygen -s`). Revoke it by letting it expire or by deleting it — eitri's access ends when you say so. Because the certificate chains to a CA you already registered, VMs created before you delegated accept it too. **Your guests' host keys stay on your hosts.** A guest's SSH host key is born on the machine that runs it and never leaves it; the control plane signs only public keys. The database now holds no private keys at all — keys stored by an earlier release are dropped the first time the new server opens it. `vm_create` reports what it is waiting for as it waits — usually seconds, minutes when a host downloads an image for the first time — so a long call reads as a VM coming up rather than silence. **Upgrades read a host bundle.** A self-upgrading agent can now be handed the same tarball you would install by hand, verified against the release manifest as ever. `/dl` still carries bare binaries beside the bundles for agents from older releases. **The console speaks the site's language.** We revamped the console: monospace, hairline rules, light and dark mode—because we care. **Known limits.** - `/mcp` authenticates with a bearer token and nothing else. Browser connectors that sign in with OAuth—claude.ai among them—cannot reach it. - A delegation lives in memory. A control-plane restart or redeploy drops it and you delegate again — one `ssh-keygen` against a public key that has not changed. This is a property, not a bug: eitri's access to your guests ends when its process does. - An agent from an earlier release does not know how to generate a guest host key, so a VM it creates against a v0.0.4 control plane boots with an uncertified one and `eitri ssh` fails host verification with a clear error. Upgrade the host's agent — from the console, as usual — and re-create the VM. - Self-hosters: v0.0.4 adds a required `key_encryption_key` to `server.json` (`openssl rand -hex 32`), and the server refuses to start without it. It encrypts the fleet's own SSH CA and gate host key, so keep it outside your database backups. ## v0.0.3 macOS hosts. An Apple-silicon Mac joins the fleet as a host and runs Linux guests beside your Linux boxes (so the Mac Mini can earn its keep after all). Around it: hosts own their guest networks, you can publish a VM's ports, and the agent reads guest images without a conversion tool. **Mac hosts.** An Apple-silicon Mac enrolls like any other host and runs guests through vfkit, the signed helper that drives Apple's Virtualization.framework. `brew install vfkit` is the one thing the agent cannot do for you—vfkit only works carrying Apple's virtualization entitlement, and an entitlement lives in a signature, so it comes from a signed distribution or not at all. Everything else the agent does itself: it installs as a LaunchAgent in the session you're already in, keeps its state in `~/.eitri/agent`, and upgrades from the console like every other host. Releases ship a `darwin_arm64` host bundle beside the Linux ones. **Hosts own their guest network.** The subnet a host's guests live on is a fact about that host, and the host is the only thing that can observe it. So the direction reverses: a host tells the fleet which subnet its guests are on, a joining host proposes its own—the fleet's allocation is only a suggestion— and a Mac reports whatever subnet vmnet actually gave it. A host also refuses a guest subnet that contains its own uplink, naming both sides and the flag that fixes it, because that overlap takes out the very connection you'd use to fix it. **Published ports.** SSH goes through the gate; anything else a guest serves needs a published port. You publish a VM's port on its host: the fleet binds a port on the host (30000–32767 when you leave the choice to it) and pipes it to the guest. The MCP tools speak it too, so an LLM that just built something in a VM can publish the port and hand back the address to dial. There is no authentication in front of a published port—whoever can reach the host on that port reaches the service—so publish what you're content to serve to everything on that network, and leave the rest to the gate. **Images, decoded in-process.** The agent reads guest images itself: qcow2, vmdk, vhdx, vdi, parallels, vpc and asif are parsed in-process, gzip is decompressed sparsely as it lands, and an image that is already raw is renamed into place—there is no format to change. `qemu-img` goes with it. A Linux host needs KVM and nothing else. **Fixes.** A VM says which SSH key eitri put in it—the key type, its fingerprint, its comment. A one-click create picks a default image its host's architecture can run, instead of one image for the whole fleet. A guest's address is judged on its own terms—does it name something reachable—rather than against an allocation the fleet made. A lost guest's report quotes the last thing its hypervisor said, instead of "lost" and nothing else. A backend signals only the processes it started, so a pid recycled across a host reboot is never someone else's process getting SIGKILL. And a host is asked whether it can run a guest at all before a create spends anything on a download. **Known limits.** - Apple silicon only: there is no darwin/amd64 bundle. An Intel Mac is told there is no artifact for it—true, and checkable—rather than offered a binary nobody has booted a guest on. - A Mac joins as a host. The control plane stays Linux; the Mac bundle carries no server. - vfkit is yours to install (`brew install vfkit`); eitri cannot mirror a binary whose entitlement lives in Apple's signature. ## v0.0.2 One fix, worth its own release. A guest's disk was handed to cloud-hypervisor without a declared format, and an autodetected raw image gets its sector 0 write-protected—so the first boot's partition grow only ever happened in the kernel's memory, and the guest died in initramfs the first time it was power-cycled. Disks are now declared raw and the partition table stays writable. ## v0.0.1 The first release. eitri is a small cloud backed by your own hardware: one control-plane server—hosted at eitri.sh or run yourself—an agent on every Linux/KVM box you enroll, cloud-hypervisor microVMs, a web console, and SSH into guests through an identity-checking jump gate. **Fleet.** Enroll any Linux/KVM host with a one-shot join command from the console. The agent reconciles each VM in its own worker, admits VMs against the host's real resources, fetches cloud-hypervisor and the guest firmware itself (sha-verified against the release), and upgrades itself from the console—running VMs survive agent restarts and upgrades. **VMs.** UEFI guests boot any cloud-init disk image—the guest owns its kernel. Addressing is an embedded DHCP server with per-VM reservations. The console shows live host metrics, per-VM status and a browser serial console; tenant quotas bound what a tenant can create. **Sign-in and tenancy.** The console signs in through OIDC—the bundled `eitri-oidc` issuer for a single box, or [bring your own IdP](byo-idp.md) (Google, Okta, Keycloak…). Every identity's first sign-in creates its own tenant; every API request, event stream, console session and audit row is tenant-scoped. CLI and automation authenticate with personal access tokens minted in the console. **SSH.** Tenants register their own user CA—eitri never holds a user private key. `eitri ssh` self-signs a short-lived certificate, pins eitri's host CA, and jumps the gate to `<tenant>.<vm>`; certificates are revocable per serial. See [connecting.md](connecting.md). **Hosted.** Don't want to run the control plane? Sign in at [console.eitri.sh](https://console.eitri.sh) with Google and join your own machines—the same server the tarball ships, run for you. Your hardware still serves every VM; the hosted plane only coordinates it. Self-hosting stays a first-class path. **Running it.** `eitri-server` and `eitri-oidc` ship with hardened systemd units and run as dedicated non-root users; the agent's unit documents exactly why it runs as root. Server and issuer state live under `/var/lib`, config under `/etc/eitri`. The server speaks plain HTTP—front it with TLS if it leaves your LAN. **Platforms.** Hosts: Linux amd64 and arm64 with KVM. Client CLI: Linux and macOS, amd64 and arm64. **Known limits.** - One user per tenant. - No TLS termination in the server—put a proxy in front for anything routable. - Switching `oidc.issuer` after tenants exist strands their identity bindings; pick your issuer before inviting sign-ins. - macOS hosts are not supported.