Add DHCP pool support.

This commit is contained in:
Yaro Kasear 2026-05-01 13:33:44 -05:00
parent f35e402b4d
commit bf6ba269af
3 changed files with 141 additions and 39 deletions

View file

@ -83,6 +83,11 @@ let
ci = 23;
media = 24;
homeAutomation = 25;
dhcpStart = 29;
dhcpPool = 30;
dhcpEnd = 31;
# Legacy alias. New generated DHCP ranges use dhcpStart/dhcpEnd.
pool = 31;
};
@ -124,6 +129,9 @@ let
ci = "ci";
media = "media";
homeAutomation = "home-auto";
dhcpStart = "dhcp-start";
dhcpPool = "dhcp-pool";
dhcpEnd = "dhcp-end";
pool = "dhcp-pool";
};
@ -283,27 +291,44 @@ let
inherit (scope) domain;
};
# locationName -> numeric id (0, 1, 2, ...)
locationIdForSpec =
# All networks across all locations, in deterministic attr-name order.
# Network name, not location name, owns the second octet:
# 10.{network_id}.{subnet_type * 32 + host_role}.{host_id}
networkNamesForSpec =
spec:
let
locations = spec.locations or (throw "metatron-addressing: spec.locations is required");
locationNames = attrNames locations;
networkNamesNested = builtins.map
(locationName: attrNames (locations.${locationName}.networks or { }))
locationNames;
in
unique (builtins.concatLists networkNamesNested);
# networkName -> numeric id (0, 1, 2, ...)
networkIdForSpec =
spec:
let
networkNames = networkNamesForSpec spec;
in
name:
let
idx = elemIndex name locationNames;
idx = elemIndex name networkNames;
in
if idx == null then
throw "metatron-addressing: unknown location '${name}'"
throw "metatron-addressing: unknown network '${name}'"
else
idx;
mkSubnetKey =
{ networkName, typeName }:
"${networkName}-${typeName}";
hostRole = path: hostCfg:
hostCfg.role or (throw "metatron-addressing: host '${path}' is missing required field 'role' for address generation");
hostMapForSubnet =
{ loc, locCode, locationName, networkName, typeName, subnetCfg, scope }:
{ networkId, networkCode, locationName, networkName, typeName, subnetCfg, scope }:
let
hostsForSubnet = subnetCfg.hosts or { };
hostNames = filter (hn: builtins.substring 0 1 hn != "_") (attrNames hostsForSubnet);
@ -333,7 +358,7 @@ let
hostId = hostCfg.hostId or roleIndex;
ip = mkIpNamed {
location = loc;
location = networkId;
inherit typeName;
roleName = roleName;
host = hostId;
@ -355,10 +380,10 @@ let
location = locationName;
network = networkName;
subnetType = typeName;
subnetKey = "${locationName}-${networkName}-${typeName}";
subnetKey = mkSubnetKey { inherit networkName typeName; };
}
// optionalAttrs (hostScope.domain != null) {
fqdn = "${hostname}.${subnetSlug}.${locCode}.${hostScope.domain}";
fqdn = "${hostname}.${subnetSlug}.${networkCode}.${hostScope.domain}";
}
// materializeScope hostScope
// optionalAttrs (hostCfg ? hw-address) {
@ -383,19 +408,19 @@ let
spec:
let
locations = spec.locations or (throw "metatron-addressing: spec.locations is required");
locationId = locationIdForSpec spec;
networkId = networkIdForSpec spec;
in
concatMapAttrs
(locationName: locationCfg:
let
loc = locationId locationName;
locCode = mkLocationCode locationName;
locationScope = mergeScopes emptyScope locationCfg;
networks = locationCfg.networks or { };
in
concatMapAttrs
(networkName: networkCfg:
let
net = networkId networkName;
networkCode = mkLocationCode networkName;
networkScope = mergeScopes locationScope networkCfg;
subnets = networkCfg.subnets or { };
in
@ -405,7 +430,8 @@ let
subnetScope = mergeScopes networkScope subnetCfg;
in
hostMapForSubnet {
inherit loc locCode locationName networkName typeName subnetCfg;
networkId = net;
inherit networkCode locationName networkName typeName subnetCfg;
scope = subnetScope;
})
subnets)
@ -416,19 +442,19 @@ let
spec:
let
locations = spec.locations or (throw "metatron-addressing: spec.locations is required");
locationId = locationIdForSpec spec;
networkId = networkIdForSpec spec;
in
concatMapAttrs
(locationName: locationCfg:
let
loc = locationId locationName;
locCode = mkLocationCode locationName;
locationScope = mergeScopes emptyScope locationCfg;
networks = locationCfg.networks or { };
in
concatMapAttrs
(networkName: networkCfg:
let
net = networkId networkName;
networkCode = mkLocationCode networkName;
networkScope = mergeScopes locationScope networkCfg;
subnets = networkCfg.subnets or { };
in
@ -436,8 +462,9 @@ let
(typeName: subnetCfg:
let
subnetScope = mergeScopes networkScope subnetCfg;
subnetKey = mkSubnetKey { inherit networkName typeName; };
cidr = mkSubnetNamed { location = loc; inherit typeName; };
cidr = mkSubnetNamed { location = net; inherit typeName; };
subnetSlug =
if builtins.hasAttr typeName subnetSlugs
@ -447,38 +474,73 @@ let
vlan =
subnetCfg.vlan or
subnetCfg._vlan or
(mkVlanNamed { location = loc; inherit typeName; });
(mkVlanNamed { location = net; inherit typeName; });
dhcpCfg = subnetCfg.dhcp or null;
# DHCP is generated by default for every subnet unless explicitly disabled.
# Default range:
# start = 10.{network_id}.{subnet_type * 32 + 29}.1
# end = 10.{network_id}.{subnet_type * 32 + 31}.250
# Override options:
# dhcp = false;
# dhcpRange = { startIp = "..."; endIp = "..."; };
# dhcp = {
# startRole = "dhcpStart";
# startHostId = 1;
# endRole = "dhcpEnd";
# endHostId = 250;
# };
dhcpRaw = subnetCfg.dhcp or true;
dhcpEnabled = dhcpRaw != false;
dhcpCfg =
if builtins.isAttrs dhcpRaw
then dhcpRaw
else { };
dhcpStartRole = dhcpCfg.startRole or "dhcpStart";
dhcpEndRole = dhcpCfg.endRole or "dhcpEnd";
# start/end are kept as compatibility aliases for the old dhcp = { start = ...; end = ...; } shape.
dhcpStartHostId = dhcpCfg.startHostId or dhcpCfg.start or 1;
dhcpEndHostId = dhcpCfg.endHostId or dhcpCfg.end or 250;
generatedDhcpRange = {
startIp = mkIpNamed {
location = net;
inherit typeName;
roleName = dhcpStartRole;
host = dhcpStartHostId;
};
endIp = mkIpNamed {
location = net;
inherit typeName;
roleName = dhcpEndRole;
host = dhcpEndHostId;
};
};
dhcpRange =
if dhcpCfg == null then null else {
startIp = mkIpNamed {
location = loc;
inherit typeName;
roleName = "pool";
host = dhcpCfg.start;
};
endIp = mkIpNamed {
location = loc;
inherit typeName;
roleName = "pool";
host = dhcpCfg.end;
};
};
if !dhcpEnabled then null
else subnetCfg.dhcpRange or generatedDhcpRange;
effectiveDhcp = {
startRole = dhcpStartRole;
startHostId = dhcpStartHostId;
endRole = dhcpEndRole;
endHostId = dhcpEndHostId;
};
in
nameValuePair
"${locationName}-${networkName}-${typeName}"
subnetKey
(
{
inherit cidr locationName networkName typeName vlan;
inherit cidr locationName networkName typeName subnetKey vlan;
}
// optionalAttrs (subnetScope.domain != null) {
zone = "${subnetSlug}.${locCode}.${subnetScope.domain}";
zone = "${subnetSlug}.${networkCode}.${subnetScope.domain}";
}
// materializeScope subnetScope
// optionalAttrs (dhcpCfg != null) {
dhcp = dhcpCfg;
// optionalAttrs (dhcpEnabled && dhcpRange != null) {
dhcp = effectiveDhcp;
dhcpRange = dhcpRange;
}
))
@ -509,6 +571,9 @@ in
mkNetworkFromSpec
mkRoleRange
mkRoleRangeNamed
networkNamesForSpec
networkIdForSpec
mkSubnetKey
mergeScopes;
# Shorthand; you were calling mkHosts before.