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:
- Given
ipv4.address = "192.0.2.5/24", When the module evaluates, Thennetworking.interfaces.<interface>.ipv4.addressesholds address192.0.2.5withprefixLength = 24. - Given an address without a
/Nsuffix, When the module evaluates, Then the prefix length is24for IPv4 and64for IPv6. - Given a
namethat is not a valid host label (for example an IP address), When the module evaluates, Thennetworking.hostNameis 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:
- Given
publicUplink = "nat", When the module evaluates, Then no default gateway is set. - Given
ipv4.gateway = nullor"", When the module evaluates, Then no IPv4 default gateway is set. - 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:
- 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.
- 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:
- Given
fediversity.network.allocated.ipv4 = "198.51.100.7/28", When the module evaluates, Then the interface holds address198.51.100.7withprefixLength = 28, and not the address of the settings. - Given settings with gateways and no address, and an allocated address, When the module evaluates, Then the default gateways are set.
Edge Cases
domainhas no default. A node evaluated without the setup fails on the missing value.- A
nulladdress adds no interface address and no gateway. - The placeholder gateways are
nulland""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(defaultnixos),needsPublicIp(defaulttrue),publicUplink(bridged,routedornat, defaultbridged),ipv4andipv6. - FR-002: Each of
ipv4andipv6MUST acceptinterface(defaulteth0),address(defaultnull) andgateway(defaultnull). - FR-003: The module MUST set
networking.domainfromdomain, and putnameserversbefore the other entries ofnetworking.nameservers. - FR-004: The module MUST set
networking.hostNameonly whennamematches^$|^[[: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 notnull, and the gateway is not a placeholder. - FR-007:
core/setups/hosts-common.nixMUST givedomainthe group domain for every node it deploys. - FR-008: When no external service holds the apex,
core/setups/hosts-common.nixMUST setnameserverson 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.ipv4oripv6is set, the module MUST use it in place of theaddressof that family, for the interface address and for the gateway condition. - FR-010:
writeConfigMUST setfediversity.network.allocatedfrom the entry of the node inNETBOX_NODE_IPS, andfediversity.network.peersfrom all entries, and MUST NOT put these addresses into the plan.
Key Entities
- Machine network entry: the
networkattribute of a machine in the roster, withname,ipv4andipv6. - 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,routedornat.
Success Criteria (mandatory)
Measurable Outcomes
- SC-001:
nix-unit-setups-public-uplinkandnix-unit-resources-networkpass.
Assumptions
- The roster gives each machine a
networkentry. - A NetBox-allocated address arrives in CIDR form.
Known Deviations
- When the operator tree has no
core/resources/network/allocated.nix,writeConfigputs the allocated address into the plan as theaddressof the settings (FR-010). Thus a deployer can deploy with an older operator tree. When the operator tree has nocore/resources/network/peers.nix,writeConfigdoes not setfediversity.network.peers. - The
needsPublicIpoption is unused (#2131). Its description says thathosts-common.nixskips NetBox allocation for a node that sets it tofalse.hosts-common.nixderivesneedsPublicIpfrompublicNodesand does not read the option.