{ 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; 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"; 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; }; # locationName -> numeric id (0, 1, 2, ...) locationIdForSpec = spec: let locations = spec.locations or (throw "metatron-addressing: spec.locations is required"); locationNames = attrNames locations; in name: let idx = elemIndex name locationNames; in if idx == null then throw "metatron-addressing: unknown location '${name}'" else idx; 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 }: 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 = loc; 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 = "${locationName}-${networkName}-${typeName}"; } // optionalAttrs (hostScope.domain != null) { fqdn = "${hostname}.${subnetSlug}.${locCode}.${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"); locationId = locationIdForSpec 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 networkScope = mergeScopes locationScope networkCfg; subnets = networkCfg.subnets or { }; in concatMapAttrs (typeName: subnetCfg: let subnetScope = mergeScopes networkScope subnetCfg; in hostMapForSubnet { inherit loc locCode locationName networkName typeName subnetCfg; scope = subnetScope; }) subnets) networks) locations; mkSubnetsFromSpec = spec: let locations = spec.locations or (throw "metatron-addressing: spec.locations is required"); locationId = locationIdForSpec 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 networkScope = mergeScopes locationScope networkCfg; subnets = networkCfg.subnets or { }; in mapAttrs' (typeName: subnetCfg: let subnetScope = mergeScopes networkScope subnetCfg; cidr = mkSubnetNamed { location = loc; inherit typeName; }; subnetSlug = if builtins.hasAttr typeName subnetSlugs then subnetSlugs.${typeName} else typeName; vlan = subnetCfg.vlan or subnetCfg._vlan or (mkVlanNamed { location = loc; inherit typeName; }); dhcpCfg = subnetCfg.dhcp or null; 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; }; }; in nameValuePair "${locationName}-${networkName}-${typeName}" ( { inherit cidr locationName networkName typeName vlan; } // optionalAttrs (subnetScope.domain != null) { zone = "${subnetSlug}.${locCode}.${subnetScope.domain}"; } // materializeScope subnetScope // optionalAttrs (dhcpCfg != null) { dhcp = dhcpCfg; 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 mergeScopes; # Shorthand; you were calling mkHosts before. mkHosts = mkNetworkFromSpec; }