{ config, lib, ... }: with lib; let inherit (config.networking) domain; inherit (config.local.nets) gate-public; inherit (config.local.ns.server) tsigName; ptrNets = config.local.ns.ptr; in { options.local.ns.zones = mkOption { type = with lib.types; attrsOf (submodule ({ config, name, ... }: let inherit (config.soa) primary; cfg = config.localNS; ptrDomain = cfg.ptrNet.v4 != null || cfg.ptrNet.v6 != null; in { options.localNS = { enable = mkEnableOption "local NS settings"; acme = mkOption { default = { }; type = attrsOf str; }; ptrNet = { v4 = mkOption { type = nullOr str; default = null; }; v6 = mkOption { type = nullOr str; default = null; }; }; }; config = mkIf cfg.enable { ptrName = let name = if cfg.ptrNet.v6 != null then "${cfg.ptrNet.v6}-v6" else "${cfg.ptrNet.v4}-v4"; in mkIf ptrDomain name; # https://docs.gandi.net/en/domain_names/advanced_users/secondary_nameserver.html nsdConfig = let providerSecondary = [ "37.205.15.45 ${tsigName}" # ns3.vpsfree.cz "37.205.11.85 ${tsigName}" # ns4.vpsfree.cz "2a03:3b40:fe:2be::1 ${tsigName}" # ns3.vpsfree.cz "2a03:3b40:101:4::1 ${tsigName}" # ns4.vpsfree.cz ]; in { notify = providerSecondary; provideXFR = providerSecondary; }; ns = [ { name = "@"; host = primary; } { name = "@"; host = "ns3.vpsfree.cz."; } { name = "@"; host = "ns4.vpsfree.cz."; } ]; a = optional (!ptrDomain) { name = primary; ipv4 = gate-public.hosts.gate.v4.address; ptr = null; }; aaaa = optional (!ptrDomain) { name = primary; ipv6 = gate-public.hosts.gate.v6.address; ptr = null; }; ptr = let ptrsToRecords = mapAttrsToList (suffix: target: { name = suffix; inherit target; }); v4Net = cfg.ptrNet.v4; v6Net = cfg.ptrNet.v6; v4Records = optionals (v4Net != null) (ptrsToRecords ptrNets.${v4Net}.v4.targets); v6Records = optionals (v6Net != null) (ptrsToRecords ptrNets.${v6Net}.v6.targets); in v4Records ++ v6Records; soa = mkIf ptrDomain { authorityZone = mkDefault "${domain}."; }; cname = mapAttrsToList (name: id: { name = "_acme-challenge" + optionalString (name != "@") ".${name}"; target = "${id}.acme-challenge.${domain}."; }) cfg.acme; }; })); }; config = { assertions = mapAttrsToList (name: zone: { assertion = zone.localNS.ptrNet.v4 != null -> zone.localNS.ptrNet.v6 == null; message = "zone '${name}' defined as both a v4 and v6 PTR zone"; }) config.local.ns.zones; local.ns.ptr = let zonePtrNets = name: zone: optionalAttrs (zone.localNS.ptrNet.v4 != null) { ${zone.localNS.ptrNet.v4}.v4.zone = name; } // optionalAttrs (zone.localNS.ptrNet.v6 != null) { ${zone.localNS.ptrNet.v6}.v6.zone = name; }; in mkMerge (flatten (mapAttrsToList zonePtrNets (filterAttrs (_: zone: zone.localNS.enable) config.local.ns.zones))); }; }