diff --git a/hosts/by-name/servo/services/calibre.nix b/hosts/by-name/servo/services/calibre.nix index c39553d0..05ebb036 100644 --- a/hosts/by-name/servo/services/calibre.nix +++ b/hosts/by-name/servo/services/calibre.nix @@ -30,5 +30,5 @@ lib.mkIf false proxyPass = "http://${ip}:${builtins.toString port}"; }; }; - sane.services.trust-dns.zones."uninsane.org".inet.CNAME."calibre" = "native"; + sane.dns.zones."uninsane.org".inet.CNAME."calibre" = "native"; } diff --git a/hosts/by-name/servo/services/ejabberd.nix b/hosts/by-name/servo/services/ejabberd.nix index bec4be2e..61c12189 100644 --- a/hosts/by-name/servo/services/ejabberd.nix +++ b/hosts/by-name/servo/services/ejabberd.nix @@ -115,7 +115,7 @@ useACMEHost = "uninsane.org"; }; - sane.services.trust-dns.zones."uninsane.org".inet = { + sane.dns.zones."uninsane.org".inet = { # XXX: SRV records have to point to something with a A/AAAA record; no CNAMEs A."xmpp" = "%ANATIVE%"; CNAME."muc.xmpp" = "xmpp"; diff --git a/hosts/by-name/servo/services/email/dovecot.nix b/hosts/by-name/servo/services/email/dovecot.nix index 4f9d430b..9401cc34 100644 --- a/hosts/by-name/servo/services/email/dovecot.nix +++ b/hosts/by-name/servo/services/email/dovecot.nix @@ -24,7 +24,7 @@ enableACME = true; }; - sane.services.trust-dns.zones."uninsane.org".inet = { + sane.dns.zones."uninsane.org".inet = { CNAME."imap" = "native"; }; diff --git a/hosts/by-name/servo/services/email/postfix.nix b/hosts/by-name/servo/services/email/postfix.nix index 96c8dbc4..93639a0f 100644 --- a/hosts/by-name/servo/services/email/postfix.nix +++ b/hosts/by-name/servo/services/email/postfix.nix @@ -50,7 +50,7 @@ in }; - sane.services.trust-dns.zones."uninsane.org".inet = { + sane.dns.zones."uninsane.org".inet = { MX."@" = "10 mx.uninsane.org."; # XXX: RFC's specify that the MX record CANNOT BE A CNAME A."mx" = "185.157.162.178"; diff --git a/hosts/by-name/servo/services/freshrss.nix b/hosts/by-name/servo/services/freshrss.nix index bfde23cf..6d1768ad 100644 --- a/hosts/by-name/servo/services/freshrss.nix +++ b/hosts/by-name/servo/services/freshrss.nix @@ -59,5 +59,5 @@ # the routing is handled by services.freshrss.virtualHost }; - sane.services.trust-dns.zones."uninsane.org".inet.CNAME."rss" = "native"; + sane.dns.zones."uninsane.org".inet.CNAME."rss" = "native"; } diff --git a/hosts/by-name/servo/services/gitea.nix b/hosts/by-name/servo/services/gitea.nix index d53df76d..c2fd88dd 100644 --- a/hosts/by-name/servo/services/gitea.nix +++ b/hosts/by-name/servo/services/gitea.nix @@ -98,7 +98,7 @@ }; }; - sane.services.trust-dns.zones."uninsane.org".inet.CNAME."git" = "native"; + sane.dns.zones."uninsane.org".inet.CNAME."git" = "native"; sane.ports.ports."22" = { protocol = [ "tcp" ]; diff --git a/hosts/by-name/servo/services/goaccess.nix b/hosts/by-name/servo/services/goaccess.nix index 89340391..b6e244fc 100644 --- a/hosts/by-name/servo/services/goaccess.nix +++ b/hosts/by-name/servo/services/goaccess.nix @@ -64,5 +64,5 @@ }; }; - sane.services.trust-dns.zones."uninsane.org".inet.CNAME."sink" = "native"; + sane.dns.zones."uninsane.org".inet.CNAME."sink" = "native"; } diff --git a/hosts/by-name/servo/services/ipfs.nix b/hosts/by-name/servo/services/ipfs.nix index a3849b6d..d832c5ee 100644 --- a/hosts/by-name/servo/services/ipfs.nix +++ b/hosts/by-name/servo/services/ipfs.nix @@ -34,7 +34,7 @@ lib.mkIf false # i don't actively use ipfs anymore }; }; - sane.services.trust-dns.zones."uninsane.org".inet.CNAME."ipfs" = "native"; + sane.dns.zones."uninsane.org".inet.CNAME."ipfs" = "native"; # services.ipfs.enable = true; services.kubo.localDiscovery = true; diff --git a/hosts/by-name/servo/services/jackett.nix b/hosts/by-name/servo/services/jackett.nix index 33e6bc8b..afa2770c 100644 --- a/hosts/by-name/servo/services/jackett.nix +++ b/hosts/by-name/servo/services/jackett.nix @@ -27,6 +27,6 @@ }; }; - sane.services.trust-dns.zones."uninsane.org".inet.CNAME."jackett" = "native"; + sane.dns.zones."uninsane.org".inet.CNAME."jackett" = "native"; } diff --git a/hosts/by-name/servo/services/jellyfin.nix b/hosts/by-name/servo/services/jellyfin.nix index 276a8a5a..cfd58ca8 100644 --- a/hosts/by-name/servo/services/jellyfin.nix +++ b/hosts/by-name/servo/services/jellyfin.nix @@ -121,7 +121,7 @@ }; }; - sane.services.trust-dns.zones."uninsane.org".inet.CNAME."jelly" = "native"; + sane.dns.zones."uninsane.org".inet.CNAME."jelly" = "native"; services.jellyfin.enable = true; } diff --git a/hosts/by-name/servo/services/kiwix-serve.nix b/hosts/by-name/servo/services/kiwix-serve.nix index cc84a75f..214d263d 100644 --- a/hosts/by-name/servo/services/kiwix-serve.nix +++ b/hosts/by-name/servo/services/kiwix-serve.nix @@ -13,5 +13,5 @@ locations."/".proxyPass = "http://127.0.0.1:8013"; }; - sane.services.trust-dns.zones."uninsane.org".inet.CNAME."w" = "native"; + sane.dns.zones."uninsane.org".inet.CNAME."w" = "native"; } diff --git a/hosts/by-name/servo/services/komga.nix b/hosts/by-name/servo/services/komga.nix index 4ddedb92..2595e47a 100644 --- a/hosts/by-name/servo/services/komga.nix +++ b/hosts/by-name/servo/services/komga.nix @@ -18,5 +18,5 @@ in proxyPass = "http://127.0.0.1:${builtins.toString port}"; }; }; - sane.services.trust-dns.zones."uninsane.org".inet.CNAME."komga" = "native"; + sane.dns.zones."uninsane.org".inet.CNAME."komga" = "native"; } diff --git a/hosts/by-name/servo/services/lemmy.nix b/hosts/by-name/servo/services/lemmy.nix index 3d096de7..e8120970 100644 --- a/hosts/by-name/servo/services/lemmy.nix +++ b/hosts/by-name/servo/services/lemmy.nix @@ -54,5 +54,5 @@ in { enableACME = true; }; - sane.services.trust-dns.zones."uninsane.org".inet.CNAME."lemmy" = "native"; + sane.dns.zones."uninsane.org".inet.CNAME."lemmy" = "native"; } diff --git a/hosts/by-name/servo/services/matrix/default.nix b/hosts/by-name/servo/services/matrix/default.nix index fdb0ff74..476056d5 100644 --- a/hosts/by-name/servo/services/matrix/default.nix +++ b/hosts/by-name/servo/services/matrix/default.nix @@ -132,7 +132,7 @@ }; }; - sane.services.trust-dns.zones."uninsane.org".inet = { + sane.dns.zones."uninsane.org".inet = { CNAME."matrix" = "native"; CNAME."web.matrix" = "native"; }; diff --git a/hosts/by-name/servo/services/navidrome.nix b/hosts/by-name/servo/services/navidrome.nix index 0933a08d..ce65438f 100644 --- a/hosts/by-name/servo/services/navidrome.nix +++ b/hosts/by-name/servo/services/navidrome.nix @@ -36,5 +36,5 @@ locations."/".proxyPass = "http://127.0.0.1:4533"; }; - sane.services.trust-dns.zones."uninsane.org".inet.CNAME."music" = "native"; + sane.dns.zones."uninsane.org".inet.CNAME."music" = "native"; } diff --git a/hosts/by-name/servo/services/nixserve.nix b/hosts/by-name/servo/services/nixserve.nix index e3731ae8..5da142bf 100644 --- a/hosts/by-name/servo/services/nixserve.nix +++ b/hosts/by-name/servo/services/nixserve.nix @@ -14,7 +14,7 @@ ''; }; - sane.services.trust-dns.zones."uninsane.org".inet.CNAME."nixcache" = "native"; + sane.dns.zones."uninsane.org".inet.CNAME."nixcache" = "native"; sane.services.nixserve.enable = true; sane.services.nixserve.secretKeyFile = config.sops.secrets.nix_serve_privkey.path; diff --git a/hosts/by-name/servo/services/pleroma.nix b/hosts/by-name/servo/services/pleroma.nix index c4fd8118..223049a1 100644 --- a/hosts/by-name/servo/services/pleroma.nix +++ b/hosts/by-name/servo/services/pleroma.nix @@ -182,7 +182,7 @@ }; }; - sane.services.trust-dns.zones."uninsane.org".inet.CNAME."fed" = "native"; + sane.dns.zones."uninsane.org".inet.CNAME."fed" = "native"; sops.secrets."pleroma_secrets" = { owner = config.users.users.pleroma.name; diff --git a/hosts/by-name/servo/services/transmission.nix b/hosts/by-name/servo/services/transmission.nix index c7762c5e..19e8ac56 100644 --- a/hosts/by-name/servo/services/transmission.nix +++ b/hosts/by-name/servo/services/transmission.nix @@ -75,6 +75,6 @@ }; }; - sane.services.trust-dns.zones."uninsane.org".inet.CNAME."bt" = "native"; + sane.dns.zones."uninsane.org".inet.CNAME."bt" = "native"; } diff --git a/hosts/by-name/servo/services/trust-dns.nix b/hosts/by-name/servo/services/trust-dns.nix index 66b35759..ce71087e 100644 --- a/hosts/by-name/servo/services/trust-dns.nix +++ b/hosts/by-name/servo/services/trust-dns.nix @@ -1,4 +1,4 @@ -{ config, pkgs, ... }: +{ config, lib, pkgs, ... }: { sane.services.trust-dns.enable = true; @@ -11,7 +11,7 @@ ]; sane.services.trust-dns.quiet = true; - sane.services.trust-dns.zones."uninsane.org".TTL = 900; + sane.dns.zones."uninsane.org".TTL = 900; # SOA record structure: # SOA MNAME RNAME (... rest) @@ -21,7 +21,7 @@ # Refresh = how frequently secondary NS should query master # Retry = how long secondary NS should wait until re-querying master after a failure (must be < Refresh) # Expire = how long secondary NS should continue to reply to queries after master fails (> Refresh + Retry) - sane.services.trust-dns.zones."uninsane.org".inet = { + sane.dns.zones."uninsane.org".inet = { SOA."@" = '' ns1.uninsane.org. admin-dns.uninsane.org. ( 2022122101 ; Serial @@ -51,7 +51,9 @@ ]; }; - sane.services.trust-dns.zones."uninsane.org".file = "uninsane.org.zone"; + # we need trust-dns to load our zone by relative path instead of /nix/store path + # because we generate it at runtime. + sane.services.trust-dns.zones."uninsane.org".file = lib.mkForce "uninsane.org.zone"; sane.services.trust-dns.zonedir = null; sane.services.trust-dns.package = @@ -60,7 +62,7 @@ zone-dir = "/var/lib/trust-dns"; zone-wan = "${zone-dir}/wan/uninsane.org.zone"; zone-lan = "${zone-dir}/lan/uninsane.org.zone"; - zone-template = pkgs.writeText "uninsane.org.zone.in" config.sane.services.trust-dns.generatedZones."uninsane.org"; + zone-template = pkgs.writeText "uninsane.org.zone.in" config.sane.services.trust-dns.zones."uninsane.org".text; in pkgs.writeShellScriptBin "named" '' # compute wan/lan values mkdir -p ${zone-dir}/{ovpn,wan,lan} diff --git a/modules/default.nix b/modules/default.nix index e5bc063e..1e09ea95 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -2,6 +2,7 @@ { imports = [ + ./dns.nix ./feeds.nix ./fs ./ids.nix diff --git a/modules/dns.nix b/modules/dns.nix new file mode 100644 index 00000000..8adfba01 --- /dev/null +++ b/modules/dns.nix @@ -0,0 +1,146 @@ +{ config, lib, pkgs, ... }: + +with builtins; +let + cfg = config.sane.dns; + toml = pkgs.formats.toml { }; + recordFormatters = { + # quote rules for zone files: + # - any character may be encoded by `\DDD`, where `DDD` represents its ascii value in base 8. + # - any non-digit `X` may be encoded by `\X`. + # - stated in: : 5.1 Format + # - visible in + # for us, we can just replace `\` => `\\ and `"` -> `\"` + TXT = value: "\"" + (lib.escape [ "\\" "\"" ] value) + "\""; + }; + # proto: "INET", etc + # rrtype: "TXT", "A", "CNAME", etc + fmtRecord = proto: rrtype: name: value: + let + formatter = recordFormatters."${rrtype}" or lib.id; + in + "${name}\t${proto}\t${rrtype}\t${formatter value}"; + fmtRecordList = proto: rrtype: name: values: concatStringsSep + "\n" + (map (fmtRecord proto rrtype name) values) + ; + fmtRecordAttrs = proto: rrtype: rrAttrs: + concatStringsSep + "\n" + ( + attrValues ( + mapAttrs + (name: fmtRecordList proto rrtype name) + rrAttrs + ) + ); + # format other .zone files to include into this one + fmtIncludes = paths: concatStringsSep + "\n" + (map (path: "$INCLUDE ${path}") paths); + + genZone = zcfg: '' + $TTL ${toString zcfg.TTL} + ${fmtRecordAttrs "IN" "SOA" zcfg.inet.SOA} + ${fmtRecordAttrs "IN" "A" zcfg.inet.A} + ${fmtRecordAttrs "IN" "CNAME" zcfg.inet.CNAME} + ${fmtRecordAttrs "IN" "MX" zcfg.inet.MX} + ${fmtRecordAttrs "IN" "NS" zcfg.inet.NS} + ${fmtRecordAttrs "IN" "SRV" zcfg.inet.SRV} + ${fmtRecordAttrs "IN" "TXT" zcfg.inet.TXT} + ${fmtIncludes zcfg.include} + ${zcfg.extraConfig} + ''; + + # (listOf ty) type which also accepts single-assignment of `ty`. + # it's used to allow the user to write: + # CNAME."foo" = "bar"; + # as shorthand for + # CNAME."foo" = [ "bar" ]; + listOrUnit = with lib; ty: types.coercedTo ty (elem: [ elem ]) (types.listOf ty); +in +{ + options = { + sane.dns = with lib; { + zones = mkOption { + type = types.attrsOf (types.submodule { + options = { + name = mkOption { + type = types.nullOr types.str; + description = "zone name. defaults to the attribute name in zones"; + default = null; + }; + TTL = mkOption { + type = types.int; + description = "default TTL"; + default = 3600; + }; + include = mkOption { + type = types.listOf types.str; + description = "paths of other zone files to $INCLUDE into this one"; + default = []; + }; + extraConfig = mkOption { + type = types.lines; + description = "extra lines to append to the zone file"; + default = ""; + }; + inet = { + SOA = mkOption { + type = types.attrsOf (listOrUnit types.str); + description = "Start of Authority record(s)"; + default = {}; + }; + A = mkOption { + type = types.attrsOf (listOrUnit types.str); + description = "IPv4 address record(s)"; + default = {}; + }; + CNAME = mkOption { + type = types.attrsOf (listOrUnit types.str); + description = "canonical name record(s)"; + default = {}; + }; + MX = mkOption { + type = types.attrsOf (listOrUnit types.str); + description = "mail exchanger record(s)"; + default = {}; + }; + NS = mkOption { + type = types.attrsOf (listOrUnit types.str); + description = "name server record(s)"; + default = {}; + }; + SRV = mkOption { + type = types.attrsOf (listOrUnit types.str); + description = "service record(s)"; + default = {}; + }; + TXT = mkOption { + type = types.attrsOf (listOrUnit types.str); + description = "text record(s)"; + default = {}; + }; + }; + + file = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + instead of using the generated zone file, use the specified path (user should populate the file specified here). + ''; + }; + }; + }); + default = {}; + description = "Declarative zone config"; + }; + }; + }; + + config = { + sane.services.trust-dns.zones = mapAttrs (_name: zcfg: { + text = genZone zcfg; + }) cfg.zones; + }; +} diff --git a/modules/services/trust-dns.nix b/modules/services/trust-dns.nix index ae656872..6aa3a8e1 100644 --- a/modules/services/trust-dns.nix +++ b/modules/services/trust-dns.nix @@ -7,50 +7,6 @@ with lib; let cfg = config.sane.services.trust-dns; toml = pkgs.formats.toml { }; - recordFormatters = { - # quote rules for zone files: - # - any character may be encoded by `\DDD`, where `DDD` represents its ascii value in base 8. - # - any non-digit `X` may be encoded by `\X`. - # - stated in: : 5.1 Format - # - visible in - # for us, we can just replace `\` => `\\ and `"` -> `\"` - TXT = value: "\"" + (lib.escape [ "\\" "\"" ] value) + "\""; - }; - fmtRecord = proto: rrtype: name: value: - let - formatter = recordFormatters."${rrtype}" or lib.id; - in - "${name}\t${proto}\t${rrtype}\t${formatter value}"; - fmtRecordList = proto: rrtype: name: values: concatStringsSep - "\n" - (map (fmtRecord proto rrtype name) values) - ; - fmtRecordAttrs = proto: rrtype: rrAttrs: - concatStringsSep - "\n" - ( - attrValues ( - mapAttrs - (name: fmtRecordList proto rrtype name) - rrAttrs - ) - ); - fmtIncludes = paths: concatStringsSep - "\n" - (map (path: "$INCLUDE ${path}") paths); - - genZone = zcfg: '' - $TTL ${toString zcfg.TTL} - ${fmtRecordAttrs "IN" "SOA" zcfg.inet.SOA} - ${fmtRecordAttrs "IN" "A" zcfg.inet.A} - ${fmtRecordAttrs "IN" "CNAME" zcfg.inet.CNAME} - ${fmtRecordAttrs "IN" "MX" zcfg.inet.MX} - ${fmtRecordAttrs "IN" "NS" zcfg.inet.NS} - ${fmtRecordAttrs "IN" "SRV" zcfg.inet.SRV} - ${fmtRecordAttrs "IN" "TXT" zcfg.inet.TXT} - ${fmtIncludes zcfg.include} - ${zcfg.extraConfig} - ''; configFile = toml.generate "trust-dns.toml" { listen_addrs_ipv4 = cfg.listenAddrsIPv4; @@ -58,20 +14,10 @@ let mapAttrs (zname: zcfg: rec { zone = if zcfg.name == null then zname else zcfg.name; zone_type = "Primary"; - file = if zcfg.file == null then - pkgs.writeText "${zone}.zone" (genZone zcfg) - else - zcfg.file; + file = zcfg.file; }) cfg.zones ); }; - - # (listOf ty) type which also accepts single-assignment of `ty`. - # it's used to allow the user to write: - # CNAME."foo" = "bar"; - # as shorthand for - # CNAME."foo" = [ "bar" ]; - listOrUnit = ty: types.coercedTo ty (elem: [ elem ]) (types.listOf ty); in { options = { @@ -106,89 +52,37 @@ in }; # reference zones = mkOption { - type = types.attrsOf (types.submodule { + type = types.attrsOf (types.submodule ({ config, name, ... }: { options = { name = mkOption { type = types.nullOr types.str; description = "zone name. defaults to the attribute name in zones"; + default = name; + }; + text = mkOption { + type = types.nullOr types.lines; default = null; }; - TTL = mkOption { - type = types.int; - description = "default TTL"; - default = 3600; - }; - include = mkOption { - type = types.listOf types.str; - description = "paths of other zone files to $INCLUDE into this one"; - default = []; - }; - extraConfig = mkOption { - type = types.lines; - description = "extra lines to append to the zone file"; - default = ""; - }; - inet = { - SOA = mkOption { - type = types.attrsOf (listOrUnit types.str); - description = "Start of Authority record(s)"; - default = {}; - }; - A = mkOption { - type = types.attrsOf (listOrUnit types.str); - description = "IPv4 address record(s)"; - default = {}; - }; - CNAME = mkOption { - type = types.attrsOf (listOrUnit types.str); - description = "canonical name record(s)"; - default = {}; - }; - MX = mkOption { - type = types.attrsOf (listOrUnit types.str); - description = "mail exchanger record(s)"; - default = {}; - }; - NS = mkOption { - type = types.attrsOf (listOrUnit types.str); - description = "name server record(s)"; - default = {}; - }; - SRV = mkOption { - type = types.attrsOf (listOrUnit types.str); - description = "service record(s)"; - default = {}; - }; - TXT = mkOption { - type = types.attrsOf (listOrUnit types.str); - description = "text record(s)"; - default = {}; - }; - }; - file = mkOption { - type = types.nullOr types.str; - default = null; + type = types.nullOr (types.either types.path types.str); description = '' - instead of using the generated zone file, use the specified path (user should populate the file specified here). + path to a .zone file. + if omitted, will be generated from the `text` option. ''; }; }; - }); + + config = { + file = lib.mkIf (config.text != null) (pkgs.writeText "${config.name}.zone" config.text); + }; + })); default = {}; description = "Declarative zone config"; }; - - generatedZones = mkOption { - type = types.attrsOf types.str; - description = "generated zone text for each zone"; - }; }; }; config = mkIf cfg.enable { - sane.services.trust-dns.generatedZones = mapAttrs (zone: zcfg: genZone zcfg) cfg.zones; - sane.ports.ports."53" = { protocol = [ "udp" "tcp" ]; visibleTo.lan = true;