{ lib, ... }: let inherit (lib) assertMsg attrNames concatMapAttrs concatStringsSep filterAttrs listToAttrs mapAttrs' nameValuePair optionalAttrs imap1 imap0 length filter findFirst toLower unique; mkInitials = name: let lower = toLower name; noApos = builtins.replaceStrings [ "'" ] [ " " ] lower; words = filter (w: w != "") (builtins.split " +" noApos); letters = concatStringsSep "" (builtins.map (w: builtins.substring 0 1 w) words); in if letters == "" then "loc" else builtins.substring 0 3 letters; mkLocationCode = name: let base = mkInitials name; hash = builtins.substring 0 4 (builtins.hashString "sha256" name); in "${base}-${hash}"; # Manual elemIndex so we do not care what nixpkgs version you are on. elemIndex = name: list: let idxs = imap0 (i: v: if v == name then i else null) list; in findFirst (i: i != null) null idxs; subnetTypes = { dmz = 0; main = 1; guest = 2; iot = 3; storage = 4; management = 5; infra = 6; lab = 7; }; roles = { coreServer = 0; router = 1; infraDevice = 2; server = 3; workstation = 4; thinClient = 5; printer = 6; nas = 7; phone = 8; camera = 9; mobile = 10; guestClient = 11; appliance = 12; automation = 13; oobMgmt = 14; hypervisor = 15; containerHost = 16; monitoring = 17; logging = 18; identity = 19; labDevice = 20; adminWorkstation = 21; repoMirror = 22; ci = 23; media = 24; homeAutomation = 25; dhcpStart = 29; dhcpPool = 30; dhcpEnd = 31; # Legacy alias. New generated DHCP ranges use dhcpStart/dhcpEnd. pool = 31; }; subnetSlugs = { dmz = "dmz"; main = "main"; guest = "guest"; iot = "iot"; storage = "storage"; management = "mgmt"; infra = "infra"; lab = "lab"; }; roleSlugs = { coreServer = "core"; router = "router"; infraDevice = "infra-dev"; server = "server"; workstation = "ws"; thinClient = "thin"; printer = "printer"; nas = "nas"; phone = "phone"; camera = "camera"; mobile = "mobile"; guestClient = "guest"; appliance = "appliance"; automation = "automation"; oobMgmt = "oobm"; hypervisor = "hypervisor"; containerHost = "container"; monitoring = "monitoring"; logging = "logging"; identity = "identity"; labDevice = "lab-dev"; adminWorkstation = "admin-ws"; repoMirror = "mirror"; ci = "ci"; media = "media"; homeAutomation = "home-auto"; dhcpStart = "dhcp-start"; dhcpPool = "dhcp-pool"; dhcpEnd = "dhcp-end"; pool = "dhcp-pool"; }; thirdOctet = { type, role }: let _ = assertMsg (type >= 0 && type <= 7) "metatron-addressing: type must be in 0..7, got ${toString type}"; __ = assertMsg (role >= 0 && role <= 31) "metatron-addressing: role must be in 0..31, got ${toString role}"; in type * 32 + role; decodeThirdOctet = octet: let _ = assertMsg (octet >= 0 && octet <= 255) "metatron-addressing: octet must be in 0..255, got ${toString octet}"; type = builtins.div octet 32; role = octet - (type * 32); in { inherit type role; }; mkIp = { location, type, role, host }: let _ = assertMsg (location >= 0 && location <= 255) "metatron-addressing: location must be in 0..255, got ${toString location}"; __ = assertMsg (host >= 0 && host <= 255) "metatron-addressing: host must be in 0..255, got ${toString host}"; oct3 = thirdOctet { inherit type role; }; in "10.${toString location}.${toString oct3}.${toString host}"; mkRoleRange = { location, type, role }: let _ = assertMsg (location >= 0 && location <= 255) "metatron-addressing: location must be in 0..255, got ${toString location}"; __ = assertMsg (type >= 0 && type <= 7) "metatron-addressing: type must be in 0..7, got ${toString type}"; ___ = assertMsg (role >= 0 && role <= 31) "metatron-addressing: role must be in 0..31, got ${toString role}"; oct3 = thirdOctet { inherit type role; }; base = "10.${toString location}.${toString oct3}"; in { start = "${base}.0"; end = "${base}.255"; }; mkRoleRangeNamed = { location, typeName, roleName }: let _ = assertMsg (builtins.hasAttr typeName subnetTypes) "metatron-addressing: unknown subnet type '${typeName}'"; __ = assertMsg (builtins.hasAttr roleName roles) "metatron-addressing: unknown role '${roleName}'"; type = subnetTypes.${typeName}; role = roles.${roleName}; in mkRoleRange { inherit location type role; }; mkIpNamed = { location, typeName, roleName, host }: let _ = assertMsg (builtins.hasAttr typeName subnetTypes) "metatron-addressing: unknown subnet type '${typeName}'"; __ = assertMsg (builtins.hasAttr roleName roles) "metatron-addressing: unknown role '${roleName}'"; type = subnetTypes.${typeName}; role = roles.${roleName}; in mkIp { inherit location host type role; }; mkSubnet = { location, type }: let _ = assertMsg (location >= 0 && location <= 255) "metatron-addressing: location must be in 0..255, got ${toString location}"; __ = assertMsg (type >= 0 && type <= 7) "metatron-addressing: type must be in 0..7, got ${toString type}"; oct3 = type * 32; in "10.${toString location}.${toString oct3}.0/19"; mkSubnetNamed = { location, typeName }: let _ = assertMsg (builtins.hasAttr typeName subnetTypes) "metatron-addressing: unknown subnet type '${typeName}'"; type = subnetTypes.${typeName}; in mkSubnet { inherit location type; }; mkVlan = { location, type }: let _ = assertMsg (location >= 0 && location <= 255) "metatron-addressing: location must be in 0..255, got ${toString location}"; __ = assertMsg (type >= 0 && type <= 7) "metatron-addressing: type must be in 0..7, got ${toString type}"; in (location * 8) + type + 2; mkVlanNamed = { location, typeName }: let _ = assertMsg (builtins.hasAttr typeName subnetTypes) "metatron-addressing: unknown subnet type '${typeName}'"; type = subnetTypes.${typeName}; in mkVlan { inherit location type; }; asList = value: if value == null then [ ] else if builtins.isList value then value else [ value ]; scopeFromCfg = cfg: { owners = asList (cfg.owners or null) ++ asList (cfg.owner or null); admins = asList (cfg.admins or null) ++ asList (cfg.admin or null); users = asList (cfg.users or null); domain = cfg.domain or null; }; mergeScopes = parent: child: let c = scopeFromCfg child; in { owners = unique (parent.owners ++ c.owners); admins = unique (parent.admins ++ c.admins); users = unique (parent.users ++ c.users); domain = if c.domain != null then c.domain else parent.domain; }; emptyScope = { owners = [ ]; admins = [ ]; users = [ ]; domain = null; }; materializeScope = scope: { inherit (scope) owners admins users; } // optionalAttrs (scope.domain != null) { inherit (scope) domain; }; # 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 networkNames; in if idx == null then 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 = { networkId, networkCode, locationName, networkName, typeName, subnetCfg, scope }: let hostsForSubnet = subnetCfg.hosts or { }; hostNames = filter (hn: builtins.substring 0 1 hn != "_") (attrNames hostsForSubnet); subnetSlug = if builtins.hasAttr typeName subnetSlugs then subnetSlugs.${typeName} else typeName; in listToAttrs (imap1 (idx: hostName: let path = "${locationName}.${networkName}.${typeName}.${hostName}"; hostCfg = hostsForSubnet.${hostName}; hostScope = mergeScopes scope hostCfg; roleName = hostRole path hostCfg; # Count how many hosts of this role exist up to and including this one. roleIndex = let prefix = lib.lists.sublist 0 idx hostNames; in length (filter (hn: (hostsForSubnet.${hn}.role or null) == roleName) prefix); hostId = hostCfg.hostId or roleIndex; ip = mkIpNamed { location = networkId; inherit typeName; roleName = roleName; host = hostId; }; roleSlug = if builtins.hasAttr roleName roleSlugs then roleSlugs.${roleName} else roleName; hostnameBase = "${roleSlug}-${builtins.toString hostId}"; hostname = hostCfg.hostname or hostnameBase; in nameValuePair hostName ( { ip-address = ip; inherit hostname; location = locationName; network = networkName; subnetType = typeName; subnetKey = mkSubnetKey { inherit networkName typeName; }; } // optionalAttrs (hostScope.domain != null) { fqdn = "${hostname}.${subnetSlug}.${networkCode}.${hostScope.domain}"; } // materializeScope hostScope // optionalAttrs (hostCfg ? hw-address) { inherit (hostCfg) hw-address; } // optionalAttrs (hostCfg ? dns) { inherit (hostCfg) dns; } // optionalAttrs (hostCfg ? aliases) { inherit (hostCfg) aliases; } // optionalAttrs (hostCfg ? interface) { inherit (hostCfg) interface; } // optionalAttrs (hostCfg ? tags) { inherit (hostCfg) tags; } )) hostNames); mkHostsFromSpec = spec: let locations = spec.locations or (throw "metatron-addressing: spec.locations is required"); networkId = networkIdForSpec spec; in concatMapAttrs (locationName: locationCfg: let 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 concatMapAttrs (typeName: subnetCfg: let subnetScope = mergeScopes networkScope subnetCfg; in hostMapForSubnet { networkId = net; inherit networkCode locationName networkName typeName subnetCfg; scope = subnetScope; }) subnets) networks) locations; mkSubnetsFromSpec = spec: let locations = spec.locations or (throw "metatron-addressing: spec.locations is required"); networkId = networkIdForSpec spec; in concatMapAttrs (locationName: locationCfg: let 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 mapAttrs' (typeName: subnetCfg: let subnetScope = mergeScopes networkScope subnetCfg; subnetKey = mkSubnetKey { inherit networkName typeName; }; cidr = mkSubnetNamed { location = net; inherit typeName; }; subnetSlug = if builtins.hasAttr typeName subnetSlugs then subnetSlugs.${typeName} else typeName; vlan = subnetCfg.vlan or subnetCfg._vlan or (mkVlanNamed { location = net; inherit typeName; }); # 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 !dhcpEnabled then null else subnetCfg.dhcpRange or generatedDhcpRange; effectiveDhcp = { startRole = dhcpStartRole; startHostId = dhcpStartHostId; endRole = dhcpEndRole; endHostId = dhcpEndHostId; }; in nameValuePair subnetKey ( { inherit cidr locationName networkName typeName subnetKey vlan; } // optionalAttrs (subnetScope.domain != null) { zone = "${subnetSlug}.${networkCode}.${subnetScope.domain}"; } // materializeScope subnetScope // optionalAttrs (dhcpEnabled && dhcpRange != null) { dhcp = effectiveDhcp; dhcpRange = dhcpRange; } )) subnets) networks) locations; mkNetworkFromSpec = spec: { hosts = mkHostsFromSpec spec; subnets = mkSubnetsFromSpec spec; }; in { inherit subnetTypes roles thirdOctet decodeThirdOctet mkIp mkIpNamed mkSubnet mkSubnetNamed mkVlan mkVlanNamed mkHostsFromSpec mkSubnetsFromSpec mkNetworkFromSpec mkRoleRange mkRoleRangeNamed networkNamesForSpec networkIdForSpec mkSubnetKey mergeScopes; # Shorthand; you were calling mkHosts before. mkHosts = mkNetworkFromSpec; }