How nsl works¶
Your ordinary machines run as systemd-nspawn containers inside one shared VM. Each isolated machine gets a VM of its own. On the host, nsl uses systemd user units that start and stop with the VM, so there's no separate host daemon to manage.
flowchart LR
CLI[nsl CLI] --> Unit[VM user unit]
Unit --> VM[vmspawn + QEMU/KVM]
VMImage[Signed VM image] --> Root[Replaceable root]
Root --> VM
Data[Data disk: machines + state] --> VM
Cache[Verified image cache] -->|read-only virtiofs| VM
Home[Home, /run/media/USER, /mnt] -->|virtiofs /mnt/host| VM
CLI -->|vsock SSH| Agent[nsl agent]
Agent --> Machines[nspawn machines]
VM --> Ports[Loopback forwarding]
Machines --> Waypipe[Waypipe per machine]
Waypipe --> Desktop[Host Wayland desktop]
Terms¶
- Shared VM: the single virtual machine that runs all ordinary machines. Isolated machines each have their own VM.
- Machine image: a distro root filesystem used to create a machine with its own packages, services and guest home. Updating that image does not change existing machines.
- Default machine: the machine that bare
nslandnsl runenter when you omit-m NAME. The first machine you create or import becomes the default;nsl default NAMEchanges it.
The VM¶
The shared VM boots the signed nsl VM image with systemd-vmspawn and QEMU/KVM, under a user unit named nsl-UID-vm-ID.service. It has two disks:
- The root, a copy of the cached VM image. It holds no user state, so
nsl updateandnsl recovercan replace it at the next start. - The data disk, a sparse file with a btrfs filesystem. It holds every machine and the VM's own state: its identity, its SSH host keys and the machine records.
The VM starts when you first need it and stops when idle. By default it can use half the host's memory and every host CPU, much like WSL. You can change those limits in nsl.conf. The machines share the VM's resources, so adding a machine doesn't reserve another VM's worth of memory.
Launching doesn't need root. nsl opens /dev/kvm and /dev/vhost-vsock through your existing kvm membership, inside an unprivileged user namespace, and passes them to vmspawn. nsl doesn't change host permissions, groups, packages or sudoers.
Machines¶
Each machine is a btrfs subvolume on the data disk, created from a signed machine image and run by systemd-nspawn. Machines use the VM's kernel, network namespace and resolver. In the shared VM they also bind /mnt/host, your host's shared files.
Once the images are cached, creation needs no network. The VM imports the image from the read-only cache, then sets the time zone, hostname, account, sudo rule and nspawn settings for the new machine. Distro-specific setup is already in the image; the host and VM don't need separate creation logic for each distro.
The agent¶
The CLI reaches the VM over SSH on vsock, where the VM's sshd accepts only nsl's key and only runs the nsl agent. Before doing anything, the CLI checks the VM's identity: its ID, your UID and GID, its role, and the image's protocol and architecture. It checks this even when the VM was already running.
The agent runs each command as a transient systemd unit in the machine, with a PAM login session for the machine account. Commands run with --root have no PAM session. It passes arguments literally, keeps streams separate or uses a terminal, and returns the exit status, with 128+N for a command killed by signal N.
Host integration¶
| Mechanism | How |
|---|---|
Files at /mnt/host |
virtiofs shares, run as your user |
| Ports | A forwarder unit per VM, nsl-UID-vm-ID-ports.service, polling the agent once a second |
| Windows | A desktop unit per machine, running Waypipe between the host and the machine |
| Links and files | A per-machine broker, reached by nsl-open over Varlink |
| Editors | nsl _ssh NAME, which runs sshd -i in the machine through the agent |
Idle stop¶
The VM makes both idle decisions, so the host needs no background process. A machine with no nsl sessions, windows or recent requests stops after idle_timeout. The VM powers off 60 seconds after its last machine stops and its last request ends. A command that arrives as the VM powers off waits for it to stop, then starts it again.
Isolated machines¶
An isolated machine gets its own VM from the same image and launcher, created and removed with the machine. That VM has no shares, no desktop session and no broker, and runs no other machine. See isolated machines.
Design documents¶
For the implementation details and the reasons behind these choices, see the architecture overview ↗, the CLI contract ↗ and the decision records ↗.