{ config, lib, pkgs, ... }: with lib; let cfg = config.modules.services.matrix-homeserver; httpPort = 8008; slidingSyncPort = 8009; metricsPort = 8010; in { options.modules.services.matrix-homeserver = { enable = mkEnableOption "matrix homeserver instance"; domain = mkOption { type = types.str; }; realHost = mkOption { type = types.str; default = "matrix.${cfg.domain}"; }; slidingSyncHost = mkOption { type = types.str; default = "slidingsync.${cfg.domain}"; }; turn = { enable = mkEnableOption "VOIP using TURN"; domain = mkOption { type = types.str; default = "turn.${cfg.domain}"; }; shared_secret = mkOption { type = types.str; }; }; secrets = { matrix-server-key = mkOption { type = types.str; description = "path to the server key"; }; matrix-shared-secret = mkOption { type = types.str; description = "path to the registration shared secret"; }; extra-config-path = mkOption { type = types.nullOr types.str; description = "path to the extra configuration file to source"; }; sliding-sync-secret = mkOption { type = types.nullOr types.str; description = "path to the sliding sync secret"; }; }; }; config = mkIf cfg.enable { services.matrix-synapse = { enable = true; withJemalloc = true; dataDir = "/var/lib/matrix-synapse"; settings = { server_name = cfg.domain; public_baseurl = "https://${cfg.realHost}"; signing_key_path = cfg.secrets.matrix-server-key; allow_guest_access = false; enable_registration = false; registration_requires_token = true; registration_shared_secret_path = cfg.secrets.matrix-shared-secret; enable_metrics = true; url_preview_enabled = true; database = { name = "psycopg2"; args.password = "synapse"; }; listeners = [ { port = httpPort; resources = [ { compress = true; names = [ "client" ]; } { compress = false; names = [ "federation" ]; } ]; type = "http"; tls = false; x_forwarded = true; } { port = metricsPort; resources = [{ compress = false; names = [ "metrics" ]; }]; type = "metrics"; tls = false; } ]; trusted_key_servers = [{ server_name = "matrix.org"; verify_keys = { "ed25519:auto" = "Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw"; }; }]; # Yes, we want to use matrix.org as our trusted key server suppress_key_server_warning = true; } // optionalAttrs (cfg.turn.enable) { turn_uris = [ "turns:${cfg.turn.domain}?transport=udp" "turns:${cfg.turn.domain}?transport=tcp" "turn:${cfg.turn.domain}?transport=udp" "turn:${cfg.turn.domain}?transport=tcp" ]; }; }; services.matrix-sliding-sync = { enable = true; createDatabase = true; settings = { SYNCV3_SERVER = "https://${cfg.realHost}"; SYNCV3_BINDADDR = "[::1]:${toString slidingSyncPort}"; }; environmentFile = cfg.secrets.sliding-sync-secret; }; services.prometheus.scrapeConfigs = [ { job_name = "synapse"; metrics_path = "/_synapse/metrics"; static_configs = [{ targets = [ "127.0.0.1:${toString metricsPort}" ]; }]; } ]; modules.persistence.directories = [ "/var/lib/matrix-synapse" ]; services.postgresql.enable = true; services.postgresql.initialScript = pkgs.writeText "synapse-init.sql" '' CREATE ROLE "matrix-synapse" WITH LOGIN PASSWORD 'synapse'; CREATE DATABASE "matrix-synapse" WITH OWNER "matrix-synapse" TEMPLATE template0 LC_COLLATE = "C" LC_CTYPE = "C"; ''; services.nginx.virtualHosts.${cfg.realHost} = { forceSSL = true; useACMEHost = cfg.domain; listen = [ { addr = "0.0.0.0"; port = 443; ssl = true; } { addr = "[::]"; port = 443; ssl = true; } { addr = "0.0.0.0"; port = 8448; ssl = true; } { addr = "[::]"; port = 8448; ssl = true; } ]; extraConfig = '' proxy_set_header Host $host; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 600; client_max_body_size ${config.services.matrix-synapse.settings.max_upload_size}; ''; locations."~* ^(\\/_matrix|\\/_synapse\\/client)".proxyPass = "http://[::1]:${toString httpPort}"; }; services.nginx.virtualHosts.${cfg.domain} = let server-hello = { "m.server" = "${cfg.realHost}:443"; }; client-hello = { "m.homeserver"."base_url" = "https://${cfg.realHost}"; "m.identity_server"."base_url" = "https://vector.im"; "org.matrix.msc3575.proxy"."url" = "https://${cfg.slidingSyncHost}"; }; in { forceSSL = true; useACMEHost = cfg.domain; locations = { "/.well-known/matrix/server" = { extraConfig = '' add_header Content-Type application/json; return 200 '${builtins.toJSON server-hello}'; ''; }; "/.well-known/matrix/client" = { extraConfig = '' add_header Content-Type application/json; add_header Access-Control-Allow-Origin *; return 200 '${builtins.toJSON client-hello}'; ''; }; }; }; services.nginx.virtualHosts.${cfg.slidingSyncHost} = { forceSSL = true; useACMEHost = cfg.domain; locations."/".proxyPass = "http://${config.services.matrix-sliding-sync.settings.SYNCV3_BINDADDR}"; }; networking.firewall.allowedTCPPorts = [ 443 8448 ]; }; }