aboutsummaryrefslogtreecommitdiffstats
path: root/modules/by-name/dn/dns
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2025-02-23 18:34:58 +0100
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2025-03-09 13:44:17 +0100
commit116c55f3308efc8e6c0a35556404ab59539a6a99 (patch)
tree96c1bc14ea5f4b2704ed039898bde83406d52cb4 /modules/by-name/dn/dns
parenttests/email: Test the mvp (diff)
downloadnixos-server-116c55f3308efc8e6c0a35556404ab59539a6a99.zip
{modules,tests}/dns: Init
Most of the dns module was taken from: <https://github.com/nix-community/dns.nix>
Diffstat (limited to 'modules/by-name/dn/dns')
-rw-r--r--modules/by-name/dn/dns/dns/default.nix13
-rw-r--r--modules/by-name/dn/dns/dns/types/default.nix16
-rw-r--r--modules/by-name/dn/dns/dns/types/record.nix75
-rw-r--r--modules/by-name/dn/dns/dns/types/records/A.nix19
-rw-r--r--modules/by-name/dn/dns/dns/types/records/AAAA.nix19
-rw-r--r--modules/by-name/dn/dns/dns/types/records/CAA.nix42
-rw-r--r--modules/by-name/dn/dns/dns/types/records/CNAME.nix27
-rw-r--r--modules/by-name/dn/dns/dns/types/records/DKIM.nix75
-rw-r--r--modules/by-name/dn/dns/dns/types/records/DMARC.nix108
-rw-r--r--modules/by-name/dn/dns/dns/types/records/DNAME.nix15
-rw-r--r--modules/by-name/dn/dns/dns/types/records/DNSKEY.nix63
-rw-r--r--modules/by-name/dn/dns/dns/types/records/DS.nix48
-rw-r--r--modules/by-name/dn/dns/dns/types/records/HTTPS.nix5
-rw-r--r--modules/by-name/dn/dns/dns/types/records/MX.nix32
-rw-r--r--modules/by-name/dn/dns/dns/types/records/NS.nix24
-rw-r--r--modules/by-name/dn/dns/dns/types/records/OPENPGPKEY.nix18
-rw-r--r--modules/by-name/dn/dns/dns/types/records/PTR.nix24
-rw-r--r--modules/by-name/dn/dns/dns/types/records/SOA.nix65
-rw-r--r--modules/by-name/dn/dns/dns/types/records/SRV.nix51
-rw-r--r--modules/by-name/dn/dns/dns/types/records/SSHFP.nix39
-rw-r--r--modules/by-name/dn/dns/dns/types/records/SVCB.nix100
-rw-r--r--modules/by-name/dn/dns/dns/types/records/TLSA.nix50
-rw-r--r--modules/by-name/dn/dns/dns/types/records/TXT.nix24
-rw-r--r--modules/by-name/dn/dns/dns/types/records/default.nix42
-rw-r--r--modules/by-name/dn/dns/dns/types/records/dnssec.nix48
-rw-r--r--modules/by-name/dn/dns/dns/types/simple.nix9
-rw-r--r--modules/by-name/dn/dns/dns/types/zone.nix119
-rw-r--r--modules/by-name/dn/dns/dns/util/default.nix76
-rw-r--r--modules/by-name/dn/dns/module.nix45
29 files changed, 1291 insertions, 0 deletions
diff --git a/modules/by-name/dn/dns/dns/default.nix b/modules/by-name/dn/dns/dns/default.nix
new file mode 100644
index 0000000..4ce07d8
--- /dev/null
+++ b/modules/by-name/dn/dns/dns/default.nix
@@ -0,0 +1,13 @@
+#
+# SPDX-FileCopyrightText: 2019 Kirill Elagin <https://kir.elagin.me/>
+#
+# SPDX-License-Identifier: MPL-2.0 or MIT
+#
+{lib}: let
+ util = import ./util {inherit lib;};
+ types = import ./types {inherit lib util;};
+in {
+ inherit
+ types
+ ;
+}
diff --git a/modules/by-name/dn/dns/dns/types/default.nix b/modules/by-name/dn/dns/dns/types/default.nix
new file mode 100644
index 0000000..ece315f
--- /dev/null
+++ b/modules/by-name/dn/dns/dns/types/default.nix
@@ -0,0 +1,16 @@
+#
+# SPDX-FileCopyrightText: 2019 Kirill Elagin <https://kir.elagin.me/>
+#
+# SPDX-License-Identifier: MPL-2.0 or MIT
+#
+{
+ lib,
+ util,
+}: let
+ simple = {types = import ./simple.nix {inherit lib;};};
+in {
+ record = import ./record.nix {inherit lib util;};
+ records = import ./records {inherit lib util simple;};
+
+ zone = import ./zone.nix {inherit lib util simple;};
+}
diff --git a/modules/by-name/dn/dns/dns/types/record.nix b/modules/by-name/dn/dns/dns/types/record.nix
new file mode 100644
index 0000000..e992bf9
--- /dev/null
+++ b/modules/by-name/dn/dns/dns/types/record.nix
@@ -0,0 +1,75 @@
+#
+# SPDX-FileCopyrightText: 2019 Kirill Elagin <https://kir.elagin.me/>
+# SPDX-FileCopyrightText: 2021 Naïm Favier <n@monade.li>
+#
+# SPDX-License-Identifier: MPL-2.0 or MIT
+#
+{lib, ...}: let
+ inherit (lib) hasSuffix isString mkOption removeSuffix types;
+
+ recordType = rsubt: let
+ submodule = types.submodule {
+ options =
+ {
+ class = mkOption {
+ type = types.enum ["IN"];
+ default = "IN";
+ example = "IN";
+ description = "Resource record class. Only IN is supported";
+ };
+ ttl = mkOption {
+ type = types.nullOr types.ints.unsigned; # TODO: u32
+ default = null;
+ example = 300;
+ description = "Record caching duration (in seconds)";
+ };
+ }
+ // rsubt.options;
+ };
+ in
+ (
+ if rsubt ? fromString
+ then types.either types.str
+ else lib.id
+ )
+ submodule;
+
+ # name == "@" : use unqualified domain name
+ writeRecord = name: rsubt: data: let
+ data' =
+ if isString data && rsubt ? fromString
+ then
+ # add default values for the record type
+ (recordType rsubt).merge [] [
+ {
+ file = "";
+ value = rsubt.fromString data;
+ }
+ ]
+ else data;
+ name' = let
+ fname = rsubt.nameFixup or (n: _: n) name data';
+ in
+ if name == "@"
+ then name
+ else if (hasSuffix ".@" name)
+ then removeSuffix ".@" fname
+ else "${fname}.";
+ inherit (rsubt) rtype;
+ in
+ lib.concatStringsSep " " (with data';
+ [
+ name'
+ ]
+ ++ lib.optionals (ttl != null) [
+ (toString ttl)
+ ]
+ ++ [
+ class
+ rtype
+ (rsubt.dataToString data')
+ ]);
+in {
+ inherit recordType;
+ inherit writeRecord;
+}
diff --git a/modules/by-name/dn/dns/dns/types/records/A.nix b/modules/by-name/dn/dns/dns/types/records/A.nix
new file mode 100644
index 0000000..296943e
--- /dev/null
+++ b/modules/by-name/dn/dns/dns/types/records/A.nix
@@ -0,0 +1,19 @@
+#
+# SPDX-FileCopyrightText: 2019 Kirill Elagin <https://kir.elagin.me/>
+#
+# SPDX-License-Identifier: MPL-2.0 or MIT
+#
+{lib, ...}: let
+ inherit (lib) mkOption types;
+in {
+ rtype = "A";
+ options = {
+ address = mkOption {
+ type = types.str;
+ example = "26.3.0.103";
+ description = "IP address of the host";
+ };
+ };
+ dataToString = {address, ...}: address;
+ fromString = address: {inherit address;};
+}
diff --git a/modules/by-name/dn/dns/dns/types/records/AAAA.nix b/modules/by-name/dn/dns/dns/types/records/AAAA.nix
new file mode 100644
index 0000000..4717176
--- /dev/null
+++ b/modules/by-name/dn/dns/dns/types/records/AAAA.nix
@@ -0,0 +1,19 @@
+#
+# SPDX-FileCopyrightText: 2019 Kirill Elagin <https://kir.elagin.me/>
+#
+# SPDX-License-Identifier: MPL-2.0 or MIT
+#
+{lib, ...}: let
+ inherit (lib) mkOption types;
+in {
+ rtype = "AAAA";
+ options = {
+ address = mkOption {
+ type = types.str;
+ example = "4321:0:1:2:3:4:567:89ab";
+ description = "IPv6 address of the host";
+ };
+ };
+ dataToString = {address, ...}: address;
+ fromString = address: {inherit address;};
+}
diff --git a/modules/by-name/dn/dns/dns/types/records/CAA.nix b/modules/by-name/dn/dns/dns/types/records/CAA.nix
new file mode 100644
index 0000000..4b40510
--- /dev/null
+++ b/modules/by-name/dn/dns/dns/types/records/CAA.nix
@@ -0,0 +1,42 @@
+#
+# SPDX-FileCopyrightText: 2019 Kirill Elagin <https://kir.elagin.me/>
+#
+# SPDX-License-Identifier: MPL-2.0 or MIT
+#
+# RFC 8659
+{lib, ...}: let
+ inherit (lib) mkOption types;
+in {
+ rtype = "CAA";
+ options = {
+ issuerCritical = mkOption {
+ type = types.bool;
+ example = true;
+ description = ''
+ If set to '1', indicates that the corresponding property tag
+ MUST be understood if the semantics of the CAA record are to be
+ correctly interpreted by an issuer
+ '';
+ };
+ tag = mkOption {
+ type = types.enum ["issue" "issuewild" "iodef"];
+ example = "issue";
+ description = "One of the defined property tags";
+ };
+ value = mkOption {
+ type = types.str; # section 4.1.1: not limited in length
+ example = "ca.example.net";
+ description = "Value of the property";
+ };
+ };
+ dataToString = {
+ issuerCritical,
+ tag,
+ value,
+ ...
+ }: ''${
+ if issuerCritical
+ then "128"
+ else "0"
+ } ${tag} "${value}"'';
+}
diff --git a/modules/by-name/dn/dns/dns/types/records/CNAME.nix b/modules/by-name/dn/dns/dns/types/records/CNAME.nix
new file mode 100644
index 0000000..095b078
--- /dev/null
+++ b/modules/by-name/dn/dns/dns/types/records/CNAME.nix
@@ -0,0 +1,27 @@
+#
+# SPDX-FileCopyrightText: 2019 Kirill Elagin <https://kir.elagin.me/>
+#
+# SPDX-License-Identifier: MPL-2.0 or MIT
+#
+# RFC 1035, 3.3.1
+{
+ lib,
+ simple,
+ ...
+}: let
+ inherit (lib) mkOption;
+in {
+ rtype = "CNAME";
+ options = {
+ cname = mkOption {
+ type = simple.types.domain-name;
+ example = "www.test.com";
+ description = ''
+ A <domain-name> which specifies the canonical or primary name
+ for the owner. The owner name is an alias.
+ '';
+ };
+ };
+ dataToString = {cname, ...}: "${cname}";
+ fromString = cname: {inherit cname;};
+}
diff --git a/modules/by-name/dn/dns/dns/types/records/DKIM.nix b/modules/by-name/dn/dns/dns/types/records/DKIM.nix
new file mode 100644
index 0000000..31b2f67
--- /dev/null
+++ b/modules/by-name/dn/dns/dns/types/records/DKIM.nix
@@ -0,0 +1,75 @@
+#
+# SPDX-FileCopyrightText: 2020 Kirill Elagin <https://kir.elagin.me/>
+#
+# SPDX-License-Identifier: MPL-2.0 or MIT
+#
+# This is a “fake” record type, not actually part of DNS.
+# It gets compiled down to a TXT record.
+# RFC 6376
+{
+ lib,
+ util,
+ ...
+}: let
+ inherit (lib) mkOption types;
+in rec {
+ rtype = "TXT";
+ options = {
+ selector = mkOption {
+ type = types.str;
+ example = "mail";
+ description = "DKIM selector name";
+ };
+ h = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = ["sha1" "sha256"];
+ description = "Acceptable hash algorithms. Empty means all of them";
+ apply = lib.concatStringsSep ":";
+ };
+ k = mkOption {
+ type = types.nullOr types.str;
+ default = "rsa";
+ example = "rsa";
+ description = "Key type";
+ };
+ n = mkOption {
+ type = types.str;
+ default = "";
+ example = "Just any kind of arbitrary notes.";
+ description = "Notes that might be of interest to a human";
+ };
+ p = mkOption {
+ type = types.str;
+ example = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDwIRP/UC3SBsEmGqZ9ZJW3/DkMoGeLnQg1fWn7/zYtIxN2SnFCjxOCKG9v3b4jYfcTNh5ijSsq631uBItLa7od+v/RtdC2UzJ1lWT947qR+Rcac2gbto/NMqJ0fzfVjH4OuKhitdY9tf6mcwGjaNBcWToIMmPSPDdQPNUYckcQ2QIDAQAB";
+ description = "Public-key data (base64)";
+ };
+ s = mkOption {
+ type = types.listOf (types.enum ["*" "email"]);
+ default = ["*"];
+ example = ["email"];
+ description = "Service Type";
+ apply = lib.concatStringsSep ":";
+ };
+ t = mkOption {
+ type = types.listOf (types.enum ["y" "s"]);
+ default = [];
+ example = ["y"];
+ description = "Flags";
+ apply = lib.concatStringsSep ":";
+ };
+ };
+ dataToString = data: let
+ items =
+ ["v=DKIM1"]
+ ++ lib.pipe data [
+ (builtins.intersectAttrs options) # remove garbage list `_module`
+ (lib.filterAttrs (_k: v: v != null && v != ""))
+ (lib.filterAttrs (k: _v: k != "selector"))
+ (lib.mapAttrsToList (k: v: "${k}=${v}"))
+ ];
+ result = lib.concatStringsSep "; " items + ";";
+ in
+ util.writeCharacterString result;
+ nameFixup = name: self: "${self.selector}._domainkey.${name}";
+}
diff --git a/modules/by-name/dn/dns/dns/types/records/DMARC.nix b/modules/by-name/dn/dns/dns/types/records/DMARC.nix
new file mode 100644
index 0000000..e67dd12
--- /dev/null
+++ b/modules/by-name/dn/dns/dns/types/records/DMARC.nix
@@ -0,0 +1,108 @@
+#
+# SPDX-FileCopyrightText: 2020 Kirill Elagin <https://kir.elagin.me/>
+#
+# SPDX-License-Identifier: MPL-2.0 or MIT
+#
+# This is a “fake” record type, not actually part of DNS.
+# It gets compiled down to a TXT record.
+# RFC 7208
+{
+ lib,
+ util,
+ ...
+}: let
+ inherit (lib) mkOption types;
+in rec {
+ rtype = "TXT";
+ options = {
+ adkim = mkOption {
+ type = types.enum ["relaxed" "strict"];
+ default = "relaxed";
+ example = "strict";
+ description = "DKIM Identifier Alignment mode";
+ apply = builtins.substring 0 1;
+ };
+ aspf = mkOption {
+ type = types.enum ["relaxed" "strict"];
+ default = "relaxed";
+ example = "strict";
+ description = "SPF Identifier Alignment mode";
+ apply = builtins.substring 0 1;
+ };
+ fo = mkOption {
+ type = types.listOf (types.enum ["0" "1" "d" "s"]);
+ default = ["0"];
+ example = ["0" "1" "s"];
+ description = "Failure reporting options";
+ apply = lib.concatStringsSep ":";
+ };
+ p = mkOption {
+ type = types.enum ["none" "quarantine" "reject"];
+ example = "quarantine";
+ description = "Requested Mail Receiver policy";
+ };
+ pct = mkOption {
+ type = types.ints.between 0 100;
+ default = 100;
+ example = 30;
+ description = "Percentage of messages to which the DMARC policy is to be applied";
+ apply = builtins.toString;
+ };
+ rf = mkOption {
+ type = types.listOf (types.enum ["afrf"]);
+ default = ["afrf"];
+ example = ["afrf"];
+ description = "Format to be used for message-specific failure reports";
+ apply = lib.concatStringsSep ":";
+ };
+ ri = mkOption {
+ type = types.ints.unsigned; # FIXME: u32
+ default = 86400;
+ example = 12345;
+ description = "Interval requested between aggregate reports";
+ apply = builtins.toString;
+ };
+ rua = mkOption {
+ type = types.oneOf [types.str (types.listOf types.str)];
+ default = [];
+ example = "mailto:dmarc+rua@example.com";
+ description = "Addresses to which aggregate feedback is to be sent";
+ apply = val:
+ # FIXME: need to encode commas in URIs
+ if builtins.isList val
+ then lib.concatStringsSep "," val
+ else val;
+ };
+ ruf = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = ["mailto:dmarc+ruf@example.com" "mailto:another+ruf@example.com"];
+ description = "Addresses to which message-specific failure information is to be reported";
+ apply = val:
+ # FIXME: need to encode commas in URIs
+ if builtins.isList val
+ then lib.concatStringsSep "," val
+ else val;
+ };
+ sp = mkOption {
+ type = types.nullOr (types.enum ["none" "quarantine" "reject"]);
+ default = null;
+ example = "quarantine";
+ description = "Requested Mail Receiver policy for all subdomains";
+ };
+ };
+ dataToString = data: let
+ # The specification could be more clear on this, but `v` and `p` MUST
+ # be the first two tags in the record.
+ items =
+ ["v=DMARC1; p=${data.p}"]
+ ++ lib.pipe data [
+ (builtins.intersectAttrs options) # remove garbage list `_module`
+ (lib.filterAttrs (k: v: v != null && v != "" && k != "p"))
+ (lib.mapAttrsToList (k: v: "${k}=${v}"))
+ ];
+ result = lib.concatStringsSep "; " items + ";";
+ in
+ util.writeCharacterString result;
+ nameFixup = name: _self: "_dmarc.${name}";
+}
diff --git a/modules/by-name/dn/dns/dns/types/records/DNAME.nix b/modules/by-name/dn/dns/dns/types/records/DNAME.nix
new file mode 100644
index 0000000..042ce95
--- /dev/null
+++ b/modules/by-name/dn/dns/dns/types/records/DNAME.nix
@@ -0,0 +1,15 @@
+# RFC 6672
+{lib, ...}: let
+ inherit (lib) dns mkOption;
+in {
+ rtype = "DNAME";
+ options = {
+ dname = mkOption {
+ type = dns.types.domain-name;
+ example = "www.test.com";
+ description = "A <domain-name> which provides redirection from a part of the DNS name tree to another part of the DNS name tree";
+ };
+ };
+ dataToString = {dname, ...}: "${dname}";
+ fromString = dname: {inherit dname;};
+}
diff --git a/modules/by-name/dn/dns/dns/types/records/DNSKEY.nix b/modules/by-name/dn/dns/dns/types/records/DNSKEY.nix
new file mode 100644
index 0000000..86ce3a1
--- /dev/null
+++ b/modules/by-name/dn/dns/dns/types/records/DNSKEY.nix
@@ -0,0 +1,63 @@
+# SPDX-FileCopyrightText: 2020 Aluísio Augusto Silva Gonçalves <https://aasg.name>
+#
+# SPDX-License-Identifier: MPL-2.0 or MIT
+# RFC 4034, 2
+{lib, ...}: let
+ inherit (builtins) isInt split;
+ inherit (lib) concatStrings flatten mkOption types;
+
+ dnssecOptions = import ./dnssec.nix {inherit lib;};
+ inherit (dnssecOptions) mkDNSSECAlgorithmOption;
+in {
+ rtype = "DNSKEY";
+ options = {
+ flags = mkOption {
+ description = "Flags pertaining to this RR.";
+ type = types.either types.ints.u16 (types.submodule {
+ options = {
+ zoneSigningKey = mkOption {
+ description = "Whether this RR holds a zone signing key (ZSK).";
+ type = types.bool;
+ default = false;
+ };
+ secureEntryPoint = mkOption {
+ type = types.bool;
+ description = ''
+ Whether this RR holds a secure entry point.
+ In general, this means the key is a key-signing key (KSK), as opposed to a zone-signing key.
+ '';
+ default = false;
+ };
+ };
+ });
+ apply = value:
+ if isInt value
+ then value
+ else
+ (
+ if value.zoneSigningKey
+ then 256
+ else 0
+ )
+ + (
+ if value.secureEntryPoint
+ then 1
+ else 0
+ );
+ };
+ algorithm = mkDNSSECAlgorithmOption {
+ description = "Algorithm of the key referenced by this RR.";
+ };
+ publicKey = mkOption {
+ type = types.str;
+ description = "Base64-encoded public key.";
+ apply = value: concatStrings (flatten (split "[[:space:]]" value));
+ };
+ };
+ dataToString = {
+ flags,
+ algorithm,
+ publicKey,
+ ...
+ }: "${toString flags} 3 ${toString algorithm} ${publicKey}";
+}
diff --git a/modules/by-name/dn/dns/dns/types/records/DS.nix b/modules/by-name/dn/dns/dns/types/records/DS.nix
new file mode 100644
index 0000000..76fac9a
--- /dev/null
+++ b/modules/by-name/dn/dns/dns/types/records/DS.nix
@@ -0,0 +1,48 @@
+# SPDX-FileCopyrightText: 2020 Aluísio Augusto Silva Gonçalves <https://aasg.name>
+#
+# SPDX-License-Identifier: MPL-2.0 or MIT
+# RFC 4034, 5
+{lib, ...}: let
+ inherit (lib) mkOption types;
+
+ dnssecOptions = import ./dnssec.nix {inherit lib;};
+ inherit (dnssecOptions) mkRegisteredNumberOption mkDNSSECAlgorithmOption;
+
+ mkDSDigestTypeOption = args:
+ mkRegisteredNumberOption {
+ registryName = "Delegation Signer (DS) Resource Record (RR) Type Digest Algorithms";
+ numberType = types.ints.u8;
+ # These mnemonics are unofficial, unlike the DNSSEC algorithm ones.
+ mnemonics = {
+ "sha-1" = 1;
+ "sha-256" = 2;
+ "gost" = 3;
+ "sha-384" = 4;
+ };
+ };
+in {
+ rtype = "DS";
+ options = {
+ keyTag = mkOption {
+ description = "Tag computed over the DNSKEY referenced by this RR to identify it.";
+ type = types.ints.u16;
+ };
+ algorithm = mkDNSSECAlgorithmOption {
+ description = "Algorithm of the key referenced by this RR.";
+ };
+ digestType = mkDSDigestTypeOption {
+ description = "Type of the digest given in the `digest` attribute.";
+ };
+ digest = mkOption {
+ description = "Digest of the DNSKEY referenced by this RR.";
+ type = types.strMatching "[[:xdigit:]]+";
+ };
+ };
+ dataToString = {
+ keyTag,
+ algorithm,
+ digestType,
+ digest,
+ ...
+ }: "${toString keyTag} ${toString algorithm} ${toString digestType} ${digest}";
+}
diff --git a/modules/by-name/dn/dns/dns/types/records/HTTPS.nix b/modules/by-name/dn/dns/dns/types/records/HTTPS.nix
new file mode 100644
index 0000000..6e2ef3d
--- /dev/null
+++ b/modules/by-name/dn/dns/dns/types/records/HTTPS.nix
@@ -0,0 +1,5 @@
+args:
+import ./SVCB.nix args
+// {
+ rtype = "HTTPS";
+}
diff --git a/modules/by-name/dn/dns/dns/types/records/MX.nix b/modules/by-name/dn/dns/dns/types/records/MX.nix
new file mode 100644
index 0000000..c25b89c
--- /dev/null
+++ b/modules/by-name/dn/dns/dns/types/records/MX.nix
@@ -0,0 +1,32 @@
+#
+# SPDX-FileCopyrightText: 2019 Kirill Elagin <https://kir.elagin.me/>
+#
+# SPDX-License-Identifier: MPL-2.0 or MIT
+#
+# RFC 1035, 3.3.9
+{
+ lib,
+ simple,
+ ...
+}: let
+ inherit (lib) mkOption types;
+in {
+ rtype = "MX";
+ options = {
+ preference = mkOption {
+ type = types.ints.u16;
+ example = 10;
+ description = "The preference given to this RR among others at the same owner. Lower values are preferred";
+ };
+ exchange = mkOption {
+ type = simple.types.domain-name;
+ example = "smtp.example.com.";
+ description = "A <domain-name> which specifies a host willing to act as a mail exchange for the owner name";
+ };
+ };
+ dataToString = {
+ preference,
+ exchange,
+ ...
+ }: "${toString preference} ${exchange}";
+}
diff --git a/modules/by-name/dn/dns/dns/types/records/NS.nix b/modules/by-name/dn/dns/dns/types/records/NS.nix
new file mode 100644
index 0000000..ea60a91
--- /dev/null
+++ b/modules/by-name/dn/dns/dns/types/records/NS.nix
@@ -0,0 +1,24 @@
+#
+# SPDX-FileCopyrightText: 2019 Kirill Elagin <https://kir.elagin.me/>
+#
+# SPDX-License-Identifier: MPL-2.0 or MIT
+#
+# RFC 1035, 3.3.11
+{
+ lib,
+ simple,
+ ...
+}: let
+ inherit (lib) mkOption;
+in {
+ rtype = "NS";
+ options = {
+ nsdname = mkOption {
+ type = simple.types.domain-name;
+ example = "ns2.example.com";
+ description = "A <domain-name> which specifies a host which should be authoritative for the specified class and domain";
+ };
+ };
+ dataToString = {nsdname, ...}: "${nsdname}";
+ fromString = nsdname: {inherit nsdname;};
+}
diff --git a/modules/by-name/dn/dns/dns/types/records/OPENPGPKEY.nix b/modules/by-name/dn/dns/dns/types/records/OPENPGPKEY.nix
new file mode 100644
index 0000000..1f39cb9
--- /dev/null
+++ b/modules/by-name/dn/dns/dns/types/records/OPENPGPKEY.nix
@@ -0,0 +1,18 @@
+# RFC7929
+{
+ lib,
+ util,
+ ...
+}: let
+ inherit (lib) mkOption types;
+in {
+ rtype = "OPENPGPKEY";
+ options = {
+ data = mkOption {
+ type = types.str;
+ };
+ };
+
+ dataToString = {data, ...}: util.writeCharacterString data;
+ fromString = data: {inherit data;};
+}
diff --git a/modules/by-name/dn/dns/dns/types/records/PTR.nix b/modules/by-name/dn/dns/dns/types/records/PTR.nix
new file mode 100644
index 0000000..32a1913
--- /dev/null
+++ b/modules/by-name/dn/dns/dns/types/records/PTR.nix
@@ -0,0 +1,24 @@
+#
+# SPDX-FileCopyrightText: 2019 Kirill Elagin <https://kir.elagin.me/>
+#
+# SPDX-License-Identifier: MPL-2.0 or MIT
+#
+# RFC 1035, 3.3.12
+{
+ lib,
+ simple,
+ ...
+}: let
+ inherit (lib) mkOption;
+in {
+ rtype = "PTR";
+ options = {
+ ptrdname = mkOption {
+ type = simple.types.domain-name;
+ example = "4-3-2-1.dynamic.example.com.";
+ description = "A <domain-name> which points to some location in the domain name space";
+ };
+ };
+ dataToString = {ptrdname, ...}: "${ptrdname}";
+ fromString = ptrdname: {inherit ptrdname;};
+}
diff --git a/modules/by-name/dn/dns/dns/types/records/SOA.nix b/modules/by-name/dn/dns/dns/types/records/SOA.nix
new file mode 100644
index 0000000..db7436e
--- /dev/null
+++ b/modules/by-name/dn/dns/dns/types/records/SOA.nix
@@ -0,0 +1,65 @@
+#
+# SPDX-FileCopyrightText: 2019 Kirill Elagin <https://kir.elagin.me/>
+#
+# SPDX-License-Identifier: MPL-2.0 or MIT
+#
+# RFC 1035, 3.3.13
+{
+ lib,
+ simple,
+ ...
+}: let
+ inherit (lib) concatStringsSep removeSuffix replaceStrings;
+ inherit (lib) mkOption types;
+in {
+ rtype = "SOA";
+ options = {
+ nameServer = mkOption {
+ type = simple.types.domain-name;
+ example = "ns1.example.com";
+ description = "The <domain-name> of the name server that was the original or primary source of data for this zone. Don't forget the dot at the end!";
+ };
+ adminEmail = mkOption {
+ type = simple.types.domain-name;
+ example = "admin@example.com";
+ description = "An email address of the person responsible for this zone. (Note: in traditional zone files you are supposed to put a dot instead of `@` in your address; you can use `@` with this module and it is recommended to do so. Also don't put the dot at the end!)";
+ apply = s: replaceStrings ["@"] ["."] (removeSuffix "." s);
+ };
+ serial = mkOption {
+ type = types.ints.unsigned; # TODO: u32
+ example = 20;
+ description = "Version number of the original copy of the zone";
+ };
+ refresh = mkOption {
+ type = types.ints.unsigned; # TODO: u32
+ default = 24 * 60 * 60;
+ example = 7200;
+ description = "Time interval before the zone should be refreshed";
+ };
+ retry = mkOption {
+ type = types.ints.unsigned; # TODO: u32
+ default = 10 * 60;
+ example = 600;
+ description = "Time interval that should elapse before a failed refresh should be retried";
+ };
+ expire = mkOption {
+ type = types.ints.unsigned; # TODO: u32
+ default = 10 * 24 * 60 * 60;
+ example = 3600000;
+ description = "Time value that specifies the upper limit on the time interval that can elapse before the zone is no longer authoritative";
+ };
+ minimum = mkOption {
+ type = types.ints.unsigned; # TODO: u32
+ default = 60;
+ example = 60;
+ description = "Minimum TTL field that should be exported with any RR from this zone";
+ };
+ };
+ dataToString = data @ {
+ nameServer,
+ adminEmail,
+ ...
+ }: let
+ numbers = map toString (with data; [serial refresh retry expire minimum]);
+ in "${nameServer} ${adminEmail}. (${concatStringsSep " " numbers})";
+}
diff --git a/modules/by-name/dn/dns/dns/types/records/SRV.nix b/modules/by-name/dn/dns/dns/types/records/SRV.nix
new file mode 100644
index 0000000..5f558ed
--- /dev/null
+++ b/modules/by-name/dn/dns/dns/types/records/SRV.nix
@@ -0,0 +1,51 @@
+#
+# SPDX-FileCopyrightText: 2019 Kirill Elagin <https://kir.elagin.me/>
+#
+# SPDX-License-Identifier: MPL-2.0 or MIT
+#
+# RFC 2782
+{
+ lib,
+ simple,
+ ...
+}: let
+ inherit (lib) mkOption types;
+in {
+ rtype = "SRV";
+ options = {
+ service = mkOption {
+ type = types.str;
+ example = "foobar";
+ description = "The symbolic name of the desired service. Do not add the underscore!";
+ };
+ proto = mkOption {
+ type = types.str;
+ example = "tcp";
+ description = "The symbolic name of the desired protocol. Do not add the underscore!";
+ };
+ priority = mkOption {
+ type = types.ints.u16;
+ default = 0;
+ example = 0;
+ description = "The priority of this target host";
+ };
+ weight = mkOption {
+ type = types.ints.u16;
+ default = 100;
+ example = 20;
+ description = "The weight field specifies a relative weight for entries with the same priority. Larger weights SHOULD be given a proportionately higher probability of being selected";
+ };
+ port = mkOption {
+ type = types.ints.u16;
+ example = 9;
+ description = "The port on this target host of this service";
+ };
+ target = mkOption {
+ type = simple.types.domain-name;
+ example = "";
+ description = "The domain name of the target host";
+ };
+ };
+ dataToString = data: with data; "${toString priority} ${toString weight} ${toString port} ${target}";
+ nameFixup = name: self: "_${self.service}._${self.proto}.${name}";
+}
diff --git a/modules/by-name/dn/dns/dns/types/records/SSHFP.nix b/modules/by-name/dn/dns/dns/types/records/SSHFP.nix
new file mode 100644
index 0000000..1409860
--- /dev/null
+++ b/modules/by-name/dn/dns/dns/types/records/SSHFP.nix
@@ -0,0 +1,39 @@
+# RFC 4255
+{lib, ...}: let
+ inherit (lib) mkOption types;
+ inherit (builtins) attrNames;
+ algorithm = {
+ "rsa" = 1;
+ "dsa" = 2;
+ "ecdsa" = 3; # RFC 6594
+ "ed25519" = 4; # RFC 7479 / RFC 8709
+ "ed448" = 6; # RFC 8709
+ };
+ mode = {
+ "sha1" = 1;
+ "sha256" = 2; # RFC 6594
+ };
+in {
+ rtype = "SSHFP";
+ options = {
+ algorithm = mkOption {
+ example = "ed25519";
+ type = types.enum (attrNames algorithm);
+ apply = value: algorithm.${value};
+ };
+ fingerprintType = mkOption {
+ example = "sha256";
+ type = types.enum (attrNames mode);
+ apply = value: mode.${value};
+ };
+ fingerprint = mkOption {
+ type = types.str;
+ };
+ };
+ dataToString = {
+ algorithm,
+ fingerprintType,
+ fingerprint,
+ ...
+ }: "${toString algorithm} ${toString fingerprintType} ${fingerprint}";
+}
diff --git a/modules/by-name/dn/dns/dns/types/records/SVCB.nix b/modules/by-name/dn/dns/dns/types/records/SVCB.nix
new file mode 100644
index 0000000..62cbc3d
--- /dev/null
+++ b/modules/by-name/dn/dns/dns/types/records/SVCB.nix
@@ -0,0 +1,100 @@
+# rfc9460
+{lib, ...}: let
+ inherit
+ (lib)
+ concatStringsSep
+ filter
+ isInt
+ isList
+ mapAttrsToList
+ mkOption
+ types
+ ;
+
+ mkSvcParams = params:
+ concatStringsSep " " (
+ filter (s: s != "") (
+ mapAttrsToList (
+ name: value:
+ if value
+ then name
+ else if isList value
+ then "${name}=${concatStringsSep "," value}"
+ else if isInt value
+ then "${name}=${builtins.toString value}"
+ else ""
+ )
+ params
+ )
+ );
+in {
+ rtype = "SVCB";
+ options = {
+ svcPriority = mkOption {
+ example = 1;
+ type = types.ints.u16;
+ };
+ targetName = mkOption {
+ example = ".";
+ type = types.str;
+ };
+ mandatory = mkOption {
+ example = ["ipv4hint"];
+ default = null;
+ type = types.nullOr (types.nonEmptyListOf types.str);
+ };
+ alpn = mkOption {
+ example = ["h2"];
+ default = null;
+ type = types.nullOr (types.nonEmptyListOf types.str);
+ };
+ no-default-alpn = mkOption {
+ example = true;
+ default = false;
+ type = types.bool;
+ };
+ port = mkOption {
+ example = 443;
+ default = null;
+ type = types.nullOr types.port;
+ };
+ ipv4hint = mkOption {
+ example = ["127.0.0.1"];
+ default = null;
+ type = types.nullOr (types.nonEmptyListOf types.str);
+ };
+ ipv6hint = mkOption {
+ example = ["::1"];
+ default = null;
+ type = types.nullOr (types.nonEmptyListOf types.str);
+ };
+ ech = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ };
+ };
+ dataToString = {
+ svcPriority,
+ targetName,
+ mandatory ? null,
+ alpn ? null,
+ no-default-alpn ? null,
+ port ? null,
+ ipv4hint ? null,
+ ipv6hint ? null,
+ ech ? null,
+ ...
+ }: "${toString svcPriority} ${targetName} ${
+ mkSvcParams {
+ inherit
+ alpn
+ ech
+ ipv4hint
+ ipv6hint
+ mandatory
+ no-default-alpn
+ port
+ ;
+ }
+ }";
+}
diff --git a/modules/by-name/dn/dns/dns/types/records/TLSA.nix b/modules/by-name/dn/dns/dns/types/records/TLSA.nix
new file mode 100644
index 0000000..d92a29b
--- /dev/null
+++ b/modules/by-name/dn/dns/dns/types/records/TLSA.nix
@@ -0,0 +1,50 @@
+# RFC 6698
+{lib, ...}: let
+ inherit (lib) mkOption types;
+ inherit (builtins) attrNames;
+
+ certUsage = {
+ "pkix-ta" = 0;
+ "pkix-ee" = 1;
+ "dane-ta" = 2;
+ "dane-ee" = 3;
+ };
+ selectors = {
+ "cert" = 0;
+ "spki" = 1;
+ };
+ match = {
+ "exact" = 0;
+ "sha256" = 1;
+ "sha512" = 2;
+ };
+in {
+ rtype = "TLSA";
+ options = {
+ certUsage = mkOption {
+ example = "dane-ee";
+ type = types.enum (attrNames certUsage);
+ apply = value: certUsage.${value};
+ };
+ selector = mkOption {
+ example = "spki";
+ type = types.enum (attrNames selectors);
+ apply = value: selectors.${value};
+ };
+ matchingType = mkOption {
+ example = "sha256";
+ type = types.enum (attrNames match);
+ apply = value: match.${value};
+ };
+ certificate = mkOption {
+ type = types.str;
+ };
+ };
+ dataToString = {
+ certUsage,
+ selector,
+ matchingType,
+ certificate,
+ ...
+ }: "${toString certUsage} ${toString selector} ${toString matchingType} ${certificate}";
+}
diff --git a/modules/by-name/dn/dns/dns/types/records/TXT.nix b/modules/by-name/dn/dns/dns/types/records/TXT.nix
new file mode 100644
index 0000000..d605ce8
--- /dev/null
+++ b/modules/by-name/dn/dns/dns/types/records/TXT.nix
@@ -0,0 +1,24 @@
+#
+# SPDX-FileCopyrightText: 2019 Kirill Elagin <https://kir.elagin.me/>
+#
+# SPDX-License-Identifier: MPL-2.0 or MIT
+#
+# RFC 1035, 3.3.14
+{
+ lib,
+ util,
+ ...
+}: let
+ inherit (lib) mkOption types;
+in {
+ rtype = "TXT";
+ options = {
+ data = mkOption {
+ type = types.str;
+ example = "favorite drink=orange juice";
+ description = "Arbitrary information";
+ };
+ };
+ dataToString = {data, ...}: util.writeCharacterString data;
+ fromString = data: {inherit data;};
+}
diff --git a/modules/by-name/dn/dns/dns/types/records/default.nix b/modules/by-name/dn/dns/dns/types/records/default.nix
new file mode 100644
index 0000000..b6f6270
--- /dev/null
+++ b/modules/by-name/dn/dns/dns/types/records/default.nix
@@ -0,0 +1,42 @@
+#
+# SPDX-FileCopyrightText: 2019 Kirill Elagin <https://kir.elagin.me/>
+#
+# SPDX-License-Identifier: MPL-2.0 or MIT
+#
+{
+ lib,
+ util,
+ simple,
+}: let
+ inherit (lib.attrsets) genAttrs;
+
+ types = [
+ "A"
+ "AAAA"
+ "CAA"
+ "CNAME"
+ "DNAME"
+ "MX"
+ "NS"
+ "SOA"
+ "SRV"
+ "TXT"
+ "PTR"
+
+ # DNSSEC types
+ "DNSKEY"
+ "DS"
+
+ # DANE types
+ "SSHFP"
+ "TLSA"
+ "OPENPGPKEY"
+ "SVCB"
+ "HTTPS"
+
+ # Pseudo types
+ "DKIM"
+ "DMARC"
+ ];
+in
+ genAttrs types (t: import (./. + "/${t}.nix") {inherit lib simple util;})
diff --git a/modules/by-name/dn/dns/dns/types/records/dnssec.nix b/modules/by-name/dn/dns/dns/types/records/dnssec.nix
new file mode 100644
index 0000000..648f676
--- /dev/null
+++ b/modules/by-name/dn/dns/dns/types/records/dnssec.nix
@@ -0,0 +1,48 @@
+# SPDX-FileCopyrightText: 2020 Aluísio Augusto Silva Gonçalves <https://aasg.name>
+#
+# SPDX-License-Identifier: MPL-2.0 or MIT
+{lib}: let
+ inherit (builtins) attrNames isInt removeAttrs;
+ inherit (lib) mkOption types;
+in rec {
+ mkRegisteredNumberOption = {
+ registryName,
+ numberType,
+ mnemonics,
+ } @ args:
+ mkOption
+ {
+ type =
+ types.either numberType (types.enum (attrNames mnemonics))
+ // {
+ name = "registeredNumber";
+ description = "number in IANA registry '${registryName}'";
+ };
+ apply = value:
+ if isInt value
+ then value
+ else mnemonics.${value};
+ }
+ // removeAttrs args ["registryName" "numberType" "mnemonics"];
+
+ mkDNSSECAlgorithmOption = args:
+ mkRegisteredNumberOption {
+ registryName = "Domain Name System Security (DNSSEC) Algorithm Numbers";
+ numberType = types.ints.u8;
+ mnemonics = {
+ "dsa" = 3;
+ "rsasha1" = 5;
+ "dsa-nsec3-sha1" = 6;
+ "rsasha1-nsec3-sha1" = 7;
+ "rsasha256" = 8;
+ "rsasha512" = 10;
+ "ecc-gost" = 12;
+ "ecdsap256sha256" = 13;
+ "ecdsap384sha384" = 14;
+ "ed25519" = 15;
+ "ed448" = 16;
+ "privatedns" = 253;
+ "privateoid" = 254;
+ };
+ };
+}
diff --git a/modules/by-name/dn/dns/dns/types/simple.nix b/modules/by-name/dn/dns/dns/types/simple.nix
new file mode 100644
index 0000000..fece2c9
--- /dev/null
+++ b/modules/by-name/dn/dns/dns/types/simple.nix
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: 2021 Kirill Elagin <https://kir.elagin.me/>
+#
+# SPDX-License-Identifier: MPL-2.0 or MIT
+{lib}: let
+ inherit (builtins) stringLength;
+in {
+ # RFC 1035, 3.1
+ domain-name = lib.types.addCheck lib.types.str (s: stringLength s <= 255);
+}
diff --git a/modules/by-name/dn/dns/dns/types/zone.nix b/modules/by-name/dn/dns/dns/types/zone.nix
new file mode 100644
index 0000000..44ccb15
--- /dev/null
+++ b/modules/by-name/dn/dns/dns/types/zone.nix
@@ -0,0 +1,119 @@
+#
+# SPDX-FileCopyrightText: 2019 Kirill Elagin <https://kir.elagin.me/>
+# SPDX-FileCopyrightText: 2021 Naïm Favier <n@monade.li>
+#
+# SPDX-License-Identifier: MPL-2.0 or MIT
+#
+{
+ lib,
+ util,
+ simple,
+}: let
+ inherit (builtins) filter removeAttrs;
+ inherit
+ (lib)
+ concatMapStringsSep
+ concatStringsSep
+ mapAttrs
+ mapAttrsToList
+ optionalString
+ ;
+ inherit (lib) mkOption literalExample types;
+
+ inherit (import ./record.nix {inherit lib;}) recordType writeRecord;
+
+ rsubtypes = import ./records {inherit lib util simple;};
+ rsubtypes' = removeAttrs rsubtypes ["SOA"];
+
+ subzoneOptions =
+ {
+ subdomains = mkOption {
+ type = types.attrsOf subzone;
+ default = {};
+ example = {
+ www = {
+ A = [{address = "1.1.1.1";}];
+ };
+ staging = {
+ A = [{address = "1.0.0.1";}];
+ };
+ };
+ description = "Records for subdomains of the domain";
+ };
+ }
+ // mapAttrs (n: t:
+ mkOption {
+ type = types.listOf (recordType t);
+ default = [];
+ # example = [ t.example ]; # TODO: any way to auto-generate an example for submodule?
+ description = "List of ${n} records for this zone/subzone";
+ })
+ rsubtypes';
+
+ subzone = types.submodule {
+ options = subzoneOptions;
+ };
+
+ writeSubzone = name: zone: let
+ groupToString = pseudo: subt:
+ concatMapStringsSep "\n" (writeRecord name subt) zone."${pseudo}";
+ groups = mapAttrsToList groupToString rsubtypes';
+ groups' = filter (s: s != "") groups;
+
+ writeSubzone' = subname: writeSubzone "${subname}.${name}";
+ sub = concatStringsSep "\n\n" (mapAttrsToList writeSubzone' zone.subdomains);
+ in
+ concatStringsSep "\n\n" groups'
+ + optionalString (sub != "") ("\n\n" + sub);
+ zone = types.submodule ({name, ...}: {
+ options =
+ {
+ useOrigin = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Wether to use $ORIGIN and unqualified name or fqdn when exporting the zone.";
+ };
+
+ TTL = mkOption {
+ type = types.ints.unsigned;
+ default = 24 * 60 * 60;
+ example = literalExample "60 * 60";
+ description = "Default record caching duration. Sets the $TTL variable";
+ };
+ SOA = mkOption rec {
+ type = recordType rsubtypes.SOA;
+ example =
+ {
+ ttl = 24 * 60 * 60;
+ }
+ // type.example;
+ description = "SOA record";
+ };
+ }
+ // subzoneOptions;
+ });
+ renderToString = name: {
+ useOrigin,
+ TTL,
+ SOA,
+ ...
+ } @ zone:
+ if useOrigin
+ then ''
+ $ORIGIN ${name}.
+ $TTL ${toString TTL}
+
+ ${writeRecord "@" rsubtypes.SOA SOA}
+
+ ${writeSubzone "@" zone}
+ ''
+ else ''
+ $TTL ${toString TTL}
+
+ ${writeRecord name rsubtypes.SOA SOA}
+
+ ${writeSubzone name zone}
+ '';
+in {
+ inherit zone subzone renderToString;
+}
diff --git a/modules/by-name/dn/dns/dns/util/default.nix b/modules/by-name/dn/dns/dns/util/default.nix
new file mode 100644
index 0000000..59e661d
--- /dev/null
+++ b/modules/by-name/dn/dns/dns/util/default.nix
@@ -0,0 +1,76 @@
+# SPDX-FileCopyrightText: 2021 Kirill Elagin <https://kir.elagin.me/>
+#
+# SPDX-License-Identifier: MPL-2.0 or MIT
+{lib}: let
+ inherit
+ (builtins)
+ concatStringsSep
+ genList
+ stringLength
+ substring
+ ;
+ inherit
+ (lib.strings)
+ concatMapStrings
+ concatMapStringsSep
+ fixedWidthString
+ splitString
+ stringToCharacters
+ ;
+ inherit (lib.lists) filter reverseList;
+
+ /*
+ Split a string into byte chunks, such that each output String is less then or equal to
+ `n` bytes.
+
+ # Type
+
+ splitInGroupsOf :: Integer -> String -> [String]
+
+ # Arguments
+
+ n
+ : The number of bytes to put into each String.
+
+ s
+ : The String to split.
+ */
+ splitInGroupsOf = n: s: let
+ groupCount = (stringLength s - 1) / n + 1;
+ in
+ genList (i: substring (i * n) n s) groupCount;
+
+ # : str -> str
+ # Prepares a Nix string to be written to a zone file as a character-string
+ # literal: breaks it into chunks of 255 (per RFC 1035, 3.3) and encloses
+ # each chunk in quotation marks.
+ writeCharacterString = s:
+ if stringLength s <= 255
+ then ''"${s}"''
+ else concatMapStringsSep " " (x: ''"${x}"'') (splitInGroupsOf 255 s);
+
+ # : str -> str, with length 4 (zeros are padded to the left)
+ align4Bytes = fixedWidthString 4 "0";
+
+ # : int -> str -> str
+ # Expands "" to 4n zeros and aligns the rest on 4 bytes
+ align4BytesOrExpand = n: v:
+ if v == ""
+ then (fixedWidthString (4 * n) "0" "")
+ else align4Bytes v;
+
+ # : str -> [ str ]
+ # Returns the record of the ipv6 as a list
+ mkRecordAux = v6: let
+ splitted = splitString ":" v6;
+ n = 8 - builtins.length (filter (x: x != "") splitted);
+ in
+ stringToCharacters (concatMapStrings (align4BytesOrExpand n) splitted);
+
+ # : str -> str
+ # Returns the reversed record of the ipv6
+ mkReverseRecord = v6:
+ concatStringsSep "." (reverseList (mkRecordAux v6)) + ".ip6.arpa";
+in {
+ inherit writeCharacterString mkReverseRecord;
+}
diff --git a/modules/by-name/dn/dns/module.nix b/modules/by-name/dn/dns/module.nix
new file mode 100644
index 0000000..6611f8c
--- /dev/null
+++ b/modules/by-name/dn/dns/module.nix
@@ -0,0 +1,45 @@
+{
+ config,
+ lib,
+ ...
+}: let
+ cfg = config.vhack.dns;
+
+ zones = lib.debug.traceValSeqN 2 (
+ builtins.mapAttrs (name: value: {
+ data =
+ dns.types.zone.renderToString name value;
+ })
+ (lib.debug.traceValSeqN 4 cfg.zones)
+ );
+
+ dns = import ./dns {inherit lib;};
+in {
+ options.vhack.dns = {
+ enable = lib.mkEnableOption "custom dns server";
+
+ interfaces = lib.mkOption {
+ type = lib.types.listOf lib.types.str;
+ description = ''
+ A list of the interfaces to bind to.
+ '';
+ example = [
+ "192.168.1.3"
+ "2001:db8:1::3"
+ ];
+ };
+
+ zones = lib.mkOption {
+ type = lib.types.attrsOf dns.types.zone.zone;
+ description = "DNS zones";
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ services.nsd = {
+ enable = true;
+ inherit (cfg) interfaces;
+ inherit zones;
+ };
+ };
+}