Upgrading

Three things version independently: the per-host agent, the server, and cloud-hypervisor. None of them touch running VMs.

Agents, from the console

The fleet overview shows each host's agent version. When the server knows a newer release (it polls https://eitri.sh/dl/latest/manifest.json daily), an ↑ button appears next to hosts that are behind. Clicking it tells that one agent to upgrade itself:

  1. The agent downloads the host bundle from eitri.sh—the same tarball you would install by hand—verifies its sha256 against the release manifest, and takes the eitri-agent out of it.
  2. It swaps the binary in place—the old one is kept next to it as eitri-agent.prev—and re-execs. The process keeps its PID; running VMs are untouched and stay under the agent's care throughout.
  3. The host reports its new version on the next sync, and the button disappears.

If a download fails, nothing is swapped; click again to retry. To roll back by hand, stop the agent, move eitri-agent.prev back over the binary, and start it again.

The release pipeline makes this same offer to every connected host after it rolls a plane, and waits for them to report the new version, so a hosted fleet arrives at a release without anyone clicking through it host by host (deploy/server/README.md, stage 8).

Requirements. The button lights up only when the running agent's version orders before the published release. Release tags (vX.Y.Z), pre-releases (vX.Y.Z-pre.N, which order below the release they lead to), and git-describe builds (vX.Y.Z-N-g<hex>, N commits past the tag) all order, so a hand-deployed describe build behind the latest release is offered the upgrade, and one ahead of it is never offered a downgrade. Anything else—dev, a -dirty tree—orders before nothing and is never offered anything. The server needs release_manifest_url reachable; set it to "" in the server config to disable upgrade checks entirely.

Agents, by hand

An agent whose version never orders—a dev or -dirty build—never gets the button. The manual path is the same swap, done by you.

On Linux, take the current release from /dl/latest/ and read its version out of SHA256SUMS; on an arm64 host, _arm64 replaces _amd64 throughout:

curl -fsSLO https://eitri.sh/dl/latest/SHA256SUMS
V=$(sed -n 's/.*eitri-server_\(v[^_]*\)_linux_amd64\.tar\.gz$/\1/p' SHA256SUMS)
curl -fsSLO "https://eitri.sh/dl/latest/eitri-server_${V}_linux_amd64.tar.gz"
grep " eitri-server_${V}_linux_amd64.tar.gz$" SHA256SUMS | sha256sum -c -
tar xzf "eitri-server_${V}_linux_amd64.tar.gz"
sudo cp /usr/local/bin/eitri-agent /usr/local/bin/eitri-agent.prev
sudo install -m 0755 "eitri-server_${V}_linux_amd64/eitri-agent" /usr/local/bin/eitri-agent
sudo systemctl restart eitri-agent

The host bundle is the artifact—there is one set of bytes per platform per release, and it is the same one the console's button fetches. Each release also has an immutable home at /dl/<version>/, but only the current one is served, so /dl/latest/ is the address that always answers.

Running VMs survive the restart—the unit's KillMode=process (see Operations below) leaves the guests alone. The .prev copy mirrors what the button path keeps, so rollback is the same either way.

On a Mac the binary lives in the running account's own space (e.g. ~/.local/bin/eitri-agent). Overwrite it with eitri-agent from the darwin bundle (eitri-agent_<version>_darwin_arm64.tar.gz), then restart the LaunchAgent:

launchctl kickstart -k gui/$(id -u)/sh.eitri.agent

No sudo anywhere, same as the install. If guests turn unreachable through the gate after the swap ("cannot reach VM"), check the agent's Local Network permission—the quickstart's "Join a Mac" section covers it.

If the swapped-in agent reports it is not enrolled, it is running as a different account than the one that joined: enrollment state is per-account (~/.eitri/agent on macOS, /var/lib/eitri-agent on Linux), so under a new account the binary is a new host. Join it fresh from the console, then decommission the old host row it leaves behind. If that old agent is gone for good, the delete needs force—the dead-hardware escape hatch on host delete.

The server

The console banner links here when the published release differs from the running server's version. Server upgrades are manual and downtime is fine—agents keep reconciling and VMs keep running while it's away:

  1. Stop eitri-server.
  2. Replace the binary with the new release.
  3. Start it. The schema is applied idempotently on boot (existing tables are left as-is), and agents reconnect on their own.

A newer server with older agents is safe: new fields in the sync protocol are ignored by agents that predate them.

One exception, from v0.0.4 on. A guest's SSH host key is generated by the host that runs it, and an agent from before v0.0.4 does not know how to do that. The server will not place a VM on such a host: the create is refused with the host's name, the version it reports, and the endpoint that upgrades it — nothing could verify a guest created there. A connected host that has reported no version at all is read the same way, since nothing says otherwise. Only a host that is connected is judged: one that is offline has told this server nothing about the agent that will eventually run the VM, so the create is accepted as desired state and the guest is verified — or refused — when it boots. Placement chosen for you skips those hosts entirely. Existing VMs are unaffected — they keep the host key and certificate they were built with; one created against an older release with an uncertified key is refused by eitri ssh at host verification, and re-creating it after the upgrade is the fix.

cloud-hypervisor

Swap /usr/local/bin/cloud-hypervisor on the host. New and restarted VMs use the new binary; running VMs keep their old process until they stop. There is no live handover for running guests.

Operations

The agent runs under systemd (eitri-agent.service). The unit sets KillMode=process—that line is load-bearing: the default would kill every cloud-hypervisor guest in the unit's cgroup whenever the agent stops. Logs: journalctl -u eitri-agent.