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

with lib;
let
  domain = "fudo.org";
  hostname = "france.${domain}";
  mail-hostname = "mail.${domain}";
  host_ipv4 = "208.81.3.117";
  # Use a special IP for git.fudo.org, since it needs to be SSH-able
  link_ipv4 = "208.81.3.126";
  all-hostnames = [ ];

  acme-private-key = hostname: "/var/lib/acme/${hostname}/key.pem";
  acme-certificate = hostname: "/var/lib/acme/${hostname}/fullchain.pem";
  acme-ca = "/etc/nixos/static/letsencryptauthorityx3.pem";

  fudo-ca = "/etc/nixos/static/fudo_ca.pem";

  minecraft-data-dir = "/srv/minecraft/data";

  system-mail-directory = "/srv/mail";

in {

  boot.loader.grub = {
    enable = true;
    version = 2;
    device = "/dev/sda";
  };

  imports = [
    ../hardware-configuration.nix
    ../defaults.nix
    ./france/jabber.nix
    ./france/backplane.nix
    ./france/selby-forum.nix
  ];

  environment.systemPackages = with pkgs; [
    docker
    lxd
    multipath-tools
    nix-prefetch-docker
    powerdns
    tshark
  ];

  fudo.common = {
    # Sets some server-common settings. See /etc/nixos/fudo/profiles/...
    profile = "server";

    # Sets some common site-specific settings: gateway, monitoring, etc. See /etc/nixos/fudo/sites/...
    site = "portage";

    domain = domain;

    www-root = /srv/www;

    local-networks =
      [ "208.81.1.128/28" "208.81.3.112/28" "172.17.0.0/16" "127.0.0.0/8" ];
  };

  fudo.prometheus = {
    enable = true;
    hostname = "metrics.fudo.org";
    service-discovery-dns = {
      node = [ "node._metrics._tcp.fudo.org" ];
      postfix = [ "postfix._metrics._tcp.fudo.org" ];
      dovecot = [ "dovecot._metrics._tcp.fudo.org" ];
      rspamd = [ "rspamd._metrics._tcp.fudo.org" ];
    };
  };

  fudo.grafana = {
    enable = true;
    hostname = "monitor.fudo.org";
    smtp-username = "metrics";
    smtp-password-file = "/srv/grafana/secure/smtp.passwd";
    admin-password-file = "/srv/grafana/secure/admin.passwd";
    secret-key-file = "/srv/grafana/secure/secret.key";
    prometheus-host = "metrics.fudo.org";
    database = {
      name = "grafana";
      hostname = "localhost";
      user = "grafana";
      password-file = /srv/grafana/secure/db.passwd;
    };
  };

  # So that grafana waits for postgresql
  systemd.services.grafana.after = [ "postgresql.service" ];

  fudo.postgresql = {
    enable = true;
    ssl-private-key = (acme-private-key hostname);
    ssl-certificate = (acme-certificate hostname);
    keytab = "/srv/postgres/secure/postgres.keytab";

    # We allow connections from local networks. Auth is still required. Outside
    # of these networks, no access is allowed.
    #
    # TODO: that's probably too strict, allow kerberos connections from anywhere?
    local-networks = [
      "208.81.1.128/28"
      "208.81.3.112/28"
      "192.168.11.1/24"
      "127.0.0.1/8"
      "172.17.0.0/16"
    ];

    users = {
      fudo_git = {
        password-file = "/srv/git/secure/db.passwd";
        databases = {
          fudo_git = {
            access = "CONNECT";
            entity-access = {
              "ALL TABLES IN SCHEMA public" = "SELECT,INSERT,UPDATE,DELETE";
              "ALL SEQUENCES IN SCHEMA public" = "SELECT,UPDATE";
            };
          };
        };
      };
      grafana = {
        password-file = "/srv/grafana/secure/db.passwd";
        databases = {
          grafana = {
            access = "CONNECT";
            entity-access = {
              "ALL TABLES IN SCHEMA public" = "SELECT,INSERT,UPDATE,DELETE";
              "ALL SEQUENCES IN SCHEMA public" = "SELECT,UPDATE";
            };
          };
        };
      };
      mattermost = {
        password-file = "/srv/mattermost/secure/db.passwd";
        databases = {
          mattermost = {
            access = "CONNECT";
            entity-access = {
              "ALL TABLES IN SCHEMA public" = "SELECT,INSERT,UPDATE,DELETE";
              "ALL SEQUENCES IN SCHEMA public" = "SELECT,UPDATE";
            };
          };
        };
      };
      webmail = {
        password-file = "/srv/webmail/secure/db.passwd";
        databases = {
          webmail = {
            access = "CONNECT";
            entity-access = {
              "ALL TABLES IN SCHEMA public" = "SELECT,INSERT,UPDATE,DELETE";
              "ALL SEQUENCES IN SCHEMA public" = "SELECT,UPDATE";
            };
          };
        };
      };
      niten = { };
    };

    local-users = [ "niten" "fudo_git" ];

    databases = {
      fudo_git = { users = [ "niten" ]; };
      grafana = { users = [ "niten" ]; };
      mattermost = { users = [ "niten" ]; };
      webmail = { users = [ "niten" ]; };
    };
  };

  fudo.dns = {
    enable = true;

    identity = "france.fudo.org";

    nameservers = {
      ns1 = {
        ip-addresses = [ "208.81.3.117" ];
        ipv6-addresses = [ "2605:e200:d200:1:5054:ff:fe8c:9738" ];
        description = "Nameserver 1, france, in Winnipeg, MB, CA";
        rp = "reaper reaper.rp";
      };
      ns2 = {
        ip-addresses = [ "209.117.102.102" ];
        ipv6-addresses = [ "2001:470:1f16:40::2" ];
        description = "Nameserver 2, musashi, in Winnipeg, MB, CA";
        rp = "reaper reaper.rp";
      };
      ns3 = {
        ip-addresses = [ "104.131.53.95" ];
        ipv6-addresses = [ "2604:a880:800:10::8:7001" ];
        description =
          "Nameserver 3, ns2.henchmman21.net, in New York City, NY, US";
        rp = "reaper reaper.rp";
      };
      ns4 = {
        ip-addresses = [ "204.42.254.5" ];
        ipv6-addresses = [ "2001:418:3f4::5" ];
        description = "Nameserver 4, puck.nether.net, in Chicago, IL, US";
        rp = "reaper reaper.rp";
      };
    };

    listen-ips = [ host_ipv4 ];

    domains = { "fudo.org" = import ../fudo/fudo.org.nix { inherit config; }; };
  };

  # Not all users need access to france; don't allow LDAP-user access.
  fudo.authentication.enable = false;

  # But we DO run an LDAP auth server. Should be better-named.
  fudo.auth = {
    server = {
      enable = true;
      base = "dc=fudo,dc=org";
      organization = "Fudo";
      rootpw-file = "/srv/ldap/secure/root.pw";
      kerberos-host = "france.fudo.org";
      kerberos-keytab = "/srv/ldap/secure/ldap.keytab";

      sslCert = "/srv/ldap/france.fudo.org.pem";
      sslKey = "/srv/ldap/secure/france.fudo.org-key.pem";
      sslCACert = fudo-ca;

      # We're using fudo-generated certs for now, but we should move to ACME
      # once I can figure out how to correctly produce the ca.pem file. Until
      # then, the server will fail to start using these certs. See:
      # https://serverfault.com/a/834565

      # sslCert = (acme-bare-cert hostname);
      # sslKey = (acme-private-key hostname);
      # sslCACert = acme-ca;

      listen-uris = [ "ldap:///" "ldaps:///" "ldapi:///" ];

      users = import ../fudo/users.nix;

      groups = import ../fudo/groups.nix;

      system-users = import ../fudo/system-users.nix;
    };

    # Heimdal Kerberos server
    kdc = {
      enable = true;
      database-path = "/var/heimdal/heimdal";
      realm = "FUDO.ORG";
      mkey-file = "/var/heimdal/m-key";
      acl-file = "/etc/heimdal/kdc.acl";
      bind-addresses = [ host_ipv4 "127.0.0.1" "127.0.1.1" ];
    };
  };

  # TODO: not used yet
  fudo.acme.hostnames = all-hostnames;

  fudo.client.dns = {
    enable = true;
    ipv4 = true;
    ipv6 = true;
    user = "fudo-client";
    external-interface = "extif0";
    password-file = "/srv/client/secure/client.passwd";
  };

  fudo.mail-server = import ../fudo/email.nix { inherit config; } // {
    enableContainer = true;
    debug = false;
    monitoring = true;

    hostname = mail-hostname;

    postfix.ssl-certificate = (acme-certificate mail-hostname);
    postfix.ssl-private-key = (acme-private-key mail-hostname);
    dovecot.ssl-certificate = (acme-certificate mail-hostname);
    dovecot.ssl-private-key = (acme-private-key mail-hostname);

    state-directory = "${system-mail-directory}/var";
    mail-directory = "${system-mail-directory}/mailboxes";

    dovecot.ldap = {
      reader-dn = "cn=user_db_reader,dc=fudo,dc=org";
      reader-passwd = fileContents /srv/ldap/secure/user_db.passwd;

      # FIXME: use SSL once I can figure out Acme SSL cert CA for LDAP.
      server-urls = [ "ldap://france.fudo.org" ];
    };

    clamav.enable = true;

    dkim.signing = true;
  };

  fudo.webmail = {
    enable = true;

    sites = {
      "webmail.fudo.link" = {
        title = "Fudo Link Webmail";
        favicon = "/etc/nixos/static/fudo.link/favicon.ico";
        mail-server = mail-hostname;
        domain = "fudo.link";
        edit-mode = "Plain";
        layout-mode = "bottom";
        database = {
          name = "webmail";
          hostname = "localhost";
          user = "webmail";
          password-file = "/srv/webmail/secure/db.passwd";
        };
      };

      "webmail.test.fudo.org" = {
        title = "Fudo Webmail";
        favicon = "/etc/nixos/static/fudo.org/favicon.ico";
        mail-server = mail-hostname;
        domain = "fudo.org";
        edit-mode = "Plain";
        database = {
          name = "webmail";
          hostname = "localhost";
          user = "webmail";
          password-file = "/srv/webmail/secure/db.passwd";
        };
      };

      "webmail.fudo.org" = {
        title = "Fudo Webmail";
        favicon = "/etc/nixos/static/fudo.org/favicon.ico";
        mail-server = mail-hostname;
        domain = "fudo.org";
        edit-mode = "Plain";
        database = {
          name = "webmail";
          hostname = "localhost";
          user = "webmail";
          password-file = "/srv/webmail/secure/db.passwd";
        };
      };

      "webmail.test.selby.ca" = {
        title = "Selby Webmail";
        favicon = "/etc/nixos/static/selby.ca/favicon.ico";
        mail-server = mail-hostname;
        domain = "selby.ca";
        database = {
          name = "webmail";
          hostname = "localhost";
          user = "webmail";
          password-file = "/srv/webmail/secure/db.passwd";
        };
      };

      "webmail.selby.ca" = {
        title = "Selby Webmail";
        favicon = "/etc/nixos/static/selby.ca/favicon.ico";
        mail-server = mail-hostname;
        domain = "selby.ca";
        database = {
          name = "webmail";
          hostname = "localhost";
          user = "webmail";
          password-file = "/srv/webmail/secure/db.passwd";
        };
      };
    };
  };

  fudo.chat = {
    enable = true;

    hostname = "chat.fudo.org";
    site-name = "Fudo Chat";
    smtp-server = "mail.fudo.org";
    smtp-user = "chat";
    smtp-password-file = "/srv/mattermost/secure/smtp.passwd";
    database = {
      name = "mattermost";
      hostname = "localhost";
      user = "mattermost";
      password-file = "/srv/mattermost/secure/db.passwd";
    };
  };

  fudo.git = {
    enable = true;
    hostname = "git.fudo.org";
    site-name = "Fudo Git";
    user = "fudo_git";
    database = {
      user = "fudo_git";
      password-file = /srv/git/secure/db.passwd;
      hostname = "127.0.0.1";
      name = "fudo_git";
    };
    repository-dir = /srv/git/repo;
    state-dir = /srv/git/state;
    ssh = {
      listen-ip = link_ipv4;
      listen-port = 2222;
    };
  };

  networking = {
    hostName = hostname;

    dhcpcd.enable = false;
    useDHCP = false;

    # TODO: fix IPv6
    enableIPv6 = true;

    # Create a bridge for VMs to use
    macvlans = {
      extif0 = {
        interface = "enp4s0f0";
        mode = "bridge";
      };
      extif1 = {
        interface = "enp4s0f0";
        mode = "bridge";
      };
      intif0 = {
        interface = "enp4s0f1";
        mode = "bridge";
      };
    };

    interfaces = {
      extif0 = {
        # result of:
        # echo $FQDN-extif|md5sum|sed 's/^\(..\)\(..\)\(..\)\(..\)\(..\).*$/02:\1:\2:\3:\4:\5/'
        macAddress = "02:d4:e8:3b:10:2f";
        ipv4.addresses = [{
          address = host_ipv4;
          prefixLength = 28;
        }];
      };
      extif1 = {
        macAddress = "02:6d:e2:e1:ad:ca";
        ipv4.addresses = [{
          address = link_ipv4;
          prefixLength = 28;
        }];
      };
      intif0 = {
        # result of:
        # echo $FQDN-intif|md5sum|sed 's/^\(..\)\(..\)\(..\)\(..\)\(..\).*$/02:\1:\2:\3:\4:\5/'
        macAddress = "02:ba:ba:e9:08:21";
        ipv4.addresses = [{
          address = "192.168.11.1";
          prefixLength = 24;
        }];
      };
    };
  };

  hardware.bluetooth.enable = false;

  virtualisation = {
    docker = {
      enable = true;
      enableOnBoot = true;

      autoPrune = { enable = true; };
    };

    lxd = { enable = true; };
  };

  fileSystems = {
    "/srv/archiva" = {
      fsType = "btrfs";
      options = [ "subvol=archiva" ];
      label = "pool0";
    };
    "/srv/grafana" = {
      fsType = "btrfs";
      options = [ "subvol=grafana" ];
      label = "pool0";
    };
    "${system-mail-directory}" = {
      fsType = "btrfs";
      options = [ "subvol=mail" ];
      label = "pool0";
    };
    "/srv/gitlab" = {
      fsType = "btrfs";
      options = [ "subvol=gitlab" ];
      label = "pool0";
    };
    "/var/lib/lxd/storage-pools/pool0" = {
      fsType = "btrfs";
      label = "pool0";
      device = "/dev/disk/by-label/pool0";
    };
    "/var/lib/lxd/storage-pools/pool1" = {
      fsType = "btrfs";
      label = "pool1";
      device = "/dev/france-user/fudo-user";
    };
  };

  users = {
    extraUsers = {
      archiva = {
        isNormalUser = false;
        group = "nogroup";
        uid = 8001;
      };

      fudo_git = {
        isNormalUser = false;
        uid = 8006;
      };
    };
  };

  security.acme.certs = {
    "archiva.fudo.org".email = config.fudo.common.admin-email;
    "git.fudo.org".email = config.fudo.common.admin-email;
    "mail.fudo.org".email = config.fudo.common.admin-email;
  };

  services = {

    nginx = {
      enable = true;
      recommendedGzipSettings = true;
      recommendedOptimisation = true;
      recommendedTlsSettings = true;

      virtualHosts = {
        "archiva.fudo.org" = {
          enableACME = true;
          forceSSL = true;

          locations."/" = {
            proxyPass = "http://127.0.0.1:8001";
            extraConfig = ''
              proxy_set_header Host $host;
              proxy_set_header X-Real-IP $remote_addr;
              proxy_set_header X-Forwarded-By $server_addr:$server_port;
              proxy_set_header X-Forwarded-For $remote_addr;
              proxy_set_header X-Forwarded-Proto $scheme;
            '';
          };
        };

        # Needed to grab a cert for the mail server.
        "mail.fudo.org" = {
          enableACME = true;
          # Stopped relocating all because we need /metrics/... paths to remain unforwarded
          locations."/" = {
            return = "301 https://webmail.fudo.org$request_uri";
          };
        };
      };
    };
  };

  docker-containers = {
    archiva = {
      image = "xetusoss/archiva";
      ports = [ "127.0.0.1:8001:8080" ];
      # Ugly: name-to-uid lookup fails.
      user = toString config.users.users.archiva.uid;
      volumes = [ "/srv/archiva:/archiva-data" ];
      environment = {
        # Not directly connected to the world anyway
        SSL_ENABLED = "false";
        PROXY_BASE_URL = "https://archiva.fudo.org/";
      };
    };
  };

  ###
  # Minecraft
  ###

  fudo.minecraft-server = {
    enable = true;
    package = pkgs.minecraft-server_1_16_2;
    data-dir = minecraft-data-dir;
    world-name = "selbyland";
    motd = "Welcome to the Selby Minecraft server.";
  };
}