feat(guests): place networked guests on tagged VLANs #30

Merged
alexion merged 2 commits from task-0004-guest-networking-placement into main 2026-07-25 16:49:14 -04:00
Owner

Implements the guest networking placement slice of the guests spec.

What was built

The host-side placement fields that make a guest a first-class L2 citizen on a tagged VLAN:

  • vlan — attaches the guest to its host's br-vlan<id> bridge by the naming convention. A guest naming a VLAN its host has not declared fails the build with an actionable message naming the guest and VLAN.
  • mac — set it to reuse an existing address so a router's DHCP reservation keeps working; left unset, a stable address is derived from the guest's namespace path in the locally-administered range and reads back via nix eval.
  • address — a static on-VLAN IP; left unset the guest takes its address by DHCP.

The MAC and address are pinned inside the guest by its own systemd-networkd, the only stable MAC pin for a nested container. 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.

Deviations

  • 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; 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 a committed enablement, exactly as the network foundation was. neogaia is a wifi laptop that cannot bridge and whose DNS networkd would take over, so its committed guests.sample placement leaves vlan unset. nix flake check passes with the guest building its interior in full.

Review

Overall: MEDIUM

  • Blast radius: Medium — shared bridgeName helper now consumed by two files; new networking threaded into the shared guest abstraction, but only one benign path is live.
  • Reversibility: Low — pure declarative additions, nullable options defaulting to off, clean rollback, no state/migrations.
  • Test coverage: Medium — no unit tests; only nix flake check, and the networked path isn't exercised by any current host.
  • Sensitive domain: Medium — network topology, VLAN isolation, derived MACs; guarded by the vlan-declared assertion, no auth/secrets.
  • Size & complexity: Low — small, readable diff with simple mkIf conditionals.
  • Runtime criticality: Medium — networking is production-critical in principle, but no running host uses the networked path yet.

Unaddressed findings:

  • Spec — a static address carries no gateway or DNS, unlike the DHCP path. Left as a deliberate scope boundary matching the network foundation's dropped gateway; recorded in the task's implementation notes.

— Claude

Implements the guest networking placement slice of the guests spec. ## What was built The host-side placement fields that make a guest a first-class L2 citizen on a tagged VLAN: - `vlan` — attaches the guest to its host's `br-vlan<id>` bridge by the naming convention. A guest naming a VLAN its host has not declared fails the build with an actionable message naming the guest and VLAN. - `mac` — set it to reuse an existing address so a router's DHCP reservation keeps working; left unset, a stable address is derived from the guest's namespace path in the locally-administered range and reads back via `nix eval`. - `address` — a static on-VLAN IP; left unset the guest takes its address by DHCP. The MAC and address are pinned inside the guest by its own systemd-networkd, the only stable MAC pin for a nested container. 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. ## Deviations - 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; 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 a committed enablement, exactly as the network foundation was. `neogaia` is a wifi laptop that cannot bridge and whose DNS networkd would take over, so its committed `guests.sample` placement leaves `vlan` unset. `nix flake check` passes with the guest building its interior in full. ## Review **Overall: MEDIUM** - Blast radius: Medium — shared `bridgeName` helper now consumed by two files; new networking threaded into the shared guest abstraction, but only one benign path is live. - Reversibility: Low — pure declarative additions, nullable options defaulting to off, clean rollback, no state/migrations. - Test coverage: Medium — no unit tests; only `nix flake check`, and the networked path isn't exercised by any current host. - Sensitive domain: Medium — network topology, VLAN isolation, derived MACs; guarded by the vlan-declared assertion, no auth/secrets. - Size & complexity: Low — small, readable diff with simple `mkIf` conditionals. - Runtime criticality: Medium — networking is production-critical in principle, but no running host uses the networked path yet. Unaddressed findings: - **Spec** — a static `address` carries no gateway or DNS, unlike the DHCP path. Left as a deliberate scope boundary matching the network foundation's dropped gateway; recorded in the task's implementation notes. — Claude
alexion added 2 commits 2026-07-25 16:46:09 -04:00
Bump the skills flake input and remove the grill and domain-modeling
skills, which are no longer carried in the agent toolset.
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.
alexion merged commit e6ea8a0060 into main 2026-07-25 16:49:14 -04:00
alexion deleted branch task-0004-guest-networking-placement 2026-07-25 16:49:14 -04:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: alexion/dotfiles#30