Feature Specification: Resource network

Created: 2026-10-07

Status: Implemented

Input: Reconstructed from the code at 093e68b6f (core/resources/network/default.nix, core/render/hosts.nix, core/setups/hosts-common.nix, core/setups/wiring/public-uplink.nix, core/setups/wiring/public-uplink-test.nix), pull requests #1184, #1205, #1235 and #1238.

User Scenarios & Testing (mandatory)

The network resource turns the network facts of one machine into NixOS configuration: host name, domain, resolvers, static addresses and default gateways. It has a policy only. No component requests it, and apply ignores the requests. It wraps no contract. apply gives the settings to the NixOS module core/resources/network/nixos.nix as the option fediversity.network, and that module renders them. An address that NetBox allocates (fediversity.network.allocated) takes the place of the address of the settings. The shared resource rules are in the resource family spec.

User Story 1 - Configure a machine from its roster entry (Priority: P1)

A hosting provider lists a machine with a network entry: name, ipv4 and ipv6. core/render/hosts.nix copies the entry into environments.<app>.resources.<app>.network. core/setups/hosts-common.nix sets domain to the group domain with mkDefault. Where the group's own DNS nodes hold the apex, it sets nameservers on each other node to the addresses of these DNS nodes. The node imports the process output of the policy.

Why this priority: a node without its address and name cannot join the deployment.

Independent Test: nix-unit-resources-network evaluates the module that apply gives: the host name check, the resolver order and the CIDR split. nix-unit-setups-public-uplink tests the values that the setup puts into the policy.

Acceptance Scenarios:

  1. Given ipv4.address = "192.0.2.5/24", When the module evaluates, Then networking.interfaces.<interface>.ipv4.addresses holds address 192.0.2.5 with prefixLength = 24.
  2. Given an address without a /N suffix, When the module evaluates, Then the prefix length is 24 for IPv4 and 64 for IPv6.
  3. Given a name that is not a valid host label (for example an IP address), When the module evaluates, Then networking.hostName is not set.

User Story 2 - Skip a gateway that the uplink does not deliver (Priority: P1)

A machine sits behind a NAT bridge, or its roster entry has a placeholder gateway. A rendered gateway would add a default route that shadows the DHCP route of the bridge, and all egress would fail. The policy sets defaultGateway and defaultGateway6 only when publicUplink is not nat, the address is set, and the gateway is not a placeholder.

Why this priority: a wrong default route takes all egress away from the machine.

Independent Test: nix-unit-resources-network evaluates this decision in the module that apply gives. nix-unit-setups-public-uplink tests that the setup states the mode (test-nat-config-states-the-mode-only) and the gateways (test-delivered-node-takes-interface-and-gateways, test-uplink-without-gateways-leaves-the-route-alone).

Acceptance Scenarios:

  1. Given publicUplink = "nat", When the module evaluates, Then no default gateway is set.
  2. Given ipv4.gateway = null or "", When the module evaluates, Then no IPv4 default gateway is set.
  3. Given publicUplink = "bridged" or "routed" and a real gateway, When the module evaluates, Then the default gateway uses that address on the stated interface.

User Story 3 - Take the uplink from the deployment (Priority: P2)

The uplink type is a property of the hosting provider's topology. On incus methods, core/setups/hosts-common.nix sets each node's network policy to uplink.networkConfig. It sets publicUplink from PUBLIC_UPLINK. For a node that the uplink delivers to, it forces the interface and the gateways with mkForce.

Why this priority: the same machine data serves several topologies.

Independent Test: nix-unit-setups-public-uplink.

Acceptance Scenarios:

  1. Given an incus method and a node that the uplink delivers to, When the policy evaluates, Then the interface and the gateways come from the uplink coordinate.
  2. Given a non-incus method, When the policy evaluates, Then the uplink does not change the policy.

User Story 4 - Use the address that NetBox allocates (Priority: P1)

On tf-incus methods, NetBox allocates the public address of a node, and tofu gives it to the node in NETBOX_NODE_IPS. The address is not in the plan. NETBOX_NODE_IPS holds the entries of all nodes of the deployment. writeConfig (core/effects/common/write-config.nix) sets the entry of the node as fediversity.network.allocated, and the addresses of all entries as fediversity.network.peers. The module uses the allocated address in place of ipv4.address or ipv6.address, and the firewall gate admits the allocated and peer addresses as host addresses (firewall spec).

Why this priority: without its allocated address, a node on a tf-incus method has no public address.

Independent Test: nix-unit-resources-network, tests test-an-allocated-address-replaces-the-address and test-an-allocated-address-gives-the-gateways. nix-unit-setups-internal-reach evaluates a node with an allocated address.

Acceptance Scenarios:

  1. Given fediversity.network.allocated.ipv4 = "198.51.100.7/28", When the module evaluates, Then the interface holds address 198.51.100.7 with prefixLength = 28, and not the address of the settings.
  2. Given settings with gateways and no address, and an allocated address, When the module evaluates, Then the default gateways are set.

Edge Cases

  • domain has no default. A node evaluated without the setup fails on the missing value.
  • A null address adds no interface address and no gateway.
  • The placeholder gateways are null and "" for IPv4 and IPv6.
  • An allocated address in one family only leaves the other family at the address of the settings.

Requirements (mandatory)

Functional Requirements

  • FR-001: The policy MUST accept domain, nameservers (default [ ]), name (default nixos), needsPublicIp (default true), publicUplink (bridged, routed or nat, default bridged), ipv4 and ipv6.
  • FR-002: Each of ipv4 and ipv6 MUST accept interface (default eth0), address (default null) and gateway (default null).
  • FR-003: The module MUST set networking.domain from domain, and put nameservers before the other entries of networking.nameservers.
  • FR-004: The module MUST set networking.hostName only when name matches ^$|^[[:alnum:]]([[:alnum:]_-]{0,61}[[:alnum:]])?$.
  • FR-005: The module MUST split an address in CIDR form into the address and prefixLength.
  • FR-006: The module MUST set a default gateway only when the uplink is not nat, the address is not null, and the gateway is not a placeholder.
  • FR-007: core/setups/hosts-common.nix MUST give domain the group domain for every node it deploys.
  • FR-008: When no external service holds the apex, core/setups/hosts-common.nix MUST set nameservers on each node that is not a DNS node to the IPv4 and IPv6 addresses of all the DNS nodes that the deployment stands up.
  • FR-009: When fediversity.network.allocated.ipv4 or ipv6 is set, the module MUST use it in place of the address of that family, for the interface address and for the gateway condition.
  • FR-010: writeConfig MUST set fediversity.network.allocated from the entry of the node in NETBOX_NODE_IPS, and fediversity.network.peers from all entries, and MUST NOT put these addresses into the plan.

Key Entities

  • Machine network entry: the network attribute of a machine in the roster, with name, ipv4 and ipv6.
  • Allocated address: an address in CIDR form that NetBox allocates to a node, in fediversity.network.allocated.
  • publicUplink: how the public address reaches the machine: bridged, routed or nat.

Success Criteria (mandatory)

Measurable Outcomes

  • SC-001: nix-unit-setups-public-uplink and nix-unit-resources-network pass.

Assumptions

  • The roster gives each machine a network entry.
  • A NetBox-allocated address arrives in CIDR form.

Known Deviations

  • When the operator tree has no core/resources/network/allocated.nix, writeConfig puts the allocated address into the plan as the address of the settings (FR-010). Thus a deployer can deploy with an older operator tree. When the operator tree has no core/resources/network/peers.nix, writeConfig does not set fediversity.network.peers.
  • The needsPublicIp option is unused (#2131). Its description says that hosts-common.nix skips NetBox allocation for a node that sets it to false. hosts-common.nix derives needsPublicIp from publicNodes and does not read the option.