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

with lib;
let
  cfg = config.fudo.backplane.dns;

  lisp-pkgs = with pkgs.localLispPackages; [
    arrows
    backplane-dns
    backplane-server
    cl-sasl
    cl-xmpp
    ip-utils

    alexandria
    babel
    bordeaux-threads
    cffi
    cl-base64
    cl-json
    cl-postgres
    cl-ppcre
    cl-unicode
    cl_plus_ssl
    closer-mop
    closure-common
    cxml
    flexi-streams
    global-vars
    introspect-environment
    ironclad
    iterate
    lisp-namespace
    md5
    nibbles
    postmodern
    puri
    s-sql
    split-sequence
    trivia
    trivia_dot_balland2006
    trivia_dot_level0
    trivia_dot_level1
    trivia_dot_level2
    trivia_dot_trivial
    trivial-cltl2
    trivial-features
    trivial-garbage
    trivial-gray-streams
    type-i
    uax-15
    usocket
  ];

  backup-directory = "/var/lib/fudo/backplane/dns";

  powerdns-home = "/var/lib/powerdns";

  powerdns-conf-dir = "${powerdns-home}/conf.d";

  backplaneOpts = { ... }: {
    options = {
      host = mkOption {
        type = types.str;
        description = "Hostname of the backplane jabber server.";
      };

      role = mkOption {
        type = types.str;
        description = "Backplane XMPP role name for the DNS server.";
        default = "service-dns";
      };

      password-file = mkOption {
        type = types.str;
        description = "File containing XMPP password for backplane role.";
      };

      database = mkOption {
        type = with types; submodule databaseOpts;
        description = "Database settings for backplane server.";
      };

      cl-wrapper-package = mkOption {
        type = types.package;
        description = "Common Lisp wrapper package to use.";
        default = pkgs.lispPackages.clwrapper;
      };
    };
  };

  databaseOpts = { ... }: {
    options = {
      host = mkOption {
        type = types.str;
        description = "Hostname or IP of the PostgreSQL server.";
      };

      database = mkOption {
        type = types.str;
        description = "Database to use for DNS backplane.";
        default = "backplane_dns";
      };

      username = mkOption {
        type = types.str;
        description = "Database user for DNS backplane.";
        default = "backplane_dns";
      };

      password-file = mkOption {
        type = types.str;
        description = "File containing password for database user.";
      };
    };
  };

in {
  options.fudo.backplane.dns = {
    enable = mkEnableOption "Enable backplane dynamic DNS server.";

    port = mkOption {
      type = types.port;
      description = "Port on which to serve authoritative DNS requests.";
      default = 53;
    };

    listen-v4-addresses = mkOption {
      type = with types; listOf str;
      description = "IPv4 addresses on which to listen for dns requests.";
      default = [ "0.0.0.0" ];
    };

    listen-v6-addresses = mkOption {
      type = with types; listOf str;
      description = "IPv6 addresses on which to listen for dns requests.";
      example = [ "[abcd::1]" ];
      default = [ ];
    };

    required-services = mkOption {
      type = with types; listOf str;
      description =
        "A list of services required before the DNS server can start.";
    };

    user = mkOption {
      type = types.str;
      description = "User as which to run DNS backplane listener service.";
      default = "backplane-dns";
    };

    group = mkOption {
      type = types.str;
      description = "Group as which to run DNS backplane listener service.";
      default = "backplane-dns";
    };

    database = mkOption {
      type = with types; submodule databaseOpts;
      description = "Database settings for the DNS server.";
    };

    backplane = mkOption {
      type = with types; submodule backplaneOpts;
      description = "Backplane Jabber settings for the DNS server.";
    };
  };

  config = mkIf cfg.enable {
    users = {
      users = {
        "${cfg.user}" = {
          isSystemUser = true;
          group = cfg.group;
          createHome = true;
          home = "/var/home/${cfg.user}";
        };
        backplane-powerdns = { isSystemUser = true; };
      };

      groups = {
        "${cfg.group}" = { members = [ cfg.user ]; };
        backplane-powerdns = { members = [ "backplane-powerdns" ]; };
      };
    };

    systemd = {
      targets = {
        backplane-dns = {
          description = "Fudo DNS backplane services.";
          wantedBy = [ "multi-user.target" ];
        };
      };

      services = {

        backplane-powerdns = let
          configDir = pkgs.writeTextDir "pdns.conf" ''
            local-address=${lib.concatStringsSep ", " cfg.listen-v4-addresses}
            local-ipv6=${lib.concatStringsSep ", " cfg.listen-v6-addresses}
            local-port=${toString cfg.port}
            launch=
            include-dir=${powerdns-conf-dir}/
          '';

          psql-user = config.services.postgresql.superUser;

        in {
          unitConfig.Documentation = "man:pdns_server(1) man:pdns_control(1)";
          description = "Backplane PowerDNS name server";
          requires = [
            "postgresql.service"
            "backplane-dns-config-generator.service"
            "backplane-dns.target"
          ];
          after = [ "network.target" "postgresql.service" ];
          wantedBy = [ "multi-user.target" ];

          path = with pkgs; [ postgresql ];

          serviceConfig = {
            Restart = "on-failure";
            RestartSec = "10";
            StartLimitInterval = "0";
            PrivateDevices = true;
            # CapabilityBoundingSet="CAP_CHOWN CAP_NET_BIND_SERVICE CAP_SETGID CAP_SETUID CAP_SYS_CHROOT";
            # NoNewPrivileges=true;
            ExecStartPre = "${pkgs.coreutils}/bin/mkdir -p ${powerdns-home}";
            ExecStart =
              "${pkgs.powerdns}/bin/pdns_server --setuid=backplane-powerdns --setgid=backplane-powerdns --chroot=${powerdns-home} --socket-dir=/ --daemon=no --guardian=no --disable-syslog --write-pid=no --config-dir=${configDir}";
            ProtectSystem = "full";
            # ProtectHome=true;
            RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6";
          };
        };

        backplane-dns-config-generator = {
          description =
            "Generate postgres configuration for backplane DNS server.";
          requiredBy = [ "backplane-powerdns.service" ];
          requires = cfg.required-services;
          serviceConfig.Type = "oneshot";
          restartIfChanged = true;
          partOf = [ "backplane-dns.target" ];

          preStart = ''
            mkdir -p ${powerdns-conf-dir}
            chown backplane-powerdns:backplane-powerdns ${powerdns-conf-dir}
          '';

          # This builds the config in a bash script, to avoid storing the password
          # in the nix store at any point
          script = ''
            if [ ! -d ${powerdns-conf-dir} ]; then
            mkdir ${powerdns-conf-dir}
            fi

            TMPDIR=$(${pkgs.coreutils}/bin/mktemp -d -t pdns-XXXXXXXXXX)
            TMPCONF=$TMPDIR/pdns.local.gpgsql.conf

            if [ ! -f ${cfg.database.password-file} ]; then
            echo "${cfg.database.password-file} does not exist!"
            exit 1
            fi

            touch $TMPCONF
            chown backplane-powerdns:backplane-powerdns $TMPCONF
            chmod go-rwx $TMPCONF
            PASSWORD=$(cat ${cfg.database.password-file})
            echo "launch+=gpgsql" >> $TMPCONF
            echo "gpgsql-host=${cfg.database.host}" >> $TMPCONF
            echo "gpgsql-dbname=${cfg.database.database}" >> $TMPCONF
            echo "gpgsql-user=${cfg.database.username}" >> $TMPCONF
            echo "gpgsql-password=$PASSWORD" >> $TMPCONF
            echo "gpgsql-dnssec=yes" >> $TMPCONF

            mv $TMPCONF ${powerdns-conf-dir}/pdns.local.gpgsql.conf

            rm -rf $TMPDIR

            exit 0
          '';
        };

        backplane-dns = {
          description = "Fudo DNS Backplane Server";
          restartIfChanged = true;

          serviceConfig = {
            ExecStart =
              "${pkgs.backplane-dns-server}/bin/launch-backplane-dns.sh";
            Restart = "on-failure";
            PIDFile = "/run/backplane-dns.$USERNAME.pid";
            User = cfg.user;
            Group = cfg.group;
            StandardOutput = "journal";
          };

          environment = {
            # LD_LIBRARY_PATH = "${pkgs.openssl_1_1.out}/lib";

            FUDO_DNS_BACKPLANE_XMPP_HOSTNAME = cfg.backplane.host;
            FUDO_DNS_BACKPLANE_XMPP_USERNAME = cfg.backplane.role;
            FUDO_DNS_BACKPLANE_XMPP_PASSWORD_FILE = cfg.backplane.password-file;
            FUDO_DNS_BACKPLANE_DATABASE_HOSTNAME = cfg.backplane.database.host;
            FUDO_DNS_BACKPLANE_DATABASE_NAME = cfg.backplane.database.database;
            FUDO_DNS_BACKPLANE_DATABASE_USERNAME =
              cfg.backplane.database.username;
            FUDO_DNS_BACKPLANE_DATABASE_PASSWORD_FILE =
              cfg.backplane.database.password-file;

            # CL_SOURCE_REGISTRY = "${pkgs.localLispPackages.backplane-dns}//";

            CL_SOURCE_REGISTRY =
              lib.concatStringsSep ":" (map (pkg: "${pkg}//") lisp-pkgs);
          };

          requires = cfg.required-services;
          partOf = [ "backplane-dns.target" ];
          wantedBy = [ "multi-user.target" ];
        };
      };
    };
  };
}