{ config, pkgs, lib, ... }:

with lib;

let
  reverseDomain = domain: concatStringsSep "." (reverseList (splitString "." domain));

  acmeChallengeZonefile = domain: pkgs.writeText "${reverseDomain "_acme-challenge.${domain}"}.soa" ''
    $ORIGIN _acme-challenge.${domain}.
    $TTL 3600
    @ IN SOA ns.yggdrasil.li. hostmaster.yggdrasil.li 2023013000 10800 3600 604800 30

      IN NS  ns.yggdrasil.li.
  '';

  knotKeys = let
    dir = ./keys;
    toKeyInfo = name: v:
      if v == "regular" || v == "symlink"
      then { path = dir + "/${name}"; inherit name; }
      else null;
  in filter (v: v != null) (mapAttrsToList toKeyInfo (builtins.readDir dir));

  indentString = indentation: str: concatMapStringsSep "\n" (str: "  ${str}") (splitString "\n" (removeSuffix "\n" str));

  mkZone = {domain, path ? (./zones + "/${reverseDomain domain}.soa"), acmeDomains ? [domain], addACLs ? {}}: indentString "  " (let
    keys = acmeDomain: [(assert (config.sops.secrets ? "${acmeDomain}_acme"); "${acmeDomain}_acme_acl")] ++ (addACLs.${acmeDomain} or []);
  in ''
    - domain: ${domain}
      template: inwx_zone
      ${optionalString (acmeDomains != []) "acl: [local_acl, inwx_acl]"}
      file: ${path}
    ${concatMapStringsSep "\n" (acmeDomain: ''
      - domain: _acme-challenge.${acmeDomain}
        template: acme_zone
        acl: [${concatStringsSep ", " (keys acmeDomain)}]
        file: ${acmeChallengeZonefile acmeDomain}
    '') acmeDomains}
  '');
in {
  config = {
    fileSystems."/var/lib/knot" =
      { device = "surtr/safe/var-lib-knot";
        fsType = "zfs";
      };

    systemd.services.knot = {
      unitConfig.RequiresMountsFor = [ "/var/lib/knot" ];
      serviceConfig.LoadCredential = map ({name, ...}: "${name}.yaml:${config.sops.secrets.${name}.path}") knotKeys;
    };

    services.knot = {
      enable = true;
      keyFiles = map ({name, ...}: "/run/credentials/knot.service/${name}.yaml") knotKeys;
      extraConfig = ''
        server:
          listen: 127.0.0.1@53
          listen: ::1@53
          listen: 202.61.241.61@53
          listen: 2a03:4000:52:ada::@53

        remote:
          - id: inwx_notify
            address: 2a0a:c980::53
            via: 2a03:4000:52:ada::
          - id: recursive
            address: ::1@5353
            via: ::1
          - id: local
            address: ::1@53
            via: ::1
            key: local_key

        acl:
          - id: inwx_acl
            address: [185.181.104.96, 2a0a:c980::53]
            action: transfer
          - id: local_acl
            key: local_key
            action: update
            update-type: DS
        ${let
          toACMEACL = { name, ... }:
            if hasSuffix "_acme" name
            then
              indentString "  " ''
                - id: ${name}_acl
                  key: ${name}_key
                  action: update
              ''
            else null;
        in concatStringsSep "\n" (filter (v: v != null) (map toACMEACL knotKeys))}

        mod-rrl:
          - id: default
            rate-limit: 200
            slip: 2

        mod-cookies:
          - id: default
            secret-lifetime: 4h
            badcookie-slip: 1

        submission:
          - id: validating-resolver
            parent: recursive
            check-interval: 5m

        policy:
          - id: rsa2048
            algorithm: rsasha256
            ksk-size: 4096
            zsk-size: 2048
            ksk-submission: validating-resolver
          - id: ed25519
            algorithm: ed25519
            nsec3: on
            nsec3-iterations: 0
            signing-threads: 2
            ksk-submission: validating-resolver
          - id: ed25519_local-push
            zone-max-ttl: 1h
            algorithm: ed25519
            nsec3: on
            nsec3-iterations: 0
            signing-threads: 2
            ksk-submission: validating-resolver
            cds-cdnskey-publish: double-ds
            propagation-delay: 0
            ds-push: [local]

        template:
          - id: default
            global-module: [mod-cookies/default, mod-rrl/default]
          - id: inwx_zone
            storage: /var/lib/knot
            zonefile-sync: -1
            zonefile-load: difference-no-serial
            serial-policy: dateserial
            journal-content: all
            semantic-checks: on
            dnssec-signing: on
            dnssec-policy: ed25519
            notify: [inwx_notify]
            acl: [inwx_acl]
          - id: acme_zone
            storage: /var/lib/knot
            zonefile-sync: -1
            zonefile-load: difference-no-serial
            serial-policy: dateserial
            journal-content: all
            semantic-checks: on
            dnssec-signing: on
            dnssec-policy: ed25519_local-push

        zone:
        ${concatMapStringsSep "\n" mkZone [
          { domain = "yggdrasil.li";
            addACLs = { "yggdrasil.li" = ["ymir_acme_acl"]; };
            acmeDomains = ["surtr.yggdrasil.li" "yggdrasil.li" "etesync.yggdrasil.li" "app.etesync.yggdrasil.li"];
          }
          { domain = "nights.email";
            addACLs = { "nights.email" = ["ymir_acme_acl"]; };
          }
          { domain = "141.li";
            acmeDomains = ["webdav.141.li" "141.li"];
            addACLs = { "141.li" = ["ymir_acme_acl"]; };
          }
          { domain = "kleen.li";
            addACLs = { "kleen.li" = ["ymir_acme_acl"]; };
          }
          { domain = "xmpp.li";
            addACLs = { "xmpp.li" = ["ymir_acme_acl"]; };
          }
          { domain = "synapse.li";
            acmeDomains = ["element.synapse.li" "turn.synapse.li" "synapse.li"];
          }
          { domain = "dirty-haskell.org";
            addACLs = { "dirty-haskell.org" = ["ymir_acme_acl"]; };
          }
          { domain = "praseodym.org";
            addACLs = { "praseodym.org" = ["ymir_acme_acl"]; };
          }
          { domain = "rheperire.org";
            addACLs = { "rheperire.org" = ["ymir_acme_acl"]; };
          }
          { domain = "bouncy.email";
            acmeDomains = ["mailin.bouncy.email" "mailsub.bouncy.email" "imap.bouncy.email" "spm.bouncy.email" "mta-sts.bouncy.email" "bouncy.email"];
          }
          { domain = "kleen.consulting";
            acmeDomains = ["mailin.kleen.consulting" "mailsub.kleen.consulting" "imap.kleen.consulting" "mta-sts.kleen.consulting" "kleen.consulting"];
          }
        ]}
      '';
    };

    sops.secrets = listToAttrs (map ({name, path}: nameValuePair name {
      format = "binary";
      sopsFile = path;
    }) knotKeys);

    fileSystems."/var/lib/unbound" =
      { device = "surtr/local/var-lib-unbound";
        fsType = "zfs";
      };

    systemd.services.unbound.unitConfig.RequiresMountsFor = [ "/var/lib/unbound" ];

    services.unbound = {
      enable = true;
      resolveLocalQueries = false;
      stateDir = "/var/lib/unbound";
      localControlSocketPath = "/run/unbound/unbound.ctl";
      enableRootTrustAnchor = false;

      settings = {
        server = {
          interface = ["lo@5353"];
          prefer-ip6 = true;
          access-control = ["127.0.0.0/8 allow" "::1/128 allow"];
          root-hints = "${pkgs.dns-root-data}/root.hints";
          trust-anchor-file = "${pkgs.dns-root-data}/root.key";
          trust-anchor-signaling = false;

          num-threads = 12;
          so-reuseport = true;
          msg-cache-slabs = 16;
          rrset-cache-slabs = 16;
          infra-cache-slabs = 16;
          key-cache-slabs = 16;

          rrset-cache-size = "100m";
          msg-cache-size = "50m";
          outgoing-range = 8192;
          num-queries-per-thread = 4096;

          so-rcvbuf = "4m";
          so-sndbuf = "4m";

          # serve-expired = true;
          # serve-expired-ttl = 86400;
          # serve-expired-reply-ttl = 0;

          prefetch = true;
          prefetch-key = true;

          minimal-responses = false;

          extended-statistics = true;

          rrset-roundrobin = true;
          use-caps-for-id = true;

          do-not-query-localhost = false;
          local-zone = [
            "141.10.in-addr.arpa. transparent"
            "1.0.0.0.a.d.a.0.2.5.0.0.0.0.0.4.3.0.a.2.ip6.arpa. transparent"
            "yggdrasil. transparent"
          ];
          domain-insecure = [
            "141.10.in-addr.arpa."
            "1.0.0.0.a.d.a.0.2.5.0.0.0.0.0.4.3.0.a.2.ip6.arpa."
            "yggdrasil."
          ];
        };

        stub-zone = map (name: {
          inherit name;
          stub-addr = "2a03:4000:52:ada:1:1::";
          stub-first = true;
          stub-no-cache = true;
          stub-prime = false;
        }) ["yggdrasil." "arpa.in-addr.10.141." "1.0.0.0.a.d.a.0.2.5.0.0.0.0.0.4.3.0.a.2.ip6.arpa."];
      };
    };
  };
}