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

# Take note we're also running with systemd-resolved which shouldn't really
# conflict much with established DNS servers default configuration considering
# it lives in 127.0.0.53 (not 127.0.0.1). So if you found some errors, that's
# on you. Either that or we can easily move the resolver somewhere else.
let
  inherit (config.networking) domain fqdn;
  inherit (import ../hardware/networks.nix) interfaces clientNetworks serverNetworks secondaryNameServers;

  dnsSubdomain = "ns1";
  dnsDomainName = "${dnsSubdomain}.${domain}";
  certs = config.security.acme.certs;
  dnsEmail = "hostmaster.${domain}";

  # This is the part of the SOA record. You'll have to modify it here instead
  # of modifying a zone file since it does not play well with a dynamically
  # configured server it seems.
  dnsSerialNumber = "2023020800";
	dnsRefresh = "3h";
	dnsUpdateRetry = "15m";
	dnsExpiry = "3w";
	dnsNxTTL = "3h";

  corednsServiceName = "coredns";

  domainZoneFile = pkgs.substituteAll {
    src = ../../config/coredns/${domain}.zone;
    inherit domain dnsSubdomain;
    email = dnsEmail;
    publicIPv4 = interfaces.main'.IPv4.address;
    publicIPv6 = interfaces.main'.IPv6.address;
  };

  secondaryNameserverDomains = lib.attrNames secondaryNameServers;
  secondaryNameServersIPv4 = lib.foldl'
    (total: addresses: total ++ addresses.IPv4)
    [ ]
    (lib.attrValues secondaryNameServers);
  secondaryNameServersIPv6 = lib.foldl'
    (total: addresses: total ++ addresses.IPv6)
    [ ]
    (lib.attrValues secondaryNameServers);
  secondaryNameServersIPs = secondaryNameServersIPv4 ++ secondaryNameServersIPv6;

  # The final location of the thing.
  domainZoneFile' = "/etc/coredns/zones/${domain}.zone";
in
{
  sops.secrets = let
      getKey = key: {
        inherit key;
        sopsFile = ../../secrets/secrets.yaml;
      };
      getSecrets = secrets:
        lib.mapAttrs'
          (secret: config:
            lib.nameValuePair
              "plover/${secret}"
              ((getKey secret) // config))
              secrets;
  in
    getSecrets {
      "dns/mailbox-security-key" = { };
      "dns/mailbox-security-key-record" = { };
    };

  # Generating a certificate for the DNS-over-TLS feature.
  security.acme.certs."${dnsDomainName}".postRun = ''
    systemctl restart ${corednsServiceName}.service
  '';

  # Setting up the firewall to make less things to screw up in case anything is
  # screwed up.
  networking.firewall.extraInputRules = ''
    meta l4proto {tcp, udp} th dport 53 ip saddr { ${lib.concatStringsSep ", " secondaryNameServersIPv4} } accept comment "Accept DNS queries from secondary nameservers"
    meta l4proto {tcp, udp} th dport 53 ip6 saddr { ${lib.concatStringsSep ", " secondaryNameServersIPv6} } accept comment "Accept DNS queries from secondary nameservers"
  '';

  # The main DNS server.
  services.coredns = {
    enable = true;

    # NOTE: Currently, Hetzner DNS servers does not support DNSSEC. Will need
    # to visit the following document periodically to see if they support but
    # it is doubtful since they are doubting the benefits of supporting it. :(
    #
    # From what I can tell, it seems like DNSSEC is not much embraced by the
    # major organizations yet so I'll wait with them on this one.
    #
    # https://docs.hetzner.com/dns-console/dns/general/dnssec
    config = ''
      (common) {
        forward . /etc/resolv.conf
        log
        cache
        errors
      }

      ${fqdn} {
        import common

        bind ${interfaces.internal.IPv4.address} ${interfaces.internal.IPv6.address}

        local

        acl {
          allow net ${lib.concatStringsSep " " (clientNetworks ++ serverNetworks)}
          block
        }

        template ANY ANY {
          authority "{{ .Zone }} IN SOA {{ .Zone }} ${dnsEmail} (1 60 60 60 60)"
          fallthrough
        }

        template IN A {
          answer "{{ .Zone }} IN 60 A ${interfaces.internal.IPv4.address}"
          answer "{{ .Zone }} IN 60 A ${interfaces.internal.IPv4.address}"
        }

        template IN AAAA {
          answer "{{ .Zone }} IN 60 AAAA ${interfaces.internal.IPv6.address}"
          answer "{{ .Zone }} IN 60 AAAA ${interfaces.internal.IPv6.address}"
        }
      }

      ${domain} {
        import common

        bind lo {
          # These are already taken from systemd-resolved.
          except 127.0.0.53 127.0.0.54
        }

        acl {
          # We're setting this up as a "hidden" primary server.
          allow type AXFR net ${lib.concatStringsSep " " secondaryNameServersIPs}
          allow type IXFR net ${lib.concatStringsSep " " secondaryNameServersIPs}
          block type AXFR
          block type IXFR
        }

        template IN NS {
          ${lib.concatStringsSep "\n    "
            (lib.lists.map
              (ns: ''answer "{{ .Zone }} IN NS ${ns}"'')
              secondaryNameserverDomains)}
        }

        file ${domainZoneFile'}

        transfer {
          to *
        }
      }

      tls://${domain} {
        import common

        tls {$CREDENTIALS_DIRECTORY}/cert.pem {$CREDENTIALS_DIRECTORY}/key.pem {$CREDENTIALS_DIRECTORY}/fullchain.pem
      }
    '';
  };

  # This is based from the Gitea pre-start script.
  systemd.services.${corednsServiceName} = {
    requires = [ "acme-finished-${dnsDomainName}.target" ];
    preStart =
      let
        secretsPath = path: config.sops.secrets."plover/${path}".path;
        replaceSecretBin = "${lib.getBin pkgs.replace-secret}/bin/replace-secret";
      in
      lib.mkBefore ''
        install -Dm0644 ${domainZoneFile} ${domainZoneFile'}

        ${replaceSecretBin} '#mailboxSecurityKey#' '${secretsPath "dns/mailbox-security-key"}' '${domainZoneFile'}'
        ${replaceSecretBin} '#mailboxSecurityKeyRecord#' '${secretsPath "dns/mailbox-security-key-record"}' '${domainZoneFile'}'
      '';
    serviceConfig.LoadCredential = let
      certDirectory = certs."${dnsDomainName}".directory;
    in
    [
      "cert.pem:${certDirectory}/cert.pem"
      "key.pem:${certDirectory}/key.pem"
      "fullchain.pem:${certDirectory}/fullchain.pem"
    ];
  };
}