- Nix 98.5%
- Shell 1.5%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
Dropping the login user from trusted-users (previous commit) breaks allod/nexus rebuild-vm: nixos-rebuild copies the closure to the guest as the login user, and an unsigned import from a now-untrusted user is refused under require-sigs. Root gets the same authorized keys and PermitRootLogin = "prohibit-password" so the hypervisor can deliver the closure as root instead; authority is unchanged, since the hypervisor already holds passwordless sudo there through that user. Both properties are asserted, with a sabotage fixture pair. |
||
| checks | ||
| docs | ||
| hooks | ||
| modules | ||
| .gitignore | ||
| disk.nix | ||
| flake.lock | ||
| flake.nix | ||
| LICENSE | ||
| README.md | ||
| setup.sh | ||
vm
NixOS framework for dev VMs. Exports the shared libvirt guest module, the
shared Home Manager module, and the agenix app. Per-VM profiles (host configs,
secrets, keys) live in the companion profiles repo.
Repo purpose
This repo owns:
- the shared guest base (
modules/guest-base.nix) — account, sudo, sshd, Nix and git settings - the disk-installed libvirt guest (
nixosModules.qemuGuest) — disk layout + boot - shared Home Manager module (
homeModules.base) — GPG and SSH base config - the agenix app re-export (
apps.x86_64-linux.agenix)
This repo does not own:
- per-VM NixOS configs (
profilesrepo) - per-VM secrets and SSH keys (
profilesrepo) - host NixOS config and provisioning scripts (
nexusrepo) - which machine composes the guest, its credentials, or its addressing (
inventory,archetypes,nexus) - editor and other user preferences supplied by a consumer profile
Exported outputs
| Output | Type | Description |
|---|---|---|
nixosModules.qemuGuest |
NixOS module | Disk-installed libvirt guest: QEMU guest profile, disko disk layout, systemd-boot, NetworkManager, plus the shared base |
homeModules.base |
Home Manager module | GPG base config, gnupg socket tmpfile, SSH enable |
apps.x86_64-linux.agenix |
app | re-exports the agenix CLI |
checks.<system>.guest-module-contracts |
derivation | Reads the guest's generated artefacts and rejects every contract mutation |
Runtime
qemuGuest is the one guest runtime module: the disko GPT layout, systemd-boot,
nixos-anywhere provisioning and guest-local agenix. Composing the shared base
without it is an evaluation error rather than a silent default: the runtime
module appends its own name to the internal allod.vm.guestRuntimes list, and
the shared base asserts that exactly one is present.
Which machine composes it is not decided here. inventory owns the runtime
fact and archetypes selects the module from it.
nixosModules.qemuGuest
Includes:
disko.nixosModules.disko+./disk.nix— GPT layout, 512M EF00 ESP on/boot, ext4 on/modules/guest-base.nix(below)- QEMU guest kernel modules;
nixpkgs.hostPlatformdefaults tox86_64-linux systemd-boot+ EFInetworkmanager(hostname-mode=none)fix-home-ownershipactivation script — re-chowns the home dir after the nixos-anywhere bootstrap
modules/guest-base.nix
The runtime-independent half of the guest, not exported on its own:
openssh(StreamLocalBindUnlink, no password auth,PermitRootLogin = "prohibit-password")users.${username}with host SSH key(s) authorized, sudo wheel passwordlessusers.rootwith the same host SSH key(s) authorizednix.settings.experimental-features;nix.settings.trusted-users = [ "root" ]— the guest login user is not a trusted nix user, so it cannot redirect the daemon's substituters or turn the build sandbox off for its own builds (allod/vm#12)system.stateVersion = "25.11"- the
allod.vm.guestRuntimesexclusivity assertion, the trusted-users assertion, and the root-ssh-access assertion
The guest module does not enable unfree packages. Consumer profiles that ship
unfree tools (e.g. claude-code, codex) set nixpkgs.config.allowUnfree = true
themselves.
The hypervisor reaches a guest as root for closure delivery (allod/nexus
rebuild-vm), because the login user is not a trusted nix user and an
import signed only by that user's build would be refused under
require-sigs. This changes no authority: the hypervisor already holds
passwordless sudo on the guest through that same user.
Checks
nix flake check --print-build-logs
nix build .#checks.x86_64-linux.guest-module-contracts --print-build-logs
guest-module-contracts reads generated artefacts rather than module source:
the guest's /etc/fstab and bootspec, its boot-loader installer, and disko's
partitioning script. The exclusivity assertion is paired with a sabotage
fixture that must fail: the shared base composed with no runtime module. The
trusted-users assertion has its own sabotage fixture: the guest user forced
back into trusted-users. The root-ssh-access assertion has two: root's
authorized keys cleared, and PermitRootLogin forced to "yes".
homeModules.base
Includes:
programs.gpg.enable = true+settings.no-autostart = truesystemd.user.tmpfiles.rules— creates the%t/gnupgsocket dir at login (sshd's RemoteForward target)programs.ssh.enable = true+enableDefaultConfig = false
Related repos
profiles— consumer VM configs and profile modules (consumes this repo as a flake input)nexus— host NixOS config and the host-side provisioning scriptssecrets— identity and credential-schema examples consumed byprofiles
Cloning
git clone https://forge.anarch.diy/allod/vm.git
Architecture
- The host machine runs libvirt/QEMU. Dev VMs are the main development environments.
- VMs are accessed over SSH from the host. No shared folders.
- All shared VM configuration (disk layout, system boilerplate, home boilerplate) lives here.
- Per-VM configuration (packages, forge identity, secrets) lives in
profiles. profilespins all flake inputs throughvmviafollows.
Provisioning
Provisioning is host-side and owned by nexus. Its commands are built into the
nexus provisioning-scripts package and installed on the host through
nexus.homeModules.host, so they run from the Nix store — editing a checkout
does not change the installed command until the host is rebuilt.
At a high level, provision-vm <vm-name>:
- creates the libvirt VM (
new-vm, booting the installer ISO); - runs
nixos-anywhere(disko + install), injecting the pre-generated VM SSH host key via--extra-filesso agenix can decrypt secrets on first boot; - boots the VM from disk and bootstraps it — clones the workspace and runs
nixos-rebuild switch --flake .../profiles#<vm-name>.
VM specs (sizing, networking, repo lists, rebuild policy) come from inventory.
See the nexus docs for the full provisioning and host-key/token lifecycle.
SSH keys
| Key | Location | Purpose |
|---|---|---|
host |
~/.ssh/host on host |
Host identity: SSH to VMs; age decryption of all secrets |
<vm-forge-key> |
~/.ssh/<vm-forge-key> on host |
Dev VM forge SSH key; copied to VM during bootstrap |
<vm-name> host key |
/etc/ssh/<vm-name> on VM |
VM SSH host key; pre-generated, injected by nixos-anywhere before first boot |
Adding a new VM
See the profiles README for the full guide. At the framework level, adding a
new VM requires zero changes to this repo — all per-VM configuration goes in
profiles.