Remove hardcoded CPU architecture values #1

Open
opened 2026-07-09 23:37:22 +01:00 by vnprc-agent · 2 comments

User story: So that I can run the VM framework on supported CPU architectures, shared modules should derive platform and virtualization settings instead of assuming one architecture family.

Part of the "Profiles & VM archetypes" arc.


The vm framework hardcodes x86_64-linux (and Intel-specific KVM) in several
places. This blocks the aarch64-linux host support the framework otherwise
advertises, and bakes CPU assumptions into shared modules. These should be
parameterized so the framework evaluates and builds for any supported system.

Hardcoded values

  • flake.nix:25apps.x86_64-linux.agenix pins the app output to a single system.
  • flake.nix:27program = "${agenix.packages.x86_64-linux.agenix}/bin/agenix" pins the package system.
  • modules/qemu-guest.nix:18nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux" bakes an x86_64 default for the guest platform.
  • modules/qemu-guest.nix:16boot.kernelModules = [ "kvm-intel" ] assumes an Intel x86 host (AMD needs kvm-amd; aarch64 needs neither).

Suggested approach

  • Iterate the agenix app over supported systems (e.g. flake-utils.lib.eachDefaultSystem, or genAttrs over a systems list) instead of hardcoding x86_64-linux.
  • Take the guest nixpkgs.hostPlatform from consumer/inventory data (the machine's platform) rather than a baked default.
  • Make the KVM kernel module platform/vendor-aware, or move it to the consumer's hardware configuration where the CPU vendor is known.
  • Consider adding a no-hardcoded-arch flake check (as nexus has) that greps framework modules for literal Nix system strings, to prevent regressions.

README.md references x86_64-linux as well and should follow once the code is parameterized.

**User story:** So that I can run the VM framework on supported CPU architectures, shared modules should derive platform and virtualization settings instead of assuming one architecture family. _Part of the "Profiles & VM archetypes" arc._ --- The `vm` framework hardcodes `x86_64-linux` (and Intel-specific KVM) in several places. This blocks the `aarch64-linux` host support the framework otherwise advertises, and bakes CPU assumptions into shared modules. These should be parameterized so the framework evaluates and builds for any supported system. ## Hardcoded values - `flake.nix:25` — `apps.x86_64-linux.agenix` pins the app output to a single system. - `flake.nix:27` — `program = "${agenix.packages.x86_64-linux.agenix}/bin/agenix"` pins the package system. - `modules/qemu-guest.nix:18` — `nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"` bakes an x86_64 default for the guest platform. - `modules/qemu-guest.nix:16` — `boot.kernelModules = [ "kvm-intel" ]` assumes an Intel x86 host (AMD needs `kvm-amd`; `aarch64` needs neither). ## Suggested approach - Iterate the `agenix` app over supported systems (e.g. `flake-utils.lib.eachDefaultSystem`, or `genAttrs` over a systems list) instead of hardcoding `x86_64-linux`. - Take the guest `nixpkgs.hostPlatform` from consumer/inventory data (the machine's `platform`) rather than a baked default. - Make the KVM kernel module platform/vendor-aware, or move it to the consumer's hardware configuration where the CPU vendor is known. - Consider adding a `no-hardcoded-arch` flake check (as `nexus` has) that greps framework modules for literal Nix system strings, to prevent regressions. `README.md` references `x86_64-linux` as well and should follow once the code is parameterized.
Author

Additional scan detail for the inventory-owned machine platform rule:

Why it matters

Machine platform facts are selected by inventory and passed through the deploy/archetypes builder into nixosSystem { system = platform; }. A shared VM framework module should not install its own machine platform default, because that duplicates the same fact in a lower layer and can silently override or mask inventory-driven platform selection.

The agenix app key is a related host-tool surface rather than necessarily the primary machine-platform violation. A future hardcoded-architecture guard should decide how to handle host-tool app keys explicitly: generate them from supported tool platforms, allowlist them, or document the exception.

Exact grep hits

origin/master:modules/qemu-guest.nix:18:  nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
origin/master:flake.nix:25:    apps.x86_64-linux.agenix = {

Acceptance

  • modules/qemu-guest.nix no longer sets nixpkgs.hostPlatform to a machine platform default.
  • VM systems rely on the inventory-driven builder call that passes platform to nixosSystem { system = platform; }.
  • The host-tool app surface is handled deliberately: generated, allowlisted, or documented as an exception before any future hardcoded-architecture guard treats it as a violation.
  • A regression check or grep catches machine platform string literals in framework modules outside intentional examples or documented exceptions.
Additional scan detail for the inventory-owned machine platform rule: ## Why it matters Machine platform facts are selected by inventory and passed through the deploy/archetypes builder into `nixosSystem { system = platform; }`. A shared VM framework module should not install its own machine platform default, because that duplicates the same fact in a lower layer and can silently override or mask inventory-driven platform selection. The agenix app key is a related host-tool surface rather than necessarily the primary machine-platform violation. A future hardcoded-architecture guard should decide how to handle host-tool app keys explicitly: generate them from supported tool platforms, allowlist them, or document the exception. ## Exact grep hits ```text origin/master:modules/qemu-guest.nix:18: nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; origin/master:flake.nix:25: apps.x86_64-linux.agenix = { ``` ## Acceptance - `modules/qemu-guest.nix` no longer sets `nixpkgs.hostPlatform` to a machine platform default. - VM systems rely on the inventory-driven builder call that passes `platform` to `nixosSystem { system = platform; }`. - The host-tool app surface is handled deliberately: generated, allowlisted, or documented as an exception before any future hardcoded-architecture guard treats it as a violation. - A regression check or grep catches machine platform string literals in framework modules outside intentional examples or documented exceptions.
Member

Investigated this while reviewing the VM isolation model. Three findings sharpen the issue, and one changes the suggested approach.

The hostPlatform default does override inventory, not just "can"

The existing comment says the module default "can silently override or mask inventory-driven platform selection." It does, on every dev and privacy VM, unconditionally. Measured against the pinned nixpkgs b6018f87da91d19d0ab4cf979885689b469cdd41 with inventory declaring aarch64-linux:

configuration resulting pkgs.stdenv.hostPlatform.system
current code, modules/qemu-guest.nix:18 present x86_64-linux
line deleted aarch64-linux
platform passed explicitly aarch64-linux

No warnings, no failed assertions in the wrong case. The mechanism: nixos/modules/misc/nixpkgs.nix:74 branches on opt.hostPlatform.isDefined, which is true for a definition at any priority including lib.mkDefault, and that branch ignores nixpkgs.system entirely. So the builder's nixosSystem { system = platform; } in allod/archetypes (flake.nix:173, :219) is silently discarded. nixpkgs' own platform-conflict assertion (misc/nixpkgs.nix:395) does not fire because it keys off nixpkgs.localSystem's priority, and nixos/lib/eval-config.nix:55 defines nixpkgs.system, never localSystem.

This is a principle 11 violation specifically — the two facts disagree inside one configuration and nothing says so.

kvm-intel should be deleted, not made vendor-aware

The suggested approach offers "make the KVM kernel module platform/vendor-aware, or move it to the consumer's hardware configuration." Deleting it outright is better, for three reasons:

  1. Nothing consumes it. kvm_intel is the host-side VT-x backend; inside a guest it only provides /dev/kvm under nested virt. No guest module sets any virtualisation.* option, no guest package set contains qemu/libvirt/podman, and nexus/scripts/new-vm:120-129 passes no --cpu, so nested virt is never requested. The only correct occurrence in the tree is allod/inventory flake.nix:48, inside machines.nexus.hardware — a bare-metal fact in the fact-owning repo, describing the host that actually runs libvirtd.
  2. It is already a silent no-op on every host, including this one. boot.kernelModules failures are swallowed: nixos/modules/system/boot/kernel.nix:455 sets SuccessExitStatus = "0 1" on systemd-modules-load with the comment that these are "nice to have but not required." There is no build-time existence check either. So the line has zero observable effect today and would have zero effect on an AMD or aarch64 host — it is noise, not a live bug.
  3. Deriving it would mean inventing a fact. hostPlatform is a platform fact inventory already owns and asserts. CPU vendor is not — inventory carries no such field, and adding one to feed a module with no consumer is duplication in the wrong direction.

Deleting does not remove kvm_intel.ko from the system; boot.kernelModules only controls boot-time autoload, and the module tree is unaffected. If nested virt ever becomes a real requirement it can be added then, driven by an actual use case.

Nothing anywhere would catch a regression

allod/vm exposes no checks output at all (flake.nix:24-34 is apps, nixosModules, homeModules), which is what let the hardcode in. On the consumer side, none of the eight checks under allod/archetypes flake.nix:370 reads a platform, and neither do the two in allod/inventoryvmSpecsJson deliberately omits platform (inventory/flake.nix:94-97), so it is invisible to vm-specs-json and to the provisioning scripts.

The no-hardcoded-arch check suggested above already exists one repo over, at nexus/flake.nix:77-90, and would have caught modules/qemu-guest.nix:18 on day one. Worth lifting more or less verbatim.

Fix shape and sequencing

modules/qemu-guest.nix:1 should take platform as a required argument and set nixpkgs.hostPlatform = platform with no lib.mkDefault, so a second definition is a module-system conflict rather than a silent shadow, and omitting the argument is an eval error naming the missing fact. The precedent is already in the file: username is requested at :1 and supplied via specialArgs. flake.nix:30 needs no change — specialArgs reach imported modules through the { ... }: wrapper.

The consumer half is now tracked as issue allod/archetypes#14: passing platform in specialArgs at flake.nix:174 and :220, plus a coherence check comparing the built system to machines.<name>.platform with a mutation case. archetypes pins vm by flake input, so the order is (1) allod/archetypes#14 specialArgs, inert alone; (2) this issue; (3) the input bump plus the check. Reversing (1) and (2) breaks evaluation at the bump.

Note that mkHypervisor (allod/archetypes flake.nix:233) does not import this module and already derives its platform correctly — only dev and privacy VMs are affected.

Two adjacent cleanups in the same file

  • modules/qemu-guest.nix:15 lists virtio_pci and virtio_blk, both already supplied by the imported modulesPath + "/profiles/qemu-guest.nix" (nixpkgs nixos/modules/profiles/qemu-guest.nix:8,11). The remaining three are arch-generic and already fail loud via boot.initrd.allowMissingModules = false, so this is a dedupe, not a portability fix.
  • modules/qemu-guest.nix:66 hardcodes system.stateVersion = "25.11" in a framework module. That is a per-machine install-time fact, so a machine provisioned after the next release silently inherits 25.11. Not architecture, and not for this issue — raising it separately if it is worth tracking.

What this does not fix

Making the platform correct and loud does not make aarch64 provisionable. nexus/scripts/new-vm:120-129 passes no --arch and cannot see platform at all, allod/archetypes flake.nix:51-52 still forbids a heterogeneous fleet, and provision-vm-from-host and rebuild-vm-from-host build the closure on nexus, so a genuinely non-native machine would need binfmt or a remote builder. nexus/README.md:45-49 already describes aarch64 as exposed-but-unvalidated; that stays true after this lands.

Investigated this while reviewing the VM isolation model. Three findings sharpen the issue, and one changes the suggested approach. ## The `hostPlatform` default does override inventory, not just "can" The existing comment says the module default "can silently override or mask inventory-driven platform selection." It does, on every dev and privacy VM, unconditionally. Measured against the pinned nixpkgs `b6018f87da91d19d0ab4cf979885689b469cdd41` with inventory declaring `aarch64-linux`: | configuration | resulting `pkgs.stdenv.hostPlatform.system` | |---|---| | current code, `modules/qemu-guest.nix:18` present | `x86_64-linux` | | line deleted | `aarch64-linux` | | `platform` passed explicitly | `aarch64-linux` | No warnings, no failed assertions in the wrong case. The mechanism: `nixos/modules/misc/nixpkgs.nix:74` branches on `opt.hostPlatform.isDefined`, which is true for a definition at *any* priority including `lib.mkDefault`, and that branch ignores `nixpkgs.system` entirely. So the builder's `nixosSystem { system = platform; }` in `allod/archetypes` (`flake.nix:173`, `:219`) is silently discarded. nixpkgs' own platform-conflict assertion (`misc/nixpkgs.nix:395`) does not fire because it keys off `nixpkgs.localSystem`'s priority, and `nixos/lib/eval-config.nix:55` defines `nixpkgs.system`, never `localSystem`. This is a principle 11 violation specifically — the two facts disagree inside one configuration and nothing says so. ## `kvm-intel` should be deleted, not made vendor-aware The suggested approach offers "make the KVM kernel module platform/vendor-aware, or move it to the consumer's hardware configuration." Deleting it outright is better, for three reasons: 1. **Nothing consumes it.** `kvm_intel` is the host-side VT-x backend; inside a guest it only provides `/dev/kvm` under nested virt. No guest module sets any `virtualisation.*` option, no guest package set contains qemu/libvirt/podman, and `nexus/scripts/new-vm:120-129` passes no `--cpu`, so nested virt is never requested. The only correct occurrence in the tree is `allod/inventory` `flake.nix:48`, inside `machines.nexus.hardware` — a bare-metal fact in the fact-owning repo, describing the host that actually runs libvirtd. 2. **It is already a silent no-op on every host, including this one.** `boot.kernelModules` failures are swallowed: `nixos/modules/system/boot/kernel.nix:455` sets `SuccessExitStatus = "0 1"` on `systemd-modules-load` with the comment that these are "nice to have but not required." There is no build-time existence check either. So the line has zero observable effect today and would have zero effect on an AMD or aarch64 host — it is noise, not a live bug. 3. **Deriving it would mean inventing a fact.** `hostPlatform` is a platform fact inventory already owns and asserts. CPU vendor is not — inventory carries no such field, and adding one to feed a module with no consumer is duplication in the wrong direction. Deleting does not remove `kvm_intel.ko` from the system; `boot.kernelModules` only controls boot-time autoload, and the module tree is unaffected. If nested virt ever becomes a real requirement it can be added then, driven by an actual use case. ## Nothing anywhere would catch a regression `allod/vm` exposes no `checks` output at all (`flake.nix:24-34` is `apps`, `nixosModules`, `homeModules`), which is what let the hardcode in. On the consumer side, none of the eight checks under `allod/archetypes` `flake.nix:370` reads a platform, and neither do the two in `allod/inventory` — `vmSpecsJson` deliberately omits `platform` (`inventory/flake.nix:94-97`), so it is invisible to `vm-specs-json` and to the provisioning scripts. The `no-hardcoded-arch` check suggested above already exists one repo over, at `nexus/flake.nix:77-90`, and would have caught `modules/qemu-guest.nix:18` on day one. Worth lifting more or less verbatim. ## Fix shape and sequencing `modules/qemu-guest.nix:1` should take `platform` as a *required* argument and set `nixpkgs.hostPlatform = platform` with **no** `lib.mkDefault`, so a second definition is a module-system conflict rather than a silent shadow, and omitting the argument is an eval error naming the missing fact. The precedent is already in the file: `username` is requested at `:1` and supplied via `specialArgs`. `flake.nix:30` needs no change — `specialArgs` reach imported modules through the `{ ... }:` wrapper. The consumer half is now tracked as issue `allod/archetypes#14`: passing `platform` in `specialArgs` at `flake.nix:174` and `:220`, plus a coherence check comparing the built system to `machines.<name>.platform` with a mutation case. `archetypes` pins `vm` by flake input, so the order is (1) `allod/archetypes#14` `specialArgs`, inert alone; (2) this issue; (3) the input bump plus the check. Reversing (1) and (2) breaks evaluation at the bump. Note that `mkHypervisor` (`allod/archetypes` `flake.nix:233`) does not import this module and already derives its platform correctly — only dev and privacy VMs are affected. ## Two adjacent cleanups in the same file - `modules/qemu-guest.nix:15` lists `virtio_pci` and `virtio_blk`, both already supplied by the imported `modulesPath + "/profiles/qemu-guest.nix"` (nixpkgs `nixos/modules/profiles/qemu-guest.nix:8,11`). The remaining three are arch-generic and already fail loud via `boot.initrd.allowMissingModules = false`, so this is a dedupe, not a portability fix. - `modules/qemu-guest.nix:66` hardcodes `system.stateVersion = "25.11"` in a framework module. That is a per-machine install-time fact, so a machine provisioned after the next release silently inherits `25.11`. Not architecture, and not for this issue — raising it separately if it is worth tracking. ## What this does not fix Making the platform correct and loud does not make `aarch64` provisionable. `nexus/scripts/new-vm:120-129` passes no `--arch` and cannot see `platform` at all, `allod/archetypes` `flake.nix:51-52` still forbids a heterogeneous fleet, and `provision-vm-from-host` and `rebuild-vm-from-host` build the closure on nexus, so a genuinely non-native machine would need binfmt or a remote builder. `nexus/README.md:45-49` already describes aarch64 as exposed-but-unvalidated; that stays true after this lands.
Sign in to join this conversation.
No description provided.