KLYRN VM  beside SolusVM 2

Install it on the server SolusVM already runs on.

A node enrolled in observe mode reads libvirt, storage and networking and reports. It performs no mutation of any kind. That is not a habit the code keeps: it is refused at the three places a change can physically leave the agent's process, and every one of them is fail-closed. So the way to evaluate KLYRN VM is to put it on a hypervisor that is serving customers and measure what moved.

0 of 21
host artefacts differed around an observe install: packages, routes, addresses, firewall rules, libvirt config, domains, storage, services. None beyond KLYRN's own.
18
differences the same probe found when a negative control changed the host on purpose. The probe was checked before the result was believed.
27 of 27
attempts to operate a staged guest refused, across every spelling of its identity, with no task queued.
0 hits
for the panel token across eleven endpoints and the audit log, searched byte-wise for the value and its prefix.

Observe mode

The mode a node is enrolled in when it belongs to another manager.

Every KLYRN VM node starts in observe mode and stays there until an administrator promotes it. On a host SolusVM 2 owns, that promotion never happens, and the agent spends its whole life reading: the libvirt domain list and each domain's definition, the storage pools and volumes, the networks, the nwfilters, the routes and the addresses. It reports them to the controller. It writes nothing back to any of them.

  1. 1

    An external program

    Every argv is matched against an allowlist of reads, on the basename, so an absolute path cannot change the answer. The rule reads the arguments, not just the program: qemu-img info reads a disk and qemu-img resize changes one, and they are the same binary.

  2. 2

    A libvirt RPC

    The call's name must appear in a table of reads. Absent means refused, including refresh pool, which only rescans a directory and harms nothing, but which observe mode has no need of. A guest-agent ping is permitted; every other guest-agent call is not, because fsfreeze stops a customer's database writing.

  3. 3

    A write to the filesystem

    Atomic writes, directory creation, copies and symlinks all check the path against KLYRN's own trees. A write to /etc/libvirt/qemu/foo.xml, to /etc/netplan, to /etc/nftables.conf or to a guest's disk does not reach the filesystem. It returns a refusal naming the path.

All three are fail-closed. A command, a call or a path nobody classified is refused, and the agent says out loud what it declined to do. The cost of that mistake is a fact the agent could not collect. The cost of the opposite mistake is somebody's hypervisor.

The same rule that starts KLYRN's own agent refuses to stop yours.

Observe mode has to be able to install itself, or it could not be installed at all. So it is allowed three things, all of them KLYRN's own: its own directories, its own systemd unit, and systemctl daemon-reload, which re-reads unit files and starts or stops nothing. The write verbs (enable, disable, start, stop, restart, mask) accept only a unit whose name begins klyrn-vm. Everything else falls off the end of the same function.

Permitted

  • systemctl restart klyrn-vm-node
  • systemctl daemon-reload
  • systemctl is-active libvirtd read verbs may name any unit
  • virsh dumpxml the definition, unchanged
  • writes under /etc/klyrn-vm/, /var/lib/klyrn-vm/, /var/log/klyrn-vm/, /opt/klyrn-vm/, /run/klyrn-vm/

Refused

  • systemctl stop libvirtd
  • systemctl disable solusvm-agent
  • any libvirt call that is not in the read table
  • any write outside KLYRN's own trees
  • console.open reclassified from read to mutating: a console is a keyboard attached to a running guest

Whether the guard is armed is checkable rather than asserted. klyrn-vm doctor prints its state on the host:

klyrn-vm doctor observe
identity       OK    solus-fixture-2 (id 3), observe mode
observe guard  OK    armed: no package, no libvirt write,
                     no file outside /etc/klyrn-vm/, /var/lib/klyrn-vm/

The measurement

Twenty-one photographs of a host, taken twice.

A structural argument is still an argument. observe-safety.sh runs on the host being enrolled: it photographs everything a takeover could damage, runs the install, photographs it again, and diffs. The only allowance is klyrn-vm* in the two systemd lists. Every other artefact must be identical, with no allowances at all. Rule counters are stripped from both firewall rulesets, because a comparison that can never pass teaches nobody anything.

observe-safety.sh run, 11 September 2026 21 rows
  PASS  no package was installed, removed or upgraded
  PASS  no route changed
  PASS  no interface was added, removed or reconfigured
  PASS  no address moved
  PASS  the nftables ruleset is byte-identical
  PASS  the iptables ruleset is byte-identical
  PASS  the ip6tables ruleset is byte-identical
  PASS  no file under /etc/libvirt changed
  PASS  no domain was defined or undefined
  PASS  every domain is in the state it was in
  PASS  no domain definition changed
  PASS  no storage pool changed
  PASS  no volume was created, resized or removed
  PASS  no libvirt network changed
  PASS  no nwfilter was defined or removed
  PASS  no block device changed
  PASS  no LVM volume changed
  PASS  no forwarding or filtering sysctl changed
  PASS  no guest was restarted
  PASS  no service changed state
  PASS  no service was enabled or disabled

  KLYRN's own systemd lines added: 2 (expected: the agent's unit)
  systemd on-demand units a read activated: 1
  Differences that are not KLYRN's own: 0

  VERDICT: the observe install changed nothing on this host.

The probe was verified before the result was believed

A run with --negative-control changed the host on purpose (a dummy interface, an nftables table, a stopped vendor service, a restarted guest, a created volume, a line appended to /etc/libvirt/qemu.conf), and the same script reported 18 differences across eight of its rows, then undid all six changes. A run that reported nothing would have made every PASS above worthless. self-test then mutated each of the 21 artefacts in turn: 0 rows that cannot detect a change.

One honest side effect, reported rather than filtered

Reading the host's time synchronisation state with timedatectl show causes systemd to D-Bus-activate systemd-timedated.service. That is how systemd answers the question. It appears on its own line in the report (named, counted, and attributed to systemd rather than to KLYRN), because filtering it silently would turn a measurement into a reassurance.

The hole this closed first

Observe safety used to rest on one hand-maintained map of the operations that mutate, and the executor refused an operation that appeared in it. Six operations were in neither table (neither classified as mutating nor as read), so they were permitted on an observed hypervisor by omission. It is the failure mode nothing looks wrong for in review, because the wrong thing is the line that is not there. The two tables now have to partition the vocabulary exactly, and a test fails the build if any operation falls in neither or in both.

While both are installed

SolusVM stays authoritative. What KLYRN holds is a shadow.

Connect the panel and KLYRN reads your estate into staging tables: customers, locations, compute nodes, plans, storage, IP blocks, images, limit groups, SSH keys, virtual servers, snapshots and backups, and the source's own task list. Thirteen phases, each reporting a count that moves rather than a spinner. Six workers, never one per virtual machine, because the panel on the other end is serving somebody's customers. Nothing staged is a KLYRN resource.

A staged guest cannot be operated, because there is nothing to find

A staged guest has no row in the vms table, no id in KLYRN's id space and no node. Every VM handler begins by looking up a vms row. So start, shutdown, reboot, force-stop, reset, console, edit and delete do not fail a check: they find nothing. Twenty-seven of those calls were tried against a real staged estate, by source id, by UUID and by a KLYRN-looking integer: every one refused, the vms table still empty, and no task row written.

The client cannot write to your panel either

KLYRN's SolusVM 2 client has one request builder and it is hard-coded to GET. A test parses the package's syntax tree and fails the build if any other verb appears anywhere in it. The recommendation on the connection screen is still to narrow the credential at the source (a dedicated user, a role built only from get permissions, allowed IPs set to the controller), because a source-side restriction is the one that survives a bug in KLYRN.

The credential is a screen, not an environment variable.

The person who holds the API token is rarely the person with shell on the controller. An environment variable means somebody edits a unit file and restarts a service on their behalf; it is inherited by everything the controller spawns, readable in /proc, and it survives into a crash dump. So the token goes in at Migration → Connect a panel, and rotating it is the same screen.

  1. Proved before it is stored

    KLYRN calls the panel with the token first. A row whose token has never worked is a row that fails at the worst possible moment. Send test_only to check a credential and store nothing at all.

  2. Sealed, with the key outside the database

    AES-256-GCM, bound to the row's id as additional authenticated data so a ciphertext moved between rows will not open. The key is 32 random bytes in /etc/klyrn-vm/migration.key, mode 0600. A database copy that reaches a support inbox carries ciphertext and no key.

  3. Never returned, and never printed

    One function can open it, called only from the scan and the connection check, and its result goes straight into a type that redacts itself in every printing form. What an operator sees is four characters of tail behind bullets. Eleven endpoints, the audit log and the rotation refusal were searched byte-wise for the token and its twelve-character prefix: zero hits, with the database column asserted to be ciphertext.

Plain HTTP is permitted only to a private address given as a literal, and refused everywhere else, including to a private name. A great many SolusVM controllers genuinely live on a management network behind a self-signed certificate or none, and that is a different matter from a bearer token crossing the public internet.

Enrolling the host.

One command on the SolusVM node itself. --observe is the default and is written out here because a flag that matters should be visible in the shell history. --allow-controller-host permits enrolling the controller's own machine, which is how a lab runs both on one box.

on the hypervisor read-only
klyrn-vm node install \
  --controller https://ctl.example.com:8444 \
  --token <one-time bootstrap token> \
  --ca sha256:<pinned fingerprint> \
  --observe

There is a fourth flag, --install-packages, which installs the hypervisor packages in observe mode by explicit request. It exists so an operator can prepare a host they own. The safety acceptance above would report it as a violation, correctly.

Handoff is not implemented.

Read this part twice, because it is the one that decides whether this release is any use to you. KLYRN VM can sit beside SolusVM 2, read the estate and tell you what a takeover would involve. It cannot perform one. Nothing in this release freezes SolusVM, disables its agent, makes KLYRN authoritative, or touches a running guest.

  • Handoff, verification and finalisation are declared, gated and unimplemented. They return a typed error naming the preconditions they will require. The readiness gate that will one day refuse a bad handoff exists and works: run against the lab source it answers handoff_ready: false (8 checks, 4 passed, 27 of 27 guests blocked), and every failing check names what to do about it. Two of its thirteen conditions are hard-wired false in this release.
  • No real SolusVM estate has ever been connected. Every measurement on this page was taken against a fabricated SolusVM 2 built from the vendor's published response schemas, or against a disposable Ubuntu 24.04 fixture carrying libvirt, a bridge, an nftables rule, a storage pool, a running domain, two stand-in vendor services and an /etc/solusvm. No real SolusVM system has been contacted by this repository at any point, and the observe agent has not yet run on one.
  • The observe measurement covers the install, not the promotion. It says nothing about what happens after an operator promotes a node to managed, which is the moment KLYRN is supposed to start changing things. It also inspects no BIOS setting, RAID controller or BMC.
  • OpenVZ and Virtuozzo are out of scope, and their guests are never counted. A container compute resource is detected and named out of scope with the reason and the route. It is not adoptable and is not included in any number KLYRN reports as adoptable. Windows guests are out of scope too.
  • Any 802.1Q VLAN tag on a guest blocks that host. KLYRN VM creates no VLAN interfaces in this version, so the tag would be lost the first time KLYRN wrote the domain's definition. A machine type outside q35 and i440fx, a disk format that is not qcow2 or raw, a network disk, and a macvtap interface block for the same reason: KLYRN cannot manage them, so it refuses rather than preserving them by accident.
  • Per-VM XML patches under /usr/local/solus/patches cannot be read by anything. Neither the API nor the node agent can see the files, only their effect. The reconcile report names the directory to read by hand. It is the one gap nothing closes on its own, and it is here rather than absent for that reason.

What is worth doing today is the measurement: install the agent in observe mode on a host that matters, run the safety acceptance on it, and read the twenty-one rows against your own machine rather than against ours.