{ config, lib, ... }: with lib; let cfg = config.modules.services.dendrite; database = { connection_string = "postgres:///dendrite?host=/run/postgresql"; max_open_conns = 100; max_idle_conns = 5; conn_max_lifetime = -1; }; in { imports = [ ../../overlays/sliding-sync-module.nix ]; options.modules.services.dendrite = { enable = mkEnableOption "dendrite 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 suing 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"; }; dendrite-envs = mkOption { type = types.nullOr types.str; description = "path for the environment file to source"; }; sliding-sync-secret = mkOption { type = types.nullOr types.str; description = "path to the sliding sync secret"; }; }; }; config = mkIf cfg.enable { # Adapted from Mic92/dotfiles, (C) 2021 Jörg Thalheim (MIT) services.dendrite = { enable = true; settings = { global = { server_name = cfg.domain; # `private_key` has the type `path` # prefix a `/` to make `path` happy private_key = "/$CREDENTIALS_DIRECTORY/matrix-server-key"; jetstream.storage_path = "/var/lib/dendrite/jetstream"; trusted_third_party_id_servers = [ "matrix.org" "vector.im" ]; metrics.enabled = true; }; logging = [ { type = "std"; level = "info"; # "warn" on public release } ]; app_service_api = { inherit database; config_files = [ ]; }; client_api = { registration_disabled = true; rate_limiting.enabled = false; rate_limiting.exempt_user_ids = [ "@abuse:${cfg.domain}" ]; # registration_shared_secret = ""; # Initially set this option to configure the admin user. } // optionalAttrs cfg.turn.enable { turn = { turn_user_lifetime = "24h"; 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" ]; turn_shared_secret = cfg.turn.shared_secret; }; }; media_api = { inherit database; dynamic_thumbnails = true; }; room_server = { inherit database; }; push_server = { inherit database; }; mscs = { inherit database; mscs = [ "msc2836" "msc2946" ]; }; sync_api = { inherit database; real_ip_header = "X-Real-IP"; # The NixOS option is 'enable', which doesn't exist in Dendrite. search.enabled = true; }; key_server = { inherit database; }; federation_api = { inherit database; key_perspectives = [ { server_name = "matrix.org"; keys = [ { key_id = "ed25519:auto"; public_key = "Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw"; } { key_id = "ed25519:a_RXGa"; public_key = "l8Hft5qXKn1vfHrg3p4+W8gELQVo8N13JkluMfmn2sQ"; } ]; } ]; prefer_direct_fetch = false; }; user_api = { account_database = database; device_database = database; }; }; loadCredential = [ "matrix-server-key:${cfg.secrets.matrix-server-key}" ]; } // optionalAttrs (cfg.secrets.dendrite-envs != null) { environmentFile = cfg.secrets.dendrite-envs; }; services.prometheus.scrapeConfigs = [ { job_name = "dendrite"; static_configs = [{ targets = [ "127.0.0.1:${toString config.services.dendrite.httpPort}" ]; }]; } ]; systemd.services.dendrite = { after = [ "postgresql.service" ]; }; environment.persistence."/persist".directories = [ "/var/lib/private/dendrite" ]; services.sliding-sync = { enable = true; server = "https://${cfg.realHost}"; bindAddr = "[::1]:8009"; db = "postgres:///syncv3?host=/run/postgresql"; secret = cfg.secrets.sliding-sync-secret; after = [ "dendrite.service" ]; }; services.postgresql.enable = true; services.postgresql.ensureDatabases = [ "dendrite" "syncv3" ]; services.postgresql.ensureUsers = [ { name = "dendrite"; ensurePermissions."DATABASE dendrite" = "ALL PRIVILEGES"; } { name = "sliding-sync"; ensurePermissions."DATABASE syncv3" = "ALL PRIVILEGES"; } ]; 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-Real-IP $remote_addr; proxy_read_timeout 600; client_max_body_size 50M; ''; locations."/_matrix".proxyPass = "http://[::1]:${toString config.services.dendrite.httpPort}"; locations."/_dendrite".proxyPass = "http://[::1]:${toString config.services.dendrite.httpPort}"; locations."/_synapse".proxyPass = "http://[::1]:${toString config.services.dendrite.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.sliding-sync.bindAddr}"; }; networking.firewall.allowedTCPPorts = [ 443 8448 ]; }; }