diff --git a/deployment/check/octodns/constants.nix b/deployment/check/octodns/constants.nix new file mode 100644 index 00000000..0689e6c7 --- /dev/null +++ b/deployment/check/octodns/constants.nix @@ -0,0 +1,10 @@ +{ + targetMachines = [ + "node" + ]; + pathToRoot = builtins.path { + path = ../../..; + name = "root"; + }; + pathFromRoot = "/deployment/check/octodns"; +} diff --git a/deployment/check/octodns/default.nix b/deployment/check/octodns/default.nix new file mode 100644 index 00000000..e149c8fd --- /dev/null +++ b/deployment/check/octodns/default.nix @@ -0,0 +1,21 @@ +{ + pkgs, + inputs, + sources, +}: + +pkgs.testers.runNixOSTest { + imports = [ + ../common/nixosTest.nix + ./nixosTest.nix + ]; + _module.args = { + inherit inputs sources; + modulesPath = "${builtins.toString pkgs.path}/nixos/modules"; + }; + inherit (import ./constants.nix) + targetMachines + pathToRoot + pathFromRoot + ; +} diff --git a/deployment/check/octodns/nixosTest.nix b/deployment/check/octodns/nixosTest.nix new file mode 100644 index 00000000..aa0d279f --- /dev/null +++ b/deployment/check/octodns/nixosTest.nix @@ -0,0 +1,39 @@ +{ + lib, + pkgs, + sources, + ... +}: +let + inherit (pkgs.callPackage ../../utils.nix { }) evalOption; + domain = "thatsru.de"; + inherit + (evalOption "octodns-zone" + (pkgs.callPackage ../../run { + inherit sources; + }).octodns-zone + { + inherit domain; + provider = "hetzner"; + secretFiles.token = builtins.toString ( + pkgs.writeText "hetzner-token" "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + ); + zone = { + NS = [ "ns.test.com." ]; + A = [ "203.0.113.2" ]; + }; + } + ) + validate + ; +in +{ + _class = "nixosTest"; + name = "octodns"; + + extraTestScript = '' + deployer.succeed(""" + ${lib.getExe validate} + """) + ''; +} diff --git a/deployment/flake-part.nix b/deployment/flake-part.nix index f9272a19..3ad6bf55 100644 --- a/deployment/flake-part.nix +++ b/deployment/flake-part.nix @@ -48,6 +48,10 @@ netbox-ips = import ./check/netbox-ips { inherit inputs sources system; }; + + octodns = import ./check/octodns { + inherit pkgs inputs sources; + }; }; }; } diff --git a/deployment/run/default.nix b/deployment/run/default.nix index 92801079..b8e4ebba 100644 --- a/deployment/run/default.nix +++ b/deployment/run/default.nix @@ -15,7 +15,13 @@ let str submodule ; - inherit (pkgs.callPackage ../utils.nix { }) withPackages withEnv tfApply; + inherit (pkgs.callPackage ../utils.nix { }) + mapKeys + withPackages + withEnv + withSecrets + tfApply + ; writeConfig = { system, @@ -630,4 +636,163 @@ in }; }); }; + octodns-zone = mkOption { + description = "Manage DNS records."; + type = submodule ( + octodns-zone: + let + dns = pkgs.callPackage sources."dns.nix" { }; + in + { + options = { + domain = mkOption { + type = types.str; + example = "example.tld"; + }; + secretFiles = mkOption { + type = types.attrsOf types.str; + description = "The files from which to read the secrets to use with the provider."; + example = { + token = "/path/to/token"; + }; + }; + zone = mkOption { + # FIXME: error: The option `zones."domain.tld".__toString' is read-only, but it's set multiple times. + # type = dns.lib.types.zone; + description = "The zone data to use."; + example = { + NS = [ "ns.example.tld." ]; + A = [ "12.34.56.78" ]; + }; + }; + provider = mkOption { + type = types.str; + description = "The OctoDNS provider to use, see ."; + example = "desec"; + }; + providers = mkOption { + type = types.attrsOf ( + types.submodule { + options = { + class = mkOption { + type = types.str; + }; + secrets = mkOption { + type = types.listOf types.str; + }; + }; + } + ); + default = { + desec = { + class = "octodns_desec.DesecProvider"; + secrets = [ "token" ]; + }; + hetzner = { + class = "octodns_hetzner.HetznerProvider"; + secrets = [ "token" ]; + }; + }; + }; + package = mkOption { + type = types.package; + example = "The package of the OctoDNS provider to deploy to, see ."; + default = pkgs.octodns-providers.${octodns-zone.config.provider}; + }; + packages = mkOption { + type = types.listOf types.package; + default = [ + # https://github.com/NixOS/nixpkgs/issues/429294 + ( + (pkgs.callPackage "${sources.nixpkgs-unstable}/pkgs/by-name/oc/octodns/package.nix" { }) + .withProviders + (_: [ + pkgs.octodns-providers.bind + octodns-zone.config.package + ]) + ) + ]; + }; + conf = mkOption { + type = types.path; + default = + let + inherit (octodns-zone.config) + domain + zone + providers + provider + ; + in + pkgs.writers.writeYAML "octodns-config.yaml" { + zones."${domain}." = { + sources = [ "config" ]; + targets = [ provider ]; + }; + providers = { + "${provider}" = + let + inherit (providers."${provider}") class secrets; + in + { + inherit class; + } + // (lib.genAttrs secrets (k: "env/${lib.toUpper "${provider}_${k}"}")); + config = { + file_extension = ""; + class = "octodns_bind.ZoneFileSource"; + directory = pkgs.linkFarm "zones" { + "${domain}" = dns.util.writeZone domain ( + # lib.recursiveUpdate + { + # fake SOA record to satisfy octodns + SOA = { + serial = 1970010100; + nameServer = "ns1.example.com"; + adminEmail = "admin@example.com"; + }; + } + // zone + ); + }; + }; + }; + }; + }; + validate = mkOption { + type = types.package; + default = + let + inherit (octodns-zone.config) + packages + conf + provider + secretFiles + ; + in + pkgs.writers.writeBashBin "octodns-validate.sh" (withPackages packages) '' + env ${withSecrets (mapKeys (k: lib.toUpper "${provider}_${k}") secretFiles)} \ + octodns-validate --config ${conf} --all + ''; + }; + sync = mkOption { + type = types.package; + default = + let + inherit (octodns-zone.config) + packages + conf + provider + secretFiles + ; + in + pkgs.writers.writeBashBin "octodns-sync.sh" (withPackages packages) '' + env ${withSecrets (mapKeys (k: lib.toUpper "${provider}_${k}") secretFiles)} \ + octodns-sync --config ${conf} --doit + ''; + }; + }; + } + ); + }; } diff --git a/deployment/utils.nix b/deployment/utils.nix index d923275c..1404ce01 100644 --- a/deployment/utils.nix +++ b/deployment/utils.nix @@ -64,6 +64,9 @@ rec { withEnv = environment: toString (lib.mapAttrsToList (k: v: "${k}=\"${toBash v}\"") environment); + withSecrets = + environment: toString (lib.mapAttrsToList (k: v: "${k}=\"$(cat ${v})\"") (filterNull environment)); + tfApply = { directory, diff --git a/npins/sources.json b/npins/sources.json index f9806543..a3b6eee9 100644 --- a/npins/sources.json +++ b/npins/sources.json @@ -57,6 +57,19 @@ "url": "https://api.github.com/repos/nix-community/disko/tarball/v1.12.0", "hash": "0wbx518d2x54yn4xh98cgm65wvj0gpy6nia6ra7ns4j63hx14fkq" }, + "dns.nix": { + "type": "Git", + "repository": { + "type": "GitHub", + "owner": "nix-community", + "repo": "dns.nix" + }, + "branch": "master", + "submodules": false, + "revision": "f3cb11f642d4fa6224e2b1ddfd2c3ba42e9ffea2", + "url": "https://github.com/nix-community/dns.nix/archive/f3cb11f642d4fa6224e2b1ddfd2c3ba42e9ffea2.tar.gz", + "hash": "1qjdgrn4sxa9n9wws4jg75dy1vnw761dglja8gdhvj8j2yxc27dd" + }, "flake-inputs": { "type": "GitRelease", "repository": {