A guest sets `vlan` to attach to its host's `br-vlan<id>` bridge, `mac` to reuse an existing address (else a stable one is derived from its namespace path and read back via `nix eval`), and `address` for a static IP (else DHCP). The MAC and address are pinned inside the guest by its own networkd, the only stable MAC pin for a nested container. A guest naming a VLAN its host has not declared fails the build with an actionable message. The `br-vlan<id>` naming moves into a shared `bridgeName` in the lib, so the bridge a guest attaches to and the bridge the host emits have one source.
4.1 KiB
spec, blocked-by
| spec | blocked-by | ||
|---|---|---|---|
| guests |
|
What to build
The Host-side placement fields that make a Guest a first-class L2 citizen on a tagged VLAN, exactly as Proxmox did.
A Host sets vlan, and the Guest attaches to that Host's br-vlan<id> bridge by the naming convention, so the Guest states only which VLAN it lives on.
A Host may set mac to reuse an existing address so its router's DHCP reservations keep working; an unset mac derives a stable, readable address from the Guest's namespace path in a locally-administered range, surfaced via evaluation so the operator can add a reservation.
The MAC is pinned inside the guest through the guest's own systemd-networkd, which is the only way a nested-container MAC stays stable.
A Host may set address for a static IP; unset means DHCP, keeping IP management centralized at the router.
A build-time assertion ties the Guest's vlan to the set of VLANs its Host's modules.network declares, so a Guest naming an undeclared VLAN fails the Host build with a clear message rather than as a broken bridge at runtime.
Acceptance criteria
- A Host setting
guests.<path>.vlanattaches the Guest to that Host'sbr-vlan<id>bridge by the naming convention. - Setting
macpins that exact address on the Guest via the guest's own systemd-networkd; leaving it unset derives a stable MAC from the Guest's namespace path in a locally-administered range, readable vianix eval. - Setting
addressgives the Guest a static IP on its VLAN; leaving it unset takes the address by DHCP. - A Guest whose
vlanis not among its Host's declared VLANs failsnix flake checkwith a clear, actionable message naming the offending Guest and VLAN. - A Host with a correctly-placed networked Guest builds via
nix flake check, and the Guest's resolved bridge attachment and derived MAC are verifiable bynix eval.
Implementation Notes
The br-vlan<id> naming was a local helper in modules/network.nix and is now a shared bridgeName in lib.nix, exported through my and consumed by both the network foundation and guest placement.
The convention has one source, so the bridge a guest attaches to can never drift from the bridge the host emits.
The interior networking is realized by a small module injected into the guest's container config only when vlan is set.
It enables the guest's own systemd-networkd on eth0 — the name a nested container gives its bridged veth — pinning the placement MAC there and taking the static address or DHCP when it is unset.
Pinning the MAC through the guest's own networkd is the only way a nested-container MAC stays stable; the nspawn-assigned veth MAC is otherwise regenerated.
Enabling networkd default-enables systemd-resolved, so a DHCP guest also gets its resolver.
The derived MAC is the mac option's default, so an unset MAC reads back through nix eval .#nixosConfigurations.<host>.config.guests.<path>.mac.
The first octet is 02 (locally-administered, unicast) and the remaining five octets are a hash slice of the namespace path.
A static address sets only the on-VLAN IP, with no gateway or DNS.
This mirrors modules.network, which deliberately dropped a management.gateway as speculative for the foundation slice; off-VLAN routing for a statically-addressed guest is a later concern, and the centralized path stays DHCP.
The networked path is verified by nix eval against neogaia through extendModules rather than by committing an enablement, exactly as task 0003 verified modules.network.
neogaia is a wifi laptop that cannot bridge, and enabling networkd on it would take over its DNS, so its committed guests.sample placement leaves vlan unset.
Verified: with vlan = 10 the guest resolves hostBridge = br-vlan10 and the interior eth0 networkd pins the derived MAC; an unset address yields DHCP = "yes" and a set one yields the static CIDR; and vlan = 99 against declared [10 20] fails the build with the actionable message.
nix flake check passes with guests.sample building its interior in full.