{ config, pkgs, lib, ... }: # Define your DNS records declaratively let dnsRecords = { "lan." = { ttl = 86400; # Time-to-live for records in this zone ns = [ "hoardfrost.lan." ]; # Nameserver record soa = { nameServer = "hoardfrost.lan."; adminEmail = "root.lan."; serial = "2025091901"; # Update this serial number with each change refresh = "3600"; retry = "1800"; expire = "604800"; minimum = "86400"; }; # Define your host records (A for IPv4, AAAA for IPv6) records = { # Your NixOS server acting as the DNS server hoardfrost = [ { type = "A"; content = "10.0.0.217"; } { type = "AAAA"; content = "2601:282:180:630::803b"; } ]; # Other devices on your local network router = [ { type = "A"; content = "10.0.0.1"; } { type = "AAAA"; content = "fe80::1"; } ]; yukigekko = [ { type = "A"; content = "10.0.0.210"; } { type = "AAAA"; content = "2601:282:180:630::e2e"; } ]; wesbos = [ { type = "A"; content = "10.0.0.110";} # { type = "AAAA"; content = "";} ]; wsl-hive = [ {type = "A"; content = "172.18.84.193";} {type = "AAAA"; content = "fe80::215:5dff:fec1:e4b5";} ]; }; }; }; # This function generates the zone file string from the Nix expression generateZoneFile = zoneName: zoneConfig: let soaRecord = "${zoneName} ${toString zoneConfig.ttl} IN SOA ${zoneConfig.soa.nameServer} ${zoneConfig.soa.adminEmail} ( ${zoneConfig.soa.serial} ${zoneConfig.soa.refresh} ${zoneConfig.soa.retry} ${zoneConfig.soa.expire} ${zoneConfig.soa.minimum} )"; nsRecords = lib.concatStringsSep "\n" (lib.map (ns: "${zoneName} ${toString zoneConfig.ttl} IN NS ${ns}") zoneConfig.ns); hostRecords = lib.concatStringsSep "\n" (lib.flatten (lib.mapAttrsToList (hostname: records: lib.map (record: "${hostname}.${zoneName} ${toString zoneConfig.ttl} IN ${record.type} ${record.content}") records ) zoneConfig.records)); in '' $ORIGIN ${zoneName} ${soaRecord} ${nsRecords} ${hostRecords} ''; # Generate the zone file using the `generateZoneFile` function lanZoneFile = pkgs.writeText "lan.zone" (generateZoneFile "lan." dnsRecords."lan."); # Configure hickory-dns with the generated zone file hickoryConfig = pkgs.writeText "hickory-config.toml" '' listen_addrs_ipv4 = ["127.0.0.1", "10.0.0.217"] # Change to your NixOS server's IPv4 listen_addrs_ipv6 = ["::1", "fe80::215:5dff:fec1:e4b5"] # Change to your NixOS server's IPv6 # Configure as an authoritative server for the local zone [[zones]] zone = "lan." zone_type = "Primary" file = "${lanZoneFile}" # Configure as a recursive resolver for all other queries [[recursor]] # All queries *not* matching the "lan." zone will be forwarded here. [[recursor.forwarders]] name_servers = ["8.8.8.8:53", "[2001:4860:4860::8888]:53"] # All other DNS queries will be handled recursively, starting from the root hints. ''; in { systemd.services.hickory-dns = { enable = true; description = "Hickory DNS with local zone"; wantedBy = [ "multi-user.target" ]; serviceConfig = { Type = "simple"; User = "hickory"; Group = "hickory"; ExecStart = "${pkgs.hickory-dns}/bin/hickory-dns -c ${hickoryConfig}"; Restart = "on-failure"; AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ]; DynamicUser = true; StateDirectory = "hickory"; }; }; users.groups.hickory = {}; users.users.hickory = { isSystemUser = true; group = "hickory"; }; environment.systemPackages = [ pkgs.hickory-dns ]; # Configure your NixOS machine to use itself as the DNS resolver services.resolved.enable = false; networking.nameservers = [ "127.0.0.1" "fdaa:a00:0:1::100" ]; }