{ config, lib, pkgs, ... }: let inherit (lib) mkOption types mkIf concatMapStrings concatStrings mapAttrsToList attrsets; cfg = config.pepe.core.dns; in { options.pepe.core.dns = { enable = mkOption { type = types.bool; default = false; description = "Whether to enable the DNS server."; }; nextDNSId = mkOption { type = types.str; default = "d65174"; description = "NextDNS ID for DNS over TLS."; }; extraDomains = mkOption { type = types.attrsOf (types.submodule { options = { dnsInterfaces = mkOption { type = types.listOf types.str; default = [ ]; description = "List of interfaces to add DNS entries for this domain."; }; }; }); default = { }; description = "Additional domains to add to DNS configuration."; }; }; config = mkIf cfg.enable { services.coredns = { enable = true; config = let # Function to generate domain-specific configurations generateDomainConfig = domain: conf: ifaceName: let iface = config.pepe.core.network.interfaces.${ifaceName}; ifaceEndpoint = lib.head (lib.attrNames (lib.filterAttrs (_: device: device.isEndpoint) iface.devices)); serverIP = iface.devices.${ifaceEndpoint}.address; interfaceNet = iface.net; in '' ${domain} { view ${ifaceName} { expr incidr(client_ip(), '${interfaceNet}') } template IN A ${domain} { answer "${domain}. 60 IN A ${serverIP}" } template IN HTTPS ${domain} { answer "${domain}. 60 IN HTTPS 1 . ipv4hint=\"${serverIP}\"" } cache log } ''; # Function to generate device views for an interface generateDeviceViews = ifaceName: let iface = config.pepe.core.network.interfaces.${ifaceName}; in concatMapStrings ({ name, device }: let deviceIP = device.address; serverName = "${name}-${cfg.nextDNSId}.dns.nextdns.io"; in '' . { view ${name} { expr client_ip() == '${deviceIP}' } forward . tls://45.90.28.77 tls://45.90.30.77 { tls_servername ${serverName} health_check 5s } } '' ) (attrsets.mapAttrsToList (name: device: { inherit name device; }) iface.devices ); # Collect all interfaces used across all domains allInterfaces = lib.unique (lib.flatten (lib.mapAttrsToList (_: conf: conf.dnsInterfaces) (config.pepe.core.vhost.hosts // cfg.extraDomains) )); # Generate all device views once allDeviceViews = concatMapStrings generateDeviceViews allInterfaces; # Function to generate configurations for all domains generateCoreDNSConfig = domains: let generateForDomain = domain: conf: concatMapStrings (ifaceName: generateDomainConfig domain conf ifaceName) conf.dnsInterfaces; in concatStrings (mapAttrsToList generateForDomain domains); allDomains = config.pepe.core.vhost.hosts // cfg.extraDomains; in let # Function to generate DNS records for individual devices based on dnsResolvableName generateDeviceHostRecords = let generateRecordsForInterface = ifaceName: ifaceConfig: lib.concatStringsSep "\n" (lib.mapAttrsToList (deviceName: deviceConfig: if deviceConfig.hostname!= null then let deviceAddress = deviceConfig.address; deviceIface = ifaceConfig.net; deviceHost = deviceConfig.hostname; in '' ${deviceHost} { view ${ifaceName} { expr incidr(client_ip(), '${deviceIface}') } template IN A ${deviceHost} { answer "${deviceHost}. 60 IN A ${deviceAddress}" } cache log } '' else "" ) ifaceConfig.devices); in lib.concatStringsSep "\n" (lib.mapAttrsToList generateRecordsForInterface config.pepe.core.network.interfaces); in '' ${generateCoreDNSConfig allDomains} ${generateDeviceHostRecords} ${allDeviceViews} . { forward . tls://45.90.28.77 tls://45.90.30.77 { tls_servername "lan-${cfg.nextDNSId}.dns.nextdns.io" health_check 5s } } ''; }; }; }