Adding machinectl support to microvm.nix
I use microvm.nix on my homelab server to run lightweight NixOS VMs. I wanted two basic host-side operations to feel native: machinectl should see the VMs, and SSH into them should not require remembering custom targets.
That meant adding machined registration for microvm.nix VMs and adding SSH-over-VSOCK support with a microvm -s helper.
I sent this upstream as microvm.nix PR #453.
What I wanted from the host
This is the kind of flow I wanted:
That sounds small, but it changes how the host feels. Instead of remembering unit names, helper scripts, or ad-hoc SSH targets, the VM becomes visible through the same systemd machinery that already manages it.
What microvm.nix needed
The SSH part is entirely in microvm.nix, not systemd. I added a guest option:
{
microvm.vsock.cid = 4242;
microvm.vsock.ssh.enable = true;
}
That enables sshd on VSOCK, so the host can connect without any guest network configuration. The host-side microvm -s command then targets either vsock/<CID> or the cloud-hypervisor socket mux, depending on the runner.
The machined part is separate. For that I added a registerWithMachined option, generated register/unregister scripts in the runner, and hooked them into the host units with ExecStartPost and ExecStopPost.
Why systemd 259 mattered
The old machined APIs are pretty rigid:
RegisterMachineRegisterMachineWithNetwork
They are enough to make a VM show up in machinectl, but not enough to attach much structured VM-specific metadata.
systemd 259 added RegisterMachineEx() and CreateMachineEx(). That is the interesting part of the release for this feature: machined can now accept extensible property sets instead of only the old fixed argument lists.
So the implementation in microvm.nix does this at runtime:
- use
RegisterMachineExon systemd 259+ - fall back to
RegisterMachineWithNetworkorRegisterMachineon older hosts
That keeps the feature usable on older systems while letting newer hosts store richer metadata.
What gets registered
On newer hosts, microvm.nix can register properties like:
IdServiceClassLeaderPIDRootDirectoryNetworkInterfacesVSockCIDSSHAddress
The VSockCID and SSHAddress properties are the interesting ones here. They make the machined entry useful for VM access, not just VM visibility.
SSH needed some cleanup too
While I was in there, I fixed microvm -s argument handling so SSH options and remote commands are split properly.
These two should do different things:
The first forwards SSH options. The second runs a remote command. That sounds obvious, but the CLI did not separate those cases cleanly before.
The edge cases
The imperative microvm@<name>.service template now tolerates missing microvm-register and microvm-unregister scripts. Without that, the generic template could fail for VMs that do not enable machined registration.
I also cleaned up the machineId handling and docs, because the same identifier flows through machined registration, SMBIOS UUIDs, and optionally guest /etc/machine-id. That is exactly the sort of thing that becomes confusing later if the semantics stay fuzzy.
Tests
I added focused regression tests for the machined registration path, microvm -s argument splitting, and the imperative template case.
checks/machined.nixcovers registration and verifiesVSockCID/SSHAddressmetadata where applicablechecks/microvm-command.nixchecksmicrovm -sargument splittingchecks/imperative-template.nixcovers the template-unit case without register scripts
Thanks
Thanks to the microvm.nix reviewers and maintainers for pushing this into better shape, especially @SuperSandro2000 and the earlier review comments around RegisterMachineEx, machineId, and the imperative-template edge cases.
Result
microvm.nix now gives me a much nicer host-side story: the VMs show up in machinectl, newer systemd versions can store useful VM metadata, and SSH over VSOCK feels like part of the tool instead of an extra trick I have to remember.