{ pkgs, lib, config, info, ... }: let inherit (lib) types; inherit (config.services) knot; inherit (config.users) users groups; settingsFormat = pkgs.formats.yaml { }; in { imports = [ knot/autogeree.net.nix knot/sourcephile.fr.nix ]; options.services.knot = { # WARNING: multiple settings do not merge yet # https://github.com/NixOS/nixpkgs/pull/81460#pullrequestreview-1793815097 settingsFreeform = lib.mkOption { type = types.submodule { freeformType = settingsFormat.type; }; default = { }; description = ""; }; zones = lib.mkOption { default = { }; type = types.attrsOf (types.submodule ({ ... }: { #config.domain = lib.mkDefault name; options = { data = lib.mkOption { type = types.nullOr types.lines; }; }; })); }; }; config = { systemd.services.knot.serviceConfig.ExecStartPre = lib.mapAttrsToList (domain: { data, ... }: '' +${pkgs.coreutils}/bin/install -D -o ${users.knot.name} -g ${groups."knot".name} -m 700 \ ${pkgs.writeText "${domain}.zone" data} \ /var/lib/knot/zones/${domain}.zone '') knot.zones; /* systemd.services.knot.postStart = lib.mkAfter '' PATH="/run/current-system/sw/bin:$PATH" knotc zone-freeze ${domain}. while ! knotc zone-status ${domain}. +freeze | grep -q 'freeze: yes'; do sleep 1; done knotc zone-flush ${domain}. install -o knot -g knot -m 700 ${zone} /var/lib/knot/signed/${domain}.zone knotc zone-reload ${domain}. knotc zone-thaw ${domain}. ''; */ networking.nftables.ruleset = '' table inet filter { chain input-net { meta l4proto { udp, tcp } th dport domain counter accept comment "knot: DNS" } set output-net-knot-ipv4 { type ipv4_addr; } set output-net-knot-ipv6 { type ipv6_addr; } chain output-net { skuid ${users.knot.name} \ meta l4proto { udp, tcp } th dport domain \ ip daddr @output-net-knot-ipv4 \ counter accept \ comment "knot: DNS notify" skuid ${users.knot.name} \ meta l4proto { udp, tcp } th dport domain \ ip6 daddr @output-net-knot-ipv6 \ counter accept \ comment "knot: DNS notify" } } ''; services.knot = { enable = true; extraArgs = [ "-v" ]; # https://www.knot-dns.cz/docs/2.6/html/reference.html settingsFreeform = { server.listen = [ # Listen on localhost to allow only there # dynamic updates for ACME challenges. "127.0.0.1@5353" ]; template.default = { dnssec-signing = false; # move databases below the state directory, because they need to be writable storage = "/var/lib/knot/zones"; # Input-only zone files # https://www.knot-dns.cz/docs/2.8/html/operation.html#example-3 # prevents modification of the zonefiles, since the zonefiles are immutable #zonefile-sync: -1 zonefile-load = "difference"; journal-content = "changes"; global-module = "mod-rrl/default"; }; mod-rrl.default = { rate-limit = 200; slip = 2; }; database = { journal-db = "/var/lib/knot/journal"; kasp-db = "/var/lib/knot/kasp"; timer-db = "/var/lib/knot/timer"; }; log.syslog.any = "info"; remote.local_resolver.address = "127.0.0.1@53"; remote.secondary_gandi.address = "${info.gandi.dns.secondary.axfr.ipv4}@53"; remote.secondary_muarf.address = "78.192.65.63@53"; submission.dnssec_validating_resolver = { parent = "local_resolver"; }; policy.rsa = { single-type-signing = false; ksk-shared = false; algorithm = "RSASHA256"; ksk-size = 4096; zsk-size = 2048; zsk-lifetime = "30d"; ksk-lifetime = "365d"; ksk-submission = "dnssec_validating_resolver"; }; policy.ed25519 = { single-type-signing = false; ksk-shared = false; algorithm = "ED25519"; ksk-size = 256; zsk-size = 256; zsk-lifetime = "30d"; ksk-lifetime = "365d"; cds-cdnskey-publish = "always"; ksk-submission = "dnssec_validating_resolver"; }; acl.acl_gandi = { address = info.gandi.dns.secondary.axfr.ipv4; action = "transfer"; }; acl.acl_muarf = { address = "78.192.65.63"; action = "transfer"; }; }; settings = knot.settingsFreeform; }; }; }