{ 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;
          }
          { bind_addresses = ["::1" "127.0.0.1"];
            port = 9092;
            resources = [
              { names = [ "metrics" ];
                compress = false;
              }
            ];
            tls = false;
            type = "http";
          }
        ];

        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 = true;
        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" = {
        extraConfig = ''
          keepalive 64;
        '';
        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";
        kTLS = true;
        http3 = true;
        listen = [
          { addr = "0.0.0.0"; port = 80; ssl = false; }
          { addr = "[::0]"; port = 80; ssl = false; }
          { 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;

            proxy_pass_header Server;

            proxy_http_version 1.1;
            proxy_set_header "Connection" "";
          '';
        }) ["/_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" = let
        headerDirectives = ''
          add_header Strict-Transport-Security "max-age=63072000" always;

          add_header X-Frame-Options SAMEORIGIN always;
          add_header X-Content-Type-Options nosniff always;
          add_header X-XSS-Protection "1; mode=block" always;
          add_header Content-Security-Policy "frame-ancestors 'self'" always;
        '';
      in {
        forceSSL = true;
        kTLS = true;
        http3 = 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 = ''
          error_page  500 502 503 504  /50x.html;
        '';

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

        locations = {
          "= /index.html".extraConfig = ''
            ${headerDirectives}
            add_header Cache-Control "no-cache" always;
          '';
          "= /version".extraConfig = ''
            ${headerDirectives}
            add_header Cache-Control "no-cache" always;
          '';
          "/config".extraConfig = ''
            ${headerDirectives}
            add_header Cache-Control "no-cache" always;
          '';
        };
      };
    };

    security.acme.rfc2136Domains = {
      "element.synapse.li" = {
        restartUnits = ["nginx.service"];
      };
      "turn.synapse.li" = {
        restartUnits = ["coturn.service"];
      };
      "synapse.li" = {
        restartUnits = ["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 = "/run/credentials/coturn.service/auth-secret";
      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"
          "auth-secret:${config.sops.secrets."coturn-auth-secret".path}"
        ];
      };
    };

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