{ lib ? import { } }: let inherit (lib) assertMsg attrNames concatMapAttrs concatStringsSep listToAttrs nameValuePair optionalAttrs imap1 mapAttrs' imap0 length filter findFirst toLower;# mkInitials = name: let lower = toLower name; # was builtins.toLower 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 don't care what nixpkgs version you're 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 (subnetTypes ? typeName) "metatron-addressing: unknown subnet type '${typeName}'"; __ = assertMsg (roles ? roleName) "metatron-addressing: unknown role '${roleName}'"; type = subnetTypes.${typeName}; role = roles.${roleName}; in mkRoleRange { inherit location type role; }; mkIpNamed = { location, typeName, roleName, host }: let _ = assertMsg (subnetTypes ? typeName) "metatron-addressing: unknown subnet type '${typeName}'"; __ = assertMsg (roles ? roleName) "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 (subnetTypes ? typeName) "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 (subnetTypes ? typeName) "metatron-addressing: unknown subnet type '${typeName}'"; type = subnetTypes.${typeName}; in mkVlan { inherit location type; }; # 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; # Just the host map: { deimos = { ip-address = "..."; fqdn = "..."; ... }; ... } mkHostsFromSpec = spec: let locations = spec.locations or (throw "metatron-addressing: spec.locations is required"); locationId = locationIdForSpec spec; domain = spec.domain or (throw "metatron-addressing: spec.domain is required for FQDN generation"); in concatMapAttrs (locationName: locationCfg: let loc = locationId locationName; locCode = mkLocationCode locationName; # Only treat attributes that are attrsets with a `hosts` attr as subnets subnets = lib.filterAttrs (_: v: builtins.isAttrs v && v ? hosts) locationCfg; in concatMapAttrs (typeName: subnetCfg: let hostsForSubnet = subnetCfg.hosts or { }; # Only treat non-underscore-prefixed keys as hosts hostNames = filter (hn: builtins.substring 0 1 hn != "_") (attrNames hostsForSubnet); in listToAttrs (imap1 (idx: hostName: let hostCfg = hostsForSubnet.${hostName}; roleName = hostCfg.role; # 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 = hostCfg.role; host = hostId; }; subnetSlug = if builtins.hasAttr typeName subnetSlugs then subnetSlugs.${typeName} else typeName; roleSlug = if builtins.hasAttr roleName roleSlugs then roleSlugs.${roleName} else roleName; hostnameBase = "${roleSlug}-${builtins.toString hostId}"; hostname = hostCfg.hostname or hostnameBase; fqdn = "${hostname}.${subnetSlug}.${locCode}.${domain}"; in nameValuePair hostName ( { ip-address = ip; inherit hostname fqdn; } // optionalAttrs (hostCfg ? hw-address) { inherit (hostCfg) hw-address; } // optionalAttrs (hostCfg ? dns) { inherit (hostCfg) dns; } // optionalAttrs (hostCfg ? aliases) { inherit (hostCfg) aliases; } )) hostNames)) subnets) locations; # Subnet map: { "home-dmz" = "10.1.0.0/19"; "home-main" = "..."; ... } mkSubnetsFromSpec = spec: let locations = spec.locations or (throw "metatron-addressing: spec.locations is required"); locationId = locationIdForSpec spec; domain = spec.domain or (throw "metatron-addressing: spec.domain is required"); in concatMapAttrs (locationName: locationCfg: let loc = locationId locationName; locCode = mkLocationCode locationName; # Only attributes with `hosts` are subnets subnets = lib.filterAttrs (_: v: builtins.isAttrs v && v ? hosts) locationCfg; in mapAttrs' (typeName: subnetCfg: let cidr = mkSubnetNamed { location = loc; inherit typeName; }; subnetSlug = if builtins.hasAttr typeName subnetSlugs then subnetSlugs.${typeName} else typeName; zone = "${subnetSlug}.${locCode}.${domain}"; 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}-${typeName}" ( { inherit cidr locationName typeName zone vlan; } // optionalAttrs (dhcpCfg != null) { dhcp = dhcpCfg; dhcpRange = dhcpRange; } )) subnets) locations; # Combined view, if you want both mkNetworkFromSpec = spec: let domain = spec.domain or (throw "metatron-addressing: spec.domain is required"); in { inherit domain; hosts = mkHostsFromSpec spec; subnets = mkSubnetsFromSpec spec; }; in { inherit subnetTypes roles thirdOctet decodeThirdOctet mkIp mkIpNamed mkSubnet mkSubnetNamed mkHostsFromSpec mkSubnetsFromSpec mkNetworkFromSpec mkRoleRange mkRoleRangeNamed; # Shorthand; you were calling mkHosts before mkHosts = mkNetworkFromSpec; }