Skip to content
This repository was archived by the owner on Sep 20, 2026. It is now read-only.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

13 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

RevolunixOS Virtual Machines

A NixOS module that turns declarative machine and PCI definitions into host VFIO policy, libvirt configuration, generated domains, storage, device preparation and QEMU lifecycle hooks.

The same Nix data decides how each host function reaches vfio-pci, where it appears in the guest PCI tree, whether its option ROM is dumped, whether a resizable BAR resource is rewritten, and how the device is returned after shutdown.

Generated architecture

flowchart TD
  A["Nix machine definitions"] --> B["Boot and modprobe policy"]
  A --> C["libvirtd pre-start preparation"]
  A --> D["Generated libvirt XML"]
  A --> E["Per-VM QEMU hook"]
Loading

For every entry in virtualisation.virtualMachines.machines, the module can produce:

  • host kernel parameters and vfio, vfio_iommu_type1, and vfio_pci initrd modules;
  • modprobe rules built from the declared vendor IDs, host drivers, and selected binding strategy;
  • a qcow2 disk created on first libvirtd start;
  • a normal domain and a separate -setup installation domain;
  • a libvirt directory storage pool pointing at the configured ISO directory;
  • PCI <hostdev> XML with an explicit guest bus, slot, and function;
  • optional VGA option-ROM extraction and XML attachment;
  • optional resizable-BAR writes through resourceN_resize;
  • a QEMU hook containing the matching unbind, bind, rescan, display-manager, and Looking Glass operations.

Host virtualization configuration

Enabling the module currently applies the following host-wide configuration:

  • intel_iommu=on, amd_iommu=on, and iommu=pt;
  • kvm_amd.npt=1, kvm_amd.avic=1, and video=efifb:off;
  • vfio_iommu_type1.allow_unsafe_interrupts=1 and kvm.ignore_msrs=1;
  • libvirt with qemu_kvm, root-run QEMU, swtpm, and OVMF built with Secure Boot and TPM support;
  • virt-manager, looking-glass-client, rofi-vm, and the macos-dl helper in environment.systemPackages.

These are module-wide choices in the current source, not per-machine options.

VFIO binding strategies

The three booleans under functions[].blacklist select how a PCI function is prepared. If all three are false, the module uses the dynamic QEMU-hook path.

Configuration Generated behavior Release behavior
driver = true Adds options <driver> modeset=0 and blacklist <driver> for non-disk devices No explicit manual bind/unbind path is generated for this strategy
vfioPriority = true Adds the vendor ID to vfio-pci ids=... and emits softdep <driver> pre: vfio-pci Device remains assigned according to the boot-time policy
startUnload = true At libvirtd.preStart, unbinds the host driver and writes the ID to vfio-pci/new_id No QEMU-release path is emitted for this strategy
All false On QEMU prepare, unbinds the current driver and writes new_id On release, removes the ID, removes the PCI device, rescans the bus, and optionally restarts the display manager

The schema does not enforce mutual exclusion between these flags. Combinations therefore combine the generated snippets; choose them deliberately.

PCI model

passthrough.pcies models a physical slot and its individual functions:

passthrough = {
  enable = true;
  restartDm = true;

  pcies = [
    {
      disk = false;
      lines = {
        bus = "0b";
        slot = "00";
        vmBus = "09";

        functions = [
          {
            function = "0";
            vendor = "1002:0000"; # replace with the real vendor:device ID
            drivers = [ "amdgpu" ];

            blacklist = {
              driver = false;
              vfioPriority = true;
              startUnload = false;
            };

            fix = {
              rom = true;
              rebar.enable = false;
              rebar.resources = [];
            };
          }
        ];
      };
    }
  ];
};

The host address is assembled as 0000:<bus>:<slot>.<function>. The generated guest address uses vmBus while retaining the declared slot and function.

Normal PCI devices

When disk = false, each function becomes a managed libvirt PCI host device. If fix.rom is enabled and the build-time lspci probe identifies the function as VGA, the module:

  1. reads its ROM from sysfs once;
  2. stores it under /var/lib/libvirt/roms/pcie-<BDF>.rom;
  3. adds <rom bar="on" file="..."/> to that function's hostdev XML.

Passed-through controllers or disks

When disk = true, the generated hostdev receives <boot order="1"/>. This path is also inserted into the generic installation domain, allowing an entire controller or physical disk to participate in guest installation.

Resizable BAR

For every declared fix.rebar.resources entry, libvirtd pre-start writes the configured integer to:

/sys/bus/pci/devices/0000:<BDF>/resource<resource>_resize

The function is temporarily unbound from vfio-pci before the writes and bound again afterward. Resource indices and resize values are intentionally supplied by the machine configuration because they are device-specific.

Generated guest hardware

Windows and Linux template

The generic normal domain is generated with:

  • Q35 pc-q35-8.1 and OVMF pflash/NVRAM;
  • host-passthrough CPU topology with topoext;
  • shared memfd memory, required by Looking Glass;
  • Hyper-V enlightenments, a hidden KVM state, local-time clock and TPM 2.0;
  • a virtio network adapter, virtio inputs, virtio-serial, virtio-scsi, SATA, SPICE audio, watchdog and balloon device;
  • fifteen PCIe root ports plus a PCIe-to-PCI bridge, giving passed-through functions explicit guest attachment points;
  • a qcow2 system disk using cache='directsync' and discard='unmap';
  • QXL/SPICE when passthrough is disabled, or no emulated video adapter when it is enabled;
  • an optional 128 MiB ivshmem-plain Looking Glass device at guest bus 0x10.

The corresponding -setup domain adds the configured installation ISO. For os = "win11", it also attaches virtio-win.iso. The setup domain keeps a QXL/SPICE console and only injects PCI entries marked as disks; the normal domain receives the non-disk functions and Looking Glass definition.

For os = "linux", the generated libosinfo identifier changes to the Linux profile. Any other non-macos value currently falls back to the Windows 11 libosinfo identifier; only the exact win11 value adds the VirtIO driver ISO.

macOS template

os = "macos" selects separate normal and setup XML templates containing:

  • OpenCore and BaseSystem disks in addition to the guest disk;
  • custom OVMF code and variable files under /var/lib/libvirt/firmware/macos;
  • Apple SMC command-line data and an explicit Penryn/GenuineIntel CPU model;
  • a vmxnet3 network device, ICH9 USB controllers, serial console and guest agent channel;
  • no memory-balloon device;
  • normal-domain injection of non-disk PCI functions.

The installed macos-dl helper downloads the recovery media, OpenCore image, and firmware files used by those templates from kholia/OSX-KVM.

Disk and SSD handling

When hardware.disk.enable = true, libvirtd pre-start creates the qcow2 file if it does not already exist. hardware.disk.size is interpreted in GiB.

hardware.disk.ssdEmulation emits a QEMU override with rotation_rate = 1:

  • for the generic template, on the scsi0-0-0-0 device;
  • for macOS, on the three SATA aliases used by OpenCore, the guest disk, and BaseSystem.

Existing disks are never recreated or resized by the module.

Materialized libvirt state

During libvirtd.service pre-start, each machine definition materializes these paths:

Path Source
<hardware.disk.path>/<name>.qcow2 Created with qemu-img when absent
/var/lib/libvirt/roms/pcie-<BDF>.rom Dumped from sysfs for detected VGA functions when absent
/var/lib/libvirt/hooks/qemu.d/<name> Store-backed generated QEMU hook
/var/lib/libvirt/storage/ISO-<name>.xml Store-backed ISO pool XML
/var/lib/libvirt/qemu/<name>.xml Store-backed normal-domain XML
/var/lib/libvirt/qemu/<name>-setup.xml Store-backed installation-domain XML

The XML and hook files are symlinks into the Nix store. Editing them directly does not change their source; edit the Nix module or templates and rebuild.

Looking Glass lifecycle

With lookingGlass = true, the normal domain receives a fixed 128 MiB shared memory device. On the QEMU started operation, the generated hook changes /dev/shm/looking-glass ownership to <username>:libvirtd.

The module installs the client but does not configure the guest-side Looking Glass host or IVSHMEM driver.

Basic configuration

Add the module as a flake input:

inputs.revolunix-vms = {
  url = "github:RevolunixOS/module-virtual-machines";
  inputs.nixpkgs.follows = "nixpkgs";
};

Then import and configure it in the target NixOS system:

{
  imports = [ inputs.revolunix-vms.nixosModules.default ];

  virtualisation.virtualMachines = {
    enable = true;
    username = "your-user";
    vmFolderPath = "/home/your-user/VM";
    isoFolderPath = "/home/your-user/VM/ISO";

    machines = [
      {
        name = "win11";
        os = "win11";
        isoName = "Windows.iso";
        uuid = "";
        uuidSetup = "";
        lookingGlass = true;

        hardware = {
          cores = 6;
          threads = 2;
          memory = 16;
          disk = {
            enable = true;
            size = 256;
            path = "/home/your-user/VM/DISK";
            ssdEmulation = true;
          };
        };

        passthrough = {
          enable = false;
          restartDm = false;
          pcies = [];
        };
      }
    ];
  };
}

The module expects pkgs.rofi-vm to exist. The original RevolunixOS consumer passes the custom revolunixpkgs package set as pkgs; a vanilla Nixpkgs consumer must provide rofi-vm through an overlay or remove that package from the module.

Option reference

Option Type Default Effect in the current implementation
enable boolean false Enables all host, libvirt and generation logic
username string required Builds default paths, Samba ownership and Looking Glass ownership
sambaAccess.enable boolean false Exposes the user's home and /run/media/<user> as writable Samba shares
vmFolderPath string /home/<user>/VM Base for default disk and ISO paths
isoFolderPath string <vmFolderPath>/ISO Source directory of the generated ISO pool
machines[].name string win11 Domain name, disk filename and generated state paths
machines[].os string win11 Selects generic or macOS XML and OS-specific media
machines[].uuid string empty Generic normal-domain UUID; empty invokes uuidgen during the Nix build
machines[].uuidSetup string empty Generic setup-domain UUID; empty invokes uuidgen during the Nix build
machines[].isoName string win11.iso Installation ISO filename
machines[].lookingGlass boolean true Adds IVSHMEM and the ownership hook
hardware.cores integer 2 CPU cores in the generated topology
hardware.threads integer 2 Threads per core; vCPU count is cores × threads
hardware.memory integer 8 Guest memory in GiB
hardware.disk.enable boolean true Generates and attaches the qcow2 disk
hardware.disk.size integer 128 Initial qcow2 size in GiB
hardware.disk.path string <vmFolderPath>/DISK Disk directory
hardware.disk.ssdEmulation boolean true Emits the rotation-rate override
passthrough.enable boolean false Enables hostdev XML and device-management logic
passthrough.restartDm boolean false Restarts display-manager.service during the QEMU prepare and release hook operations

Current implementation contracts

These are concrete properties of the current source and matter when adapting it to a new host:

  • src/qemuHook.sh currently filters lifecycle actions with OBJECT == "win11". Domains with another name receive generated files, but the prepare/started/release body does not run until that condition is made name-aware.
  • The generic XML contains fixed SMBIOS strings and a fixed MAC address. The macOS templates also contain fixed MAC addresses and fixed UUIDs; configured uuid values are not substituted into the macOS XML.
  • The ISO pool XML has a fixed pool name and UUID even though one symlink is generated per machine.
  • VGA detection executes lspci through a Nix build-time derivation. ROM injection therefore depends on the build environment seeing the host PCI device.
  • Empty UUID options call uuidgen from a build-time derivation. Set explicit UUIDs when stable identity across rebuilds and caches is required.
  • The binding flags are independent booleans and have no assertions preventing contradictory combinations or incomplete PCI declarations.
  • The macOS templates insert normal non-disk passthrough functions but do not contain the pciesDiskXml placeholder used by the generic templates.
  • The flake pins NixOS 24.05, while the XML pins the Q35 8.1 machine type and several /run/libvirt/nix-* paths.
  • QEMU runs as root and unsafe VFIO interrupts are enabled globally.

These constraints are documented so downstream configurations can decide which values to preserve, parameterize or remove.

Repository layout

flake.nix              exports nixosModules.virtual-machine and .default
default.nix            option schema and all generated host/guest configuration
src/template.xml       generic normal domain
src/template-setup.xml generic installation domain
src/macOS.xml          macOS normal domain
src/macOS-setup.xml    macOS installation domain
src/ISO.xml            libvirt directory-pool template
src/qemuHook.sh        lifecycle-hook template

License

See LICENSE.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages