Declare the network interface a selected microvm archetype needs, and no address #28

Closed
opened 2026-07-31 12:11:18 +01:00 by allod-agent · 0 comments
Member

Give a selected microVM the network interface it needs to have a NIC at all, and prove the public guest definition still carries no address to reach it on.

Primary goals:

  • Declare one TAP interface per selected microvm — derived in the builders from the machine name and the inventory mac fact, so a microvm guest gets a virtio-net device instead of no network hardware at all.
  • Keep the interface ID valid for the host — a Linux interface name that allod/nexus can hand to ip tuntap add and that upstream's 15-character limit accepts, with an archetypes-side diagnostic naming the machine rather than only upstream's generic one.
  • Carry the MAC and nothing else across the boundary — no IP, route, gateway, DNS, or systemd-networkd value enters the guest, and the inventory ip fact that sits one attribute away from mac must be shown not to reach it.
  • Test the negative half, not comment on it — a scanner over the evaluated guest configuration and the generated runner that fails when an address-shaped value appears, with a sabotage fixture that introduces one and must fail.
  • Leave libvirt byte-identical — the declarations are gated on the module list outside the module system, so every libvirt machine's derivation is unchanged.

Current state and specifics

A microvm guest today has no network interface. allod/archetypes flake.nix:327 sets networking.hostName and nothing else network-related anywhere in the repo, and the only guest-side networking the framework configures is allod/vm modules/qemu-guest.nix:24-25, which enables NetworkManager for the libvirt guest. allod/vm modules/microvm-guest.nix declares no interface, so microvm.interfaces stays at upstream's [] default and the generated QEMU command has no -netdev or -device virtio-net-* argument. A libvirt guest gets its addressing from the host's libvirt XML; a microvm guest has no equivalent, which is why the interface has to be declared in the guest itself.

The host half already exists and is waiting for this declaration. allod/nexus nix/microvm/host.nix:89-91 reads guest.microvm.interfaces, filters type == "tap", and maps each entry to its id; nix/microvm/launcher.nix:347-364 then runs ip tuntap add name '<id>' mode tap user '<principal>' because upstream's generated tap-up hardcodes user = "microvm" and would otherwise hand the device to the shared account. So id is the host TAP device name, not a guest-side name, and allod/nexus checks/microvm/fixtures.nix:82,172-174 already assumes the tap-<machine> shape for it.

Upstream at the pinned revision 39a499ab85311b56dddb09ec43351cc3658f22c1 declares microvm.interfaces as a list of submodules with type (enum user/tap/macvtap/bridge), id (str, "interface name on the host"), mac (str, no default), bridge, macvtap.*, and tap.vhostnixos-modules/microvm/options.nix:314-371. Its assertions in nixos-modules/microvm/asserts.nix:20-58 cover duplicate ids, bridge/non-bridge consistency, and a 15-character name limit; there is no MAC-format check and no check that an id is a usable interface name. lib/runners/qemu.nix:307-357 renders each entry into -netdev tap,id=<id>,ifname=<id>,script=no,downscript=no plus -device virtio-net-...,netdev=<id>,mac=<mac>, so a malformed value lands in the QEMU argument list rather than failing earlier.

The MAC is inventory data. allod/inventory flake.nix:25,57,90 gives every machine a mac, and flake.nix:179 exports it through lib.vmSpecsJson alongside ip. That adjacency is the specific accident worth testing against: machines.<name>.ip is one attribute away from machines.<name>.mac, and the public examples carry real-shaped values (192.0.2.10, 192.0.2.11). Only the MAC may cross into the guest.

The gating pattern is settled. flake.nix:485,517 add the persistent-volume module with lib.optional (runtime == "microvm") on the builder's module list, and flake.nix:98-104 records why: microvm.* options do not exist under vm.nixosModules.qemuGuest, so an ungated definition is an unmatched-option error on every libvirt machine, and lib.mkIf does not help because it defers the value rather than the definition. runtime-module-selection already proves the gating stays outside the module system with lib.hasAttrByPath, because an undeclared option is a raw attribute error that builtins.tryEval does not catch.

Scope

In scope: one TAP interface declared per selected microvm archetype in allod/archetypes flake.nix, its derived host interface ID, the inventory MAC as the only value crossing into the guest, archetypes-side validation of the merged config.microvm.interfaces and of the guest's address-bearing option surface, a scanner over the evaluated guest configuration and the generated runner that rejects address-shaped values, and a paired sabotage fixture for every assertion and for the scanner itself.

Out of scope and tracked elsewhere in the same arc: host TAP attachment, addressing, routes, DNS, and systemd-networkd configuration, which stay deployment inputs; runtime credential delivery and the guest credential root; the extendModules host integration; exporting the runtime through vmFacts; and the nested-boot test that proves the interface actually reaches a fixture network, which needs the boot slice that does not exist yet.

This is public work. The only machine-derived value it introduces is the inventory MAC, and the change's own tests are what prove nothing else follows it.

Part of allod/strategy#20

Give a selected microVM the network interface it needs to have a NIC at all, and prove the public guest definition still carries no address to reach it on. Primary goals: - **Declare one TAP interface per selected microvm** — derived in the builders from the machine name and the inventory `mac` fact, so a microvm guest gets a virtio-net device instead of no network hardware at all. - **Keep the interface ID valid for the host** — a Linux interface name that `allod/nexus` can hand to `ip tuntap add` and that upstream's 15-character limit accepts, with an archetypes-side diagnostic naming the machine rather than only upstream's generic one. - **Carry the MAC and nothing else across the boundary** — no IP, route, gateway, DNS, or `systemd-networkd` value enters the guest, and the inventory `ip` fact that sits one attribute away from `mac` must be shown not to reach it. - **Test the negative half, not comment on it** — a scanner over the evaluated guest configuration and the generated runner that fails when an address-shaped value appears, with a sabotage fixture that introduces one and must fail. - **Leave libvirt byte-identical** — the declarations are gated on the module list outside the module system, so every libvirt machine's derivation is unchanged. ### Current state and specifics A microvm guest today has no network interface. `allod/archetypes` `flake.nix:327` sets `networking.hostName` and nothing else network-related anywhere in the repo, and the only guest-side networking the framework configures is `allod/vm` `modules/qemu-guest.nix:24-25`, which enables NetworkManager for the *libvirt* guest. `allod/vm` `modules/microvm-guest.nix` declares no interface, so `microvm.interfaces` stays at upstream's `[]` default and the generated QEMU command has no `-netdev` or `-device virtio-net-*` argument. A libvirt guest gets its addressing from the host's libvirt XML; a microvm guest has no equivalent, which is why the interface has to be declared in the guest itself. The host half already exists and is waiting for this declaration. `allod/nexus` `nix/microvm/host.nix:89-91` reads `guest.microvm.interfaces`, filters `type == "tap"`, and maps each entry to its `id`; `nix/microvm/launcher.nix:347-364` then runs `ip tuntap add name '<id>' mode tap user '<principal>'` because upstream's generated `tap-up` hardcodes `user = "microvm"` and would otherwise hand the device to the shared account. So `id` is the host TAP device name, not a guest-side name, and `allod/nexus` `checks/microvm/fixtures.nix:82,172-174` already assumes the `tap-<machine>` shape for it. Upstream at the pinned revision `39a499ab85311b56dddb09ec43351cc3658f22c1` declares `microvm.interfaces` as a list of submodules with `type` (enum `user`/`tap`/`macvtap`/`bridge`), `id` (`str`, "interface name on the host"), `mac` (`str`, no default), `bridge`, `macvtap.*`, and `tap.vhost` — `nixos-modules/microvm/options.nix:314-371`. Its assertions in `nixos-modules/microvm/asserts.nix:20-58` cover duplicate `id`s, bridge/non-bridge consistency, and a 15-character name limit; there is no MAC-format check and no check that an `id` is a usable interface name. `lib/runners/qemu.nix:307-357` renders each entry into `-netdev tap,id=<id>,ifname=<id>,script=no,downscript=no` plus `-device virtio-net-...,netdev=<id>,mac=<mac>`, so a malformed value lands in the QEMU argument list rather than failing earlier. The MAC is inventory data. `allod/inventory` `flake.nix:25,57,90` gives every machine a `mac`, and `flake.nix:179` exports it through `lib.vmSpecsJson` alongside `ip`. That adjacency is the specific accident worth testing against: `machines.<name>.ip` is one attribute away from `machines.<name>.mac`, and the public examples carry real-shaped values (`192.0.2.10`, `192.0.2.11`). Only the MAC may cross into the guest. The gating pattern is settled. `flake.nix:485,517` add the persistent-volume module with `lib.optional (runtime == "microvm")` on the builder's module list, and `flake.nix:98-104` records why: `microvm.*` options do not exist under `vm.nixosModules.qemuGuest`, so an ungated definition is an unmatched-option error on every libvirt machine, and `lib.mkIf` does not help because it defers the value rather than the definition. `runtime-module-selection` already proves the gating stays outside the module system with `lib.hasAttrByPath`, because an undeclared option is a raw attribute error that `builtins.tryEval` does not catch. ### Scope In scope: one TAP interface declared per selected microvm archetype in `allod/archetypes` `flake.nix`, its derived host interface ID, the inventory MAC as the only value crossing into the guest, archetypes-side validation of the merged `config.microvm.interfaces` and of the guest's address-bearing option surface, a scanner over the evaluated guest configuration and the generated runner that rejects address-shaped values, and a paired sabotage fixture for every assertion and for the scanner itself. Out of scope and tracked elsewhere in the same arc: host TAP attachment, addressing, routes, DNS, and `systemd-networkd` configuration, which stay deployment inputs; runtime credential delivery and the guest credential root; the `extendModules` host integration; exporting the runtime through `vmFacts`; and the nested-boot test that proves the interface actually reaches a fixture network, which needs the boot slice that does not exist yet. This is public work. The only machine-derived value it introduces is the inventory MAC, and the change's own tests are what prove nothing else follows it. Part of allod/strategy#20
vnprc closed this issue 2026-08-01 13:26:37 +01:00
Sign in to join this conversation.
No description provided.