No description
  • Nix 98.5%
  • Shell 1.5%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
vnprc d43b2c6dd1 Authorize root for guest closure delivery
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.
2026-10-22 00:23:55 -04:00
checks Authorize root for guest closure delivery 2026-10-22 00:23:55 -04:00
docs Retire guest HTTPS credential configuration 2026-10-01 12:45:57 -04:00
hooks initial public release 2026-07-09 18:21:20 -04:00
modules Authorize root for guest closure delivery 2026-10-22 00:23:55 -04:00
.gitignore initial public release 2026-07-09 18:21:20 -04:00
disk.nix initial public release 2026-07-09 18:21:20 -04:00
flake.lock flake.lock: update agenix/home-manager, agenix/home-manager/nixpkgs, disko, home-manager, nixpkgs, nixpkgs-unstable 2026-09-23 14:11:47 -04:00
flake.nix Remove the microvm guest runtime 2026-09-08 11:04:16 +00:00
LICENSE Add LICENSE (GPL-3.0-or-later) 2026-07-17 05:40:41 +00:00
README.md Authorize root for guest closure delivery 2026-10-22 00:23:55 -04:00
setup.sh initial public release 2026-07-09 18:21:20 -04:00

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 (profiles repo)
  • per-VM secrets and SSH keys (profiles repo)
  • host NixOS config and provisioning scripts (nexus repo)
  • 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.hostPlatform defaults to x86_64-linux
  • systemd-boot + EFI
  • networkmanager (hostname-mode=none)
  • fix-home-ownership activation 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 passwordless
  • users.root with the same host SSH key(s) authorized
  • nix.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.guestRuntimes exclusivity 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 = true
  • systemd.user.tmpfiles.rules — creates the %t/gnupg socket dir at login (sshd's RemoteForward target)
  • programs.ssh.enable = true + enableDefaultConfig = false
  • profiles — consumer VM configs and profile modules (consumes this repo as a flake input)
  • nexus — host NixOS config and the host-side provisioning scripts
  • secrets — identity and credential-schema examples consumed by profiles

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.
  • profiles pins all flake inputs through vm via follows.

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>:

  1. creates the libvirt VM (new-vm, booting the installer ISO);
  2. runs nixos-anywhere (disko + install), injecting the pre-generated VM SSH host key via --extra-files so agenix can decrypt secrets on first boot;
  3. 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.