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

with lib;

{
  config = {
    services.matrix-synapse = {
      enable = true;

      settings = {
        enable_metrics = true;

        enable_registration = true;
        allow_guest_access = false;

        server_name = "synapse.li";

        listeners = [
          { bind_addresses = ["::1" "127.0.0.1"];
            port = 8008;
            resources = [
              { names = [ "client" "federation" ];
                compress = false;
              }
            ];
            tls = false;
            type = "http";
            x_forwarded = true;
          }
        ];

        tls_certificate_path = "/run/credentials/matrix-synapse.service/synapse.li.pem";
        tls_private_key_path = "/run/credentials/matrix-synapse.service/synapse.li.key.pem";

        turn_uris = ["turn:turn.synapse.li?transport=udp" "turn:turn.synapse.li?transport=tcp"];
        turn_user_lifetime = "1h";

        refreshable_access_token_lifetime = "5m";
        # nonrefreshable_access_token_lifetime = "1w"; # TODO: uncomment once all (relevant) clients have support for refreshable tokens
        refresh_token_lifetime = "1w";
        registration_requires_token = true;

        admin_contact = "mailto:matrix-admin@yggdrasil.li";

        url_preview_enabled = true;
        url_preview_ip_range_blacklist = [
          "127.0.0.0/8" "10.0.0.0/8" "172.16.0.0/12" "192.168.0.0/16"
          "100.64.0.0/10" "192.0.0.0/24" "169.254.0.0/16"
          "192.88.99.0/24" "198.18.0.0/15" "192.0.2.0/24"
          "198.51.100.0/24" "203.0.113.0/24" "224.0.0.0/4" "::1/128"
          "fe80::/10" "fc00::/7" "2001:db8::/32" "ff00::/8"
          "fec0::/10" "2a03:4000:52:ada::/64"
        ];
        url_preview_ip_range_whitelist = [
          "2a03:4000:52:ada::/128"
        ];

        max_upload_size = "500M";

        suppress_key_server_warning = true;
      };

      extraConfigFiles = [
        "/run/credentials/matrix-synapse.service/registration.yaml"
        "/run/credentials/matrix-synapse.service/turn-secret.yaml"
      ];
    };
    sops.secrets."matrix-synapse-registration.yaml" = {
      format = "binary";
      sopsFile = ./registration.yaml;
    };
    sops.secrets."matrix-synapse-turn-secret.yaml" = {
      format = "binary";
      sopsFile = ./coturn-auth-secret.yaml;
    };

    systemd.services.matrix-synapse = {
      wants = ["postgresql.service"];

      serviceConfig = {
        LoadCredential = [
          "synapse.li.key.pem:${config.security.acme.certs."synapse.li".directory}/key.pem"
          "synapse.li.pem:${config.security.acme.certs."synapse.li".directory}/fullchain.pem"
          "registration.yaml:${config.sops.secrets."matrix-synapse-registration.yaml".path}"
          "turn-secret.yaml:${config.sops.secrets."matrix-synapse-turn-secret.yaml".path}"
        ];

        RuntimeDirectory = "matrix-synapse";
        StateDirectory = "matrix-synapse";

        PrivateTmp = true;
        PrivateDevices = true;

        CapabilityBoundingSet = [];
        AmbientCapabilities = [];

        ProtectSystem = "strict";
        ProtectKernelTunables = true;
        ProtectKernelModules = true;
        ProtectControlGroups = true;
        ProtectClock = true;
        ProtectHostname = true;

        ProtectHome = "tmpfs";
        ProtectKernelLogs = true;

        ProtectProc = "invisible";
        ProcSubset = "pid";

        PrivateNetwork = false;
        RestrictAddressFamilies = ["AF_INET" "AF_INET6" "AF_UNIX"];
        IPAddressAllow = "any";

        SystemCallArchitectures = "native";
        SystemCallFilter = ["@system-service" "~@privileged @resources @obsolete"];
        
        RestrictSUIDSGID = true;
        RemoveIPC = true;
        NoNewPrivileges = true;
        RestrictRealtime = true;
        RestrictNamespaces = true;
        LockPersonality = true;
        PrivateUsers = true;
        MemoryDenyWriteExecute = false;

        ReadWritePaths = ["/var/run/postgresql"];
      };
    };

    services.nginx = {
      recommendedProxySettings = true;

      upstreams = {
        "matrix-synapse" = {
          servers = {
            "127.0.0.1:8008" = {};
          };
        };
      };

      virtualHosts."synapse.li" = {
        forceSSL = true;
        sslCertificate = "/run/credentials/nginx.service/synapse.li.pem";
        sslCertificateKey = "/run/credentials/nginx.service/synapse.li.key.pem";
        sslTrustedCertificate = "/run/credentials/nginx.service/synapse.li.chain.pem";
        listen = [
          { addr = "0.0.0.0"; port = 443; ssl = true; }
          { addr = "[::0]"; port = 443; ssl = true; }
          { addr = "0.0.0.0"; port = 8448; ssl = true; }
          { addr = "[::0]"; port = 8448; ssl = true; }
        ];
        extraConfig = ''
          add_header Strict-Transport-Security "max-age=63072000" always;
        '';
        locations = let
          corsHeaders = ''
            add_header Access-Control-Allow-Origin '*';
            add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS';
            add_header Access-Control-Allow-Headers 'X-Requested-With, Content-Type, Authorization';
            add_header Access-Control-Max-Age 7200;
          '';
        in listToAttrs (map (n: nameValuePair n { proxyPass = "http://matrix-synapse"; extraConfig = "client_max_body_size 500M;"; }) ["/_matrix" "/_synapse/client" "/_synapse/admin"]) // {
          "= /.well-known/matrix/server" = {
            extraConfig = ''
              default_type application/json;
              ${corsHeaders}
            '';
            return = "200 '${builtins.toJSON {
              "m.server" = "synapse.li:443"; 
            }}'";
          };
          "= /.well-known/matrix/client" = {
            extraConfig = ''
              default_type application/json;
              ${corsHeaders}
            '';
            return = "200 '${builtins.toJSON {
              "m.homeserver" = { "base_url" = "https://synapse.li"; };
              "m.identity_server" = { "base_url" = "https://vector.im"; };
            }}'";
          };
          "/".return = "301 https://element.synapse.li$request_uri";
        };
      };

      virtualHosts."element.synapse.li" = {
        forceSSL = true;
        sslCertificate = "/run/credentials/nginx.service/element.synapse.li.pem";
        sslCertificateKey = "/run/credentials/nginx.service/element.synapse.li.key.pem";
        sslTrustedCertificate = "/run/credentials/nginx.service/element.synapse.li.chain.pem";
        extraConfig = ''
          add_header Strict-Transport-Security "max-age=63072000" always;
          
          add_header X-Frame-Options SAMEORIGIN;
          add_header X-Content-Type-Options nosniff;
          add_header X-XSS-Protection "1; mode=block";
          add_header Content-Security-Policy "frame-ancestors 'none'";
        '';

        root = pkgs.element-web.override {
          conf = {
            default_server_config."m.homeserver" = {
              "base_url" = "https://synapse.li";
              "server_name" = "synapse.li";
            };
          };
        };
      };
    };

    security.acme.domains = {
      "element.synapse.li" = {
        zone = "synapse.li";
        certCfg = {
          postRun = ''
            ${pkgs.systemd}/bin/systemctl try-restart nginx.service
          '';
        };
      };
      "turn.synapse.li" = {
        zone = "synapse.li";
        certCfg = {
          server = "https://acme.zerossl.com/v2/DV90";
          extraLegoFlags = [
            "--cert.timeout" "300"
          ];
          postRun = ''
            ${pkgs.systemd}/bin/systemctl try-restart coturn.service
          '';
        };
      };
      "synapse.li".certCfg = {
        postRun = ''
          ${pkgs.systemd}/bin/systemctl try-restart nginx.service
        '';       
      };
    };

    systemd.services.nginx = {
      serviceConfig = {
        LoadCredential = [
          "synapse.li.key.pem:${config.security.acme.certs."synapse.li".directory}/key.pem"
          "synapse.li.pem:${config.security.acme.certs."synapse.li".directory}/fullchain.pem"
          "synapse.li.chain.pem:${config.security.acme.certs."synapse.li".directory}/chain.pem"

          "element.synapse.li.key.pem:${config.security.acme.certs."element.synapse.li".directory}/key.pem"
          "element.synapse.li.pem:${config.security.acme.certs."element.synapse.li".directory}/fullchain.pem"
          "element.synapse.li.chain.pem:${config.security.acme.certs."element.synapse.li".directory}/chain.pem"
        ];
      };
    };

    services.coturn = rec {
      enable = true;
      no-cli = true;
      no-tcp-relay = true;
      min-port = 49000;
      max-port = 50000;
      use-auth-secret = true;
      static-auth-secret-file = config.sops.secrets."coturn-auth-secret".path;
      realm = "turn.synapse.li";
      cert = "/run/credentials/coturn.service/turn.synapse.li.pem";
      pkey = "/run/credentials/coturn.service/turn.synapse.li.key.pem";
      dh-file = config.security.dhparams.params.coturn.path;
      relay-ips = ["202.61.241.61" "2a03:4000:52:ada::"];
      extraConfig = ''
        # for debugging
        verbose
        # ban private IP ranges
        no-multicast-peers
        denied-peer-ip=0.0.0.0-0.255.255.255
        denied-peer-ip=10.0.0.0-10.255.255.255
        denied-peer-ip=100.64.0.0-100.127.255.255
        denied-peer-ip=127.0.0.0-127.255.255.255
        denied-peer-ip=169.254.0.0-169.254.255.255
        denied-peer-ip=172.16.0.0-172.31.255.255
        denied-peer-ip=192.0.0.0-192.0.0.255
        denied-peer-ip=192.0.2.0-192.0.2.255
        denied-peer-ip=192.88.99.0-192.88.99.255
        denied-peer-ip=192.168.0.0-192.168.255.255
        denied-peer-ip=198.18.0.0-198.19.255.255
        denied-peer-ip=198.51.100.0-198.51.100.255
        denied-peer-ip=203.0.113.0-203.0.113.255
        denied-peer-ip=240.0.0.0-255.255.255.255
        denied-peer-ip=::1
        denied-peer-ip=64:ff9b::-64:ff9b::ffff:ffff
        denied-peer-ip=::ffff:0.0.0.0-::ffff:255.255.255.255
        denied-peer-ip=100::-100::ffff:ffff:ffff:ffff
        denied-peer-ip=2001::-2001:1ff:ffff:ffff:ffff:ffff:ffff:ffff
        denied-peer-ip=2002::-2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff
        denied-peer-ip=fc00::-fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
        denied-peer-ip=fe80::-febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff

        denied-peer-ip=2a03:4000:52:ada::1-2a03:4000:52:ada:ffff:ffff:ffff:ffff
      '';
    };
    systemd.services.coturn = {
      serviceConfig = {
        LoadCredential = [
          "turn.synapse.li.key.pem:${config.security.acme.certs."turn.synapse.li".directory}/key.pem"
          "turn.synapse.li.pem:${config.security.acme.certs."turn.synapse.li".directory}/fullchain.pem"
        ];
      };
    };

    sops.secrets."coturn-auth-secret" = {
      format = "binary";
      sopsFile = ./coturn-auth-secret;
      owner = "turnserver";
      group = "turnserver";
    };
  };
}