nix module and flake for declarative non-NixOS VMs
Find a file
Git Sagar 58a317f5d2 macOS Tahoe: working fully-offline unattended install
The base image (macos.images.tahoe.upstream) now installs and boots end to end
with no dependency on Apple's servers — proven on the KVM host: the finished
qcow2 boots standalone (OpenCore from its own ESP) to the macOS 26.6.2 loginwindow.

Install driving (vm-driver.py, screenshot + OCR over QMP):
- map the whole InstallAssistant.pkg as a raw disk and dd it byte-exact into the
  app as SharedSupport.dmg (it is a pkgdmg: xar + koly footer — the bare xar
  member fails startosinstall with "pkgdmg is missing a footer")
- offline install: no NIC + /etc/hosts blackhole of Apple install/verify
  endpoints so startosinstall's calls fail fast instead of hanging
  (SecureBootModel=Disabled allows the sealed-volume install offline)
- keep the recovery display awake with a tiny mouse jiggle; coarse settle
  fingerprint so the cursor is not seen as a change
- guest watchdog re-erases/retries a startosinstall attempt that stalls or runs
  too long (prepare is intermittently slow)
- disk-aware boot watchdog: QMP system_reset only when the screen is dark AND the
  disk is idle (never interrupts a slow-but-working boot); recovery-restart if a
  post-prepare reboot lands back on recovery
- detect the bright loginwindow and power the VM down (install complete); a
  black + disk-idle screen is treated as a completed halt
- copy OpenCore into the image ESP so it boots standalone

recovery.file pins a content-addressed local BaseSystem.dmg (Apple's CDN
load-balances Sequoia/Tahoe during the rollout). fetchRecovery retries to the
pinned hash when used instead.

Not yet done: .generalize (user creation) — the first-boot agent LaunchDaemon is
blocked by Ventura+ Background Task Management on headless boots; next step is
offline user injection from the agent pkg postinstall.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XsESshRCoBoUVWV9qKURUF
2026-09-10 13:33:38 -03:00
lib macOS Tahoe: working fully-offline unattended install 2026-09-10 13:33:38 -03:00
nixos macOS Tahoe VM images (OpenCore/QEMU), Apple-ID compatible, VNC 2026-09-10 13:33:38 -03:00
.env-export-vmix-cli-local vmix CLI, laptop images, SDL display 2026-05-23 21:56:51 -03:00
.gitignore fist commit - images lib in a working condition for debian 2024-05-23 16:33:38 +00:00
cli.nix macOS Tahoe VM images (OpenCore/QEMU), Apple-ID compatible, VNC 2026-09-10 13:33:38 -03:00
flake.lock vmix CLI, laptop images, SDL display 2026-05-23 21:56:51 -03:00
flake.nix macOS Tahoe VM images (OpenCore/QEMU), Apple-ID compatible, VNC 2026-09-10 13:33:38 -03:00
README.md add README with usage, architecture, and examples 2026-06-07 10:46:32 +05:30

vmix

Composable QEMU VM image building and orchestration for NixOS.

What it does

vmix provides:

  • Image building — reproducible Linux (Debian) and Windows qcow2 images via Nix derivations
  • NixOS module — declarative VM management with QEMU in network namespaces
  • CLI tool — build, copy-to-disk, and run images from the command line

Usage

As a flake input

# flake.nix
inputs.vmix.url = "git+https://git.sagar.ch/dotfiles/vmix.nix.git";

# In your NixOS configuration (or globally via serverFunctions)
imports = [ inputs.vmix.nixosModules.default ];

# Use vmixLib via overlay
nixpkgs.overlays = [ inputs.vmix.overlays.default ];
# Then: pkgs.vmixLib.linux, pkgs.vmixLib.windows, pkgs.vmixLib.network

As a CLI

# Enter dev shell
nix develop

# Build an image
vmix build --image windows.images.win10.laptop \
  --generalize username=User,password=secret,hostname=PC

# Write to local disk
vmix copy --image windows.images.win10.laptop \
  --generalize username=User,password=secret \
  --to-disk /dev/sda

# Write to remote disk (streamed via SSH + LZ4)
vmix copy --image windows.images.win10.laptop \
  --generalize username=User,password=secret \
  --to-remote-disk root@10.10.10.100:/dev/nvme0n1

# Boot a built image with QEMU
vmix run ./result --mem 8192 --smp 8 --ahci

Flake outputs

Output Description
overlays.default Nix overlay exposing pkgs.vmixLib
nixosModules.default NixOS module for declarative VM management
lib.x86_64-linux Library functions (images + network utilities)
packages.x86_64-linux.default vmix CLI tool
apps.x86_64-linux.default Runnable vmix app

Repository structure

flake.nix          # Flake entry point
module.nix         # NixOS module export
overlay.nix        # Nix overlay (vmixLib pinned to nixpkgs 25-11)
cli.nix            # CLI tool (build, copy, run)

lib/
  default.nix      # Exports: images (linux + windows) + network
  network.nix      # IPv4/CIDR utilities
  images/
    linux/         # Debian image building + customization
    windows/       # Windows image building + customization
      helpers/     # makeImage, customizeImage, makeWinISO, etc.
      templates/   # Registry tweaks, app installers, essentials
      drivers/     # VirtIO, AMD GPU drivers
      win10/       # Windows 10 LTSC images
      win11/       # Windows 11 images

nixos/
  default.nix      # NixOS module entry point
  networks/        # Network namespace management (LAN, WAN, macvtap)
  vms/             # VM lifecycle management (QEMU, tap devices, DHCP)

Image building

Windows pipeline

  1. makeImage — unattended install from upstream ISO via QEMU
  2. customizeImageFold — apply modular templates (registry, apps, drivers)
  3. generalize — sysprep + OOBE for deployment to real hardware

Linux pipeline

  1. Fetch upstream Debian cloud image
  2. customizeImageFold — apply templates via virt-customize

Key patterns

  • customizeImageFoldbuiltins.foldl' over templates for composable layered images
  • _vmixOsType — all images carry "linux" or "windows" metadata for auto-detection
  • Offline registry — Windows templates use ControlSet001 for offline virt-win-reg --merge

NixOS module

Declare VMs and networks:

vmix.namespaces."lab" = {
  networks.lan1 = {
    subnet = "10.99.1.0/24";
    dhcp.enable = true;
  };
  vms.myvm = {
    image = pkgs.vmixLib.linux.images.debian.v12.upstream;
    memory = 2048;
    cores = 2;
    interfaces.lan1 = { ip = "10.99.1.10"; };
    autostart = true;
  };
};

Features:

  • Network namespaces with WAN (veth), LAN (bridge + dnsmasq), macvtap
  • ACPI graceful shutdown via QMP socket
  • 9p shares with auto-created mount targets
  • Conditional macvtap service (only created when macvtaps are configured)