{ lib }: let # Pad string on the right to reach a fixed width rightPad = width: str: let len = builtins.stringLength str; padding = if len >= width then "" else lib.strings.replicate (width - len) " "; in str + padding; # Extract IP address from CIDR notation (e.g., "10.69.13.5/24" -> "10.69.13.5") extractIP = address: let parts = lib.splitString "/" address; in builtins.head parts; # Check if a network interface name looks like a VPN/tunnel interface isVpnInterface = ifaceName: lib.hasPrefix "wg" ifaceName || lib.hasPrefix "tun" ifaceName || lib.hasPrefix "tap" ifaceName || lib.hasPrefix "vti" ifaceName; # Extract DNS information from a single host configuration # Returns null if host should not be included in DNS extractHostDNS = name: hostConfig: let cfg = hostConfig.config; # Handle cases where homelab module might not be imported dnsConfig = (cfg.homelab or { }).dns or { enable = true; cnames = [ ]; }; hostname = cfg.networking.hostName; networks = cfg.systemd.network.networks or { }; # Filter out VPN interfaces and find networks with static addresses # Check matchConfig.Name instead of network unit name (which can have prefixes like "40-") physicalNetworks = lib.filterAttrs (netName: netCfg: let ifaceName = netCfg.matchConfig.Name or ""; in !(isVpnInterface ifaceName) && (netCfg.address or [ ]) != [ ]) networks; # Get addresses from physical networks only networkAddresses = lib.flatten ( lib.mapAttrsToList (netName: netCfg: netCfg.address or [ ]) physicalNetworks ); # Get the first address, if any firstAddress = if networkAddresses != [ ] then builtins.head networkAddresses else null; # Check if host uses DHCP (no static address) usesDHCP = firstAddress == null || lib.any (netName: (networks.${netName}.networkConfig.DHCP or "no") != "no") (lib.attrNames networks); in if !(dnsConfig.enable or true) || firstAddress == null then null else { inherit hostname; ip = extractIP firstAddress; cnames = dnsConfig.cnames or [ ]; }; # Generate A record line generateARecord = hostname: ip: "${rightPad 20 hostname}IN A ${ip}"; # Generate CNAME record line generateCNAME = alias: target: "${rightPad 20 alias}IN CNAME ${target}"; # Generate zone file from flake configurations and external hosts generateZone = { self , externalHosts , serial , domain ? "home.2rjus.net" , ttl ? 1800 , refresh ? 3600 , retry ? 900 , expire ? 1209600 , minTtl ? 120 , nameservers ? [ "ns1" "ns2" "ns3" ] , adminEmail ? "admin.test.2rjus.net" }: let # Extract DNS info from all flake hosts nixosConfigs = self.nixosConfigurations or { }; hostDNSList = lib.filter (x: x != null) ( lib.mapAttrsToList extractHostDNS nixosConfigs ); # Sort hosts by IP for consistent output sortedHosts = lib.sort (a: b: a.ip < b.ip) hostDNSList; # Generate A records for flake hosts flakeARecords = lib.concatMapStringsSep "\n" (host: generateARecord host.hostname host.ip ) sortedHosts; # Generate CNAMEs for flake hosts flakeCNAMEs = lib.concatMapStringsSep "\n" (host: lib.concatMapStringsSep "\n" (cname: generateCNAME cname host.hostname ) host.cnames ) (lib.filter (h: h.cnames != [ ]) sortedHosts); # Generate A records for external hosts externalARecords = lib.concatStringsSep "\n" ( lib.mapAttrsToList (name: ip: generateARecord name ip ) (externalHosts.aRecords or { }) ); # Generate CNAMEs for external hosts externalCNAMEs = lib.concatStringsSep "\n" ( lib.mapAttrsToList (alias: target: generateCNAME alias target ) (externalHosts.cnames or { }) ); # NS records nsRecords = lib.concatMapStringsSep "\n" (ns: " IN NS ${ns}.${domain}." ) nameservers; # SOA record soa = '' $ORIGIN ${domain}. $TTL ${toString ttl} @ IN SOA ns1.${domain}. ${adminEmail}. ( ${toString serial} ; serial number ${toString refresh} ; refresh ${toString retry} ; retry ${toString expire} ; expire ${toString minTtl} ; ttl )''; in lib.concatStringsSep "\n\n" (lib.filter (s: s != "") [ soa nsRecords "; Flake-managed hosts (auto-generated)" flakeARecords (if flakeCNAMEs != "" then "; Flake-managed CNAMEs\n${flakeCNAMEs}" else "") "; External hosts (not managed by this flake)" externalARecords (if externalCNAMEs != "" then "; External CNAMEs\n${externalCNAMEs}" else "") "" ]); in { inherit extractIP extractHostDNS generateARecord generateCNAME generateZone; }