KLYRN VM install and updates
One command, and a signature checked before anything runs.
The bootstrap verifies the release manifest against a key compiled into it, then hands to
klyrn-vm install, which runs six resumable stages. Every update after that verifies,
downloads beside the target, runs the candidate and makes it state its own version, and keeps the
binary it is replacing.
$ curl -fsSL https://get.klyrn.com/vm | sudo bash
Ubuntu 24.04 LTS, x86_64, and nothing else on the machine. The bootstrap refuses any other release or architecture by name rather than failing later.
- 6 stages
- each one applied, then verified by a separate check. A failed run resumes where it stopped.
- 0 of 1,995
- probes interrupted while a node agent replaced itself, across five vantages.
- 4 s
- from a signed controller update to serving again, database migrated.
- 32dc1e20…
- sha256 of the installed binary on a clean machine, byte-identical to the artefact.
What the command actually prints.
This is a real run: 0.1.0-beta.6 onto a fresh Ubuntu 24.04 guest that had no KLYRN directories on it. The address and the setup token are elided; everything else is the output as the installer writes it.
$ curl -fsSL https://get.klyrn.com/vm | sudo bashDownloading KLYRN VM (beta/latest, linux/amd64)manifest signature OK KLYRN VM controller installer 0.1.0-beta.6 › Check this server done › Directories, configuration and binary done › Node certificate authority and TLS certificates done › Database and first-run token done › systemd service done › Firewall done KLYRN VM controller is running. Open: https://<controller>:8443 Setup token (create the first administrator): <elided> Nodes connect to: https://<controller>:8444 The certificate is self-signed until you give the controller ahostname and a certificate.
:8443. Nodes connect to :8444, which speaks mTLS and nothing else.
The six stages
-
Check this server
Root, systemd, and both ports free. It refuses if less than 2 GiB is free under
/var/lib, because the database and update staging need the room. -
Directories, configuration and binary
/opt/klyrn-vmholds the binaries;/etc/klyrn-vmand/var/lib/klyrn-vmstay 0700. The binary is also linked at/usr/local/bin/klyrn-vm, because/opt/klyrn-vm/binis on nobody's PATH. - Node certificate authority and TLS certificates The CA that issues node certificates, and the controller's own certificate. It is self-signed until you give the controller a hostname and a real one.
- Database and first-run token The schema, and (only when no user exists yet) a one-time setup token written 0600. It is what creates the first administrator, and using it deletes it.
-
systemd service
klyrn-vm-controller.service, started and then confirmed by connecting to both ports, rather than by asking systemd whether it believes it started something. - Firewall Opens 8443 and 8444 when ufw is active. When it is not, the installer says so and leaves your firewall alone.
Every stage is applied, then verified by a separate check, and its result is written to disk. If one
fails, the installer prints the cause and stops; running the same command again resumes at that
stage. klyrn-vm install --plan prints the six and their state and changes nothing.
The update chain, and the step most updaters leave out.
GET /api/v1/update says what the channel publishes and whether this installation may
take it. POST applies it. The order is fixed and none of it is optional.
-
Verify the signature
The manifest and its signature are checked against the ed25519 keys compiled into this binary, not against a key fetched alongside it. KLYRN VM has its own release key, separate from the hosting panel's, so a compromise of one does not reach the other's fleet. An unsigned or wrongly-signed manifest ends it, and nothing is parsed out of a manifest that has not verified.
-
Ask whether this machine may take it
A release can declare
min_upgradable_from: a floor below which it may not be applied directly, because it carries a migration that assumes an earlier one has run. A machine left switched off for two years is told, by name, which version to go through first. A published version older than the running one is a downgrade: it stays available, because an operator rolling a channel back after a bad release needs it, but it never happens without being asked for. -
Download beside the target, hashing as it arrives
The bytes stream to a temporary file in the same directory as the binary being replaced, hashed on the way in and compared against the sha256 in the signed manifest. They are never written to the target path, so a failed or partial download cannot leave half a binary where a working one was.
-
Run the candidate and make it state its own version
The new file is executed with
version --shortand has to answer with exactly the version the manifest promised. A hash proves the bytes arrived, not that they run on this machine. An arm64 build has a perfectly correct sha256 and will not start on an amd64 host. The moment to find that out is while the working binary is still on disk. That flag is treated as a contract by the code that prints it: one line, the version, nothing else, on stdout. -
Keep the outgoing binary, then rename
The binary being replaced is copied to
.prevfirst, then the candidate is renamed into place. Both paths are on one filesystem (which is why the download went beside the target), so the last step is atomic: there is no instant at which the path does not exist. Nothing in this path restarts anything or touches a guest; replacing the file is where it stops.
A node updates its agent without touching a guest.
The controller stores a desired agent version per node and sends a release name, never a binary, never a URL to trust, never a command. The node's own updater fetches that version's manifest from its configured download base and verifies it against the keys compiled into its own agent. The controller cannot make a node run bytes that a KLYRN release key did not sign.
0 of 1,995 probes interrupted while the agent went from beta.2 to beta.3
- Vantages
- five: ICMP, SSH banner and HTTP, from the node and from off-box
- Interval
- 250 ms, across a 100 s window
- Connections
- every probe a fresh one, so a held-open socket cannot hide an outage
- Agent process
- MainPID 810 → 1958, and guest boot time identical either side
The probe was calibrated before it was trusted. A probe that measures no downtime and a probe
that is broken return the same number. So a deliberate five-second virsh suspend was
run first, and the probe measured 5.11 s unavailable with an ICMP round trip peaking at
4,709 ms. Only then was its zero worth printing.
The reason a guest survives is structural rather than careful: QEMU processes are children of libvirt, not of the agent, and an agent update does not restart libvirt. The agent additionally refuses to update while a task is in flight on that node: a migration, a backup, an image pull. A fleet updates through a rollout that sends one canary alone, checks health, then batches, and pauses itself on the failure budget. Health there is not "the process started": the task must have succeeded, the channel must have come back, the agent must report the version that was asked for, it must answer a fresh inventory, and its guest count must not have dropped.
For a period, no KLYRN VM release could have been applied at all.
The signer wrote and signed the pretty-printed manifest. The verifier checked the
canonical, compacted form. Each half was correct on its own, and each had been tested only
against itself. The updater's tests signed their fixtures with the verifier's own
canonicalisation rather than with the product's signer, and the release gate's
verify-manifest checked the same raw bytes the signer had just used. So every gate
passed, every release looked signed, and every one of them would have been refused.
It surfaced the first time a live controller was asked to update itself.
the manifest is not signed by a key this build trusts
The rule now has one spelling and both sides call it: release sign writes the
canonical bytes and signs exactly the bytes it wrote, verify-manifest canonicalises
before verifying, and a test puts the real signer through the real verifier with nothing in
between. Signing the canonical form has a second effect worth having: a manifest reformatted in
transit (by a proxy, by a store that re-serialises JSON, by a person) still verifies, while one
whose meaning changed does not.
Found in the same exercise and owed the same honesty: the release public key had never actually
been embedded in any build. Nothing in the binary read the variable, so the linker dropped it and
the -X flag was silently ignored. The release verified perfectly using the key from
the file on the build machine, which was never the question. The release script now greps the
built binary for its own signer and refuses to publish a build that could not verify an update.
What is proved, and what is not.
0.1.0-beta.6 installed onto a fresh Ubuntu 24.04 guest with no KLYRN directories on it,
through curl … | bash, exit 0. The sha256 of the installed binary is byte-identical to
the artefact. klyrn-vm version answers as an unprivileged user,
/opt/klyrn-vm is 0755, and /etc/klyrn-vm and
/var/lib/klyrn-vm stay 0700 and unreadable. Here is the rest of it.
-
Nothing has been published to
dl.klyrn.comyet. The lab has no route to it, so the clean install fetched from a mirror whose manifest was re-signed with the same key. That proves the machinery end to end. It does not prove the bytes are the published ones, and this page does not claim it does. -
The public key lives in the served copy of the bootstrap, not in the repository. The
checked-in
get.shcarries an empty key so that a build can never trust one by accident. Run from a copy rather than fromget.klyrn.com, it prints a warning saying the download matched the checksum in its manifest but nothing proved the manifest came from KLYRN. - 0.1.0-beta.5 is superseded, and by an installer run rather than a repair. It installed a binary an unprivileged user could not reach. The fix was proved by running the fixed installer on a fresh guest, not by correcting the broken one by hand.
- KLYRN VM never updates libvirt, QEMU or the kernel as a side effect. Host maintenance (drain, update, reboot, verify, rejoin) is a separate operation with its own ADR, and it is not built.
- The controller is x86_64 on Ubuntu 24.04 LTS only. The manifest format carries arm64; the bootstrap refuses to install on it, and nothing else has been accepted.
An installation fetched from a mirror keeps looking at that mirror.
KLYRN_VM_DOWNLOAD_BASE is carried through to
klyrn-vm install --download-base, so a private beta or an air-gapped estate is one
command rather than one command and a hand-edited JSON file. The location moves; the signature
requirement does not.