base16-schemes: unstable-2024-06-21 -> unstable-2024-11-12
[NixPkgs.git] / nixos / modules / services / matrix / synapse.nix
blob6d2e6201d66d320cd8f6a68f38b277e35f4cd651
1 { config, lib, options, pkgs, ... }:
3 with lib;
5 let
6   cfg = config.services.matrix-synapse;
7   format = pkgs.formats.yaml { };
9   filterRecursiveNull = o:
10     if isAttrs o then
11       mapAttrs (_: v: filterRecursiveNull v) (filterAttrs (_: v: v != null) o)
12     else if isList o then
13       map filterRecursiveNull (filter (v: v != null) o)
14     else
15       o;
17   # remove null values from the final configuration
18   finalSettings = filterRecursiveNull cfg.settings;
19   configFile = format.generate "homeserver.yaml" finalSettings;
21   usePostgresql = cfg.settings.database.name == "psycopg2";
22   hasLocalPostgresDB = let args = cfg.settings.database.args; in
23     usePostgresql
24     && (!(args ? host) || (elem args.host [ "localhost" "127.0.0.1" "::1" ]))
25     && config.services.postgresql.enable;
26   hasWorkers = cfg.workers != { };
28   listenerSupportsResource = resource: listener:
29     lib.any ({ names, ... }: builtins.elem resource names) listener.resources;
31   clientListener = findFirst
32     (listenerSupportsResource "client")
33     null
34     (cfg.settings.listeners
35       ++ concatMap ({ worker_listeners, ... }: worker_listeners) (attrValues cfg.workers));
37   registerNewMatrixUser =
38     let
39       isIpv6 = hasInfix ":";
41       # add a tail, so that without any bind_addresses we still have a useable address
42       bindAddress = head (clientListener.bind_addresses ++ [ "127.0.0.1" ]);
43       listenerProtocol = if clientListener.tls
44         then "https"
45         else "http";
46     in
47     assert assertMsg (clientListener != null) "No client listener found in synapse or one of its workers";
48     pkgs.writeShellScriptBin "matrix-synapse-register_new_matrix_user" ''
49       exec ${cfg.package}/bin/register_new_matrix_user \
50         $@ \
51         ${lib.concatMapStringsSep " " (x: "-c ${x}") ([ configFile ] ++ cfg.extraConfigFiles)} \
52         "${listenerProtocol}://${
53           if (isIpv6 bindAddress) then
54             "[${bindAddress}]"
55           else
56             "${bindAddress}"
57         }:${builtins.toString clientListener.port}/"
58     '';
60   defaultExtras = [
61     "systemd"
62     "postgres"
63     "url-preview"
64     "user-search"
65   ];
67   wantedExtras = cfg.extras
68     ++ lib.optional (cfg.settings ? oidc_providers) "oidc"
69     ++ lib.optional (cfg.settings ? jwt_config) "jwt"
70     ++ lib.optional (cfg.settings ? saml2_config) "saml2"
71     ++ lib.optional (cfg.settings ? redis) "redis"
72     ++ lib.optional (cfg.settings ? sentry) "sentry"
73     ++ lib.optional (cfg.settings ? user_directory) "user-search"
74     ++ lib.optional (cfg.settings.url_preview_enabled) "url-preview"
75     ++ lib.optional (cfg.settings.database.name == "psycopg2") "postgres";
77   wrapped = pkgs.matrix-synapse.override {
78     extras = wantedExtras;
79     inherit (cfg) plugins;
80   };
82   defaultCommonLogConfig = {
83     version = 1;
84     formatters.journal_fmt.format = "%(name)s: [%(request)s] %(message)s";
85     handlers.journal = {
86       class = "systemd.journal.JournalHandler";
87       formatter = "journal_fmt";
88     };
89     root = {
90       level = "INFO";
91       handlers = [ "journal" ];
92     };
93     disable_existing_loggers = false;
94   };
96   defaultCommonLogConfigText = generators.toPretty { } defaultCommonLogConfig;
98   logConfigText = logName:
99     lib.literalMD ''
100       Path to a yaml file generated from this Nix expression:
102       ```
103       ${generators.toPretty { } (
104         recursiveUpdate defaultCommonLogConfig { handlers.journal.SYSLOG_IDENTIFIER = logName; }
105       )}
106       ```
107     '';
109   genLogConfigFile = logName: format.generate
110     "synapse-log-${logName}.yaml"
111     (cfg.log // optionalAttrs (cfg.log?handlers.journal) {
112       handlers.journal = cfg.log.handlers.journal // {
113         SYSLOG_IDENTIFIER = logName;
114       };
115     });
117   toIntBase8 = str:
118     lib.pipe str [
119       lib.stringToCharacters
120       (map lib.toInt)
121       (lib.foldl (acc: digit: acc * 8 + digit) 0)
122     ];
124   toDecimalFilePermission = value:
125     if value == null then
126       null
127     else
128       toIntBase8 value;
129 in {
131   imports = [
133     (mkRemovedOptionModule [ "services" "matrix-synapse" "trusted_third_party_id_servers" ] ''
134       The `trusted_third_party_id_servers` option as been removed in `matrix-synapse` v1.4.0
135       as the behavior is now obsolete.
136     '')
137     (mkRemovedOptionModule [ "services" "matrix-synapse" "create_local_database" ] ''
138       Database configuration must be done manually. An exemplary setup is demonstrated in
139       <nixpkgs/nixos/tests/matrix/synapse.nix>
140     '')
141     (mkRemovedOptionModule [ "services" "matrix-synapse" "web_client" ] "")
142     (mkRemovedOptionModule [ "services" "matrix-synapse" "room_invite_state_types" ] ''
143       You may add additional event types via
144       `services.matrix-synapse.room_prejoin_state.additional_event_types` and
145       disable the default events via
146       `services.matrix-synapse.room_prejoin_state.disable_default_event_types`.
147     '')
149     # options that don't exist in synapse anymore
150     (mkRemovedOptionModule [ "services" "matrix-synapse" "bind_host" ] "Use listener settings instead." )
151     (mkRemovedOptionModule [ "services" "matrix-synapse" "bind_port" ] "Use listener settings instead." )
152     (mkRemovedOptionModule [ "services" "matrix-synapse" "expire_access_tokens" ] "" )
153     (mkRemovedOptionModule [ "services" "matrix-synapse" "no_tls" ] "It is no longer supported by synapse." )
154     (mkRemovedOptionModule [ "services" "matrix-synapse" "tls_dh_param_path" ] "It was removed from synapse." )
155     (mkRemovedOptionModule [ "services" "matrix-synapse" "unsecure_port" ] "Use settings.listeners instead." )
156     (mkRemovedOptionModule [ "services" "matrix-synapse" "user_creation_max_duration" ] "It is no longer supported by synapse." )
157     (mkRemovedOptionModule [ "services" "matrix-synapse" "verbose" ] "Use a log config instead." )
159     # options that were moved into rfc42 style settings
160     (mkRemovedOptionModule [ "services" "matrix-synapse" "app_service_config_files" ] "Use settings.app_service_config_files instead" )
161     (mkRemovedOptionModule [ "services" "matrix-synapse" "database_args" ] "Use settings.database.args instead" )
162     (mkRemovedOptionModule [ "services" "matrix-synapse" "database_name" ] "Use settings.database.args.database instead" )
163     (mkRemovedOptionModule [ "services" "matrix-synapse" "database_type" ] "Use settings.database.name instead" )
164     (mkRemovedOptionModule [ "services" "matrix-synapse" "database_user" ] "Use settings.database.args.user instead" )
165     (mkRemovedOptionModule [ "services" "matrix-synapse" "dynamic_thumbnails" ] "Use settings.dynamic_thumbnails instead" )
166     (mkRemovedOptionModule [ "services" "matrix-synapse" "enable_metrics" ] "Use settings.enable_metrics instead" )
167     (mkRemovedOptionModule [ "services" "matrix-synapse" "enable_registration" ] "Use settings.enable_registration instead" )
168     (mkRemovedOptionModule [ "services" "matrix-synapse" "extraConfig" ] "Use settings instead." )
169     (mkRemovedOptionModule [ "services" "matrix-synapse" "listeners" ] "Use settings.listeners instead" )
170     (mkRemovedOptionModule [ "services" "matrix-synapse" "logConfig" ] "Use settings.log_config instead" )
171     (mkRemovedOptionModule [ "services" "matrix-synapse" "max_image_pixels" ] "Use settings.max_image_pixels instead" )
172     (mkRemovedOptionModule [ "services" "matrix-synapse" "max_upload_size" ] "Use settings.max_upload_size instead" )
173     (mkRemovedOptionModule [ "services" "matrix-synapse" "presence" "enabled" ] "Use settings.presence.enabled instead" )
174     (mkRemovedOptionModule [ "services" "matrix-synapse" "public_baseurl" ] "Use settings.public_baseurl instead" )
175     (mkRemovedOptionModule [ "services" "matrix-synapse" "report_stats" ] "Use settings.report_stats instead" )
176     (mkRemovedOptionModule [ "services" "matrix-synapse" "server_name" ] "Use settings.server_name instead" )
177     (mkRemovedOptionModule [ "services" "matrix-synapse" "servers" ] "Use settings.trusted_key_servers instead." )
178     (mkRemovedOptionModule [ "services" "matrix-synapse" "tls_certificate_path" ] "Use settings.tls_certificate_path instead" )
179     (mkRemovedOptionModule [ "services" "matrix-synapse" "tls_private_key_path" ] "Use settings.tls_private_key_path instead" )
180     (mkRemovedOptionModule [ "services" "matrix-synapse" "turn_shared_secret" ] "Use settings.turn_shared_secret instead" )
181     (mkRemovedOptionModule [ "services" "matrix-synapse" "turn_uris" ] "Use settings.turn_uris instead" )
182     (mkRemovedOptionModule [ "services" "matrix-synapse" "turn_user_lifetime" ] "Use settings.turn_user_lifetime instead" )
183     (mkRemovedOptionModule [ "services" "matrix-synapse" "url_preview_enabled" ] "Use settings.url_preview_enabled instead" )
184     (mkRemovedOptionModule [ "services" "matrix-synapse" "url_preview_ip_range_blacklist" ] "Use settings.url_preview_ip_range_blacklist instead" )
185     (mkRemovedOptionModule [ "services" "matrix-synapse" "url_preview_ip_range_whitelist" ] "Use settings.url_preview_ip_range_whitelist instead" )
186     (mkRemovedOptionModule [ "services" "matrix-synapse" "url_preview_url_blacklist" ] "Use settings.url_preview_url_blacklist instead" )
188     # options that are too specific to mention them explicitly in settings
189     (mkRemovedOptionModule [ "services" "matrix-synapse" "account_threepid_delegates" "email" ] "Use settings.account_threepid_delegates.email instead" )
190     (mkRemovedOptionModule [ "services" "matrix-synapse" "account_threepid_delegates" "msisdn" ] "Use settings.account_threepid_delegates.msisdn instead" )
191     (mkRemovedOptionModule [ "services" "matrix-synapse" "allow_guest_access" ] "Use settings.allow_guest_access instead" )
192     (mkRemovedOptionModule [ "services" "matrix-synapse" "bcrypt_rounds" ] "Use settings.bcrypt_rounds instead" )
193     (mkRemovedOptionModule [ "services" "matrix-synapse" "enable_registration_captcha" ] "Use settings.enable_registration_captcha instead" )
194     (mkRemovedOptionModule [ "services" "matrix-synapse" "event_cache_size" ] "Use settings.event_cache_size instead" )
195     (mkRemovedOptionModule [ "services" "matrix-synapse" "federation_rc_concurrent" ] "Use settings.rc_federation.concurrent instead" )
196     (mkRemovedOptionModule [ "services" "matrix-synapse" "federation_rc_reject_limit" ] "Use settings.rc_federation.reject_limit instead" )
197     (mkRemovedOptionModule [ "services" "matrix-synapse" "federation_rc_sleep_delay" ] "Use settings.rc_federation.sleep_delay instead" )
198     (mkRemovedOptionModule [ "services" "matrix-synapse" "federation_rc_sleep_limit" ] "Use settings.rc_federation.sleep_limit instead" )
199     (mkRemovedOptionModule [ "services" "matrix-synapse" "federation_rc_window_size" ] "Use settings.rc_federation.window_size instead" )
200     (mkRemovedOptionModule [ "services" "matrix-synapse" "key_refresh_interval" ] "Use settings.key_refresh_interval instead" )
201     (mkRemovedOptionModule [ "services" "matrix-synapse" "rc_messages_burst_count" ] "Use settings.rc_messages.burst_count instead" )
202     (mkRemovedOptionModule [ "services" "matrix-synapse" "rc_messages_per_second" ] "Use settings.rc_messages.per_second instead" )
203     (mkRemovedOptionModule [ "services" "matrix-synapse" "recaptcha_private_key" ] "Use settings.recaptcha_private_key instead" )
204     (mkRemovedOptionModule [ "services" "matrix-synapse" "recaptcha_public_key" ] "Use settings.recaptcha_public_key instead" )
205     (mkRemovedOptionModule [ "services" "matrix-synapse" "redaction_retention_period" ] "Use settings.redaction_retention_period instead" )
206     (mkRemovedOptionModule [ "services" "matrix-synapse" "room_prejoin_state" "additional_event_types" ] "Use settings.room_prejoin_state.additional_event_types instead" )
207     (mkRemovedOptionModule [ "services" "matrix-synapse" "room_prejoin_state" "disable_default_event_types" ] "Use settings.room_prejoin-state.disable_default_event_types instead" )
209     # Options that should be passed via extraConfigFiles, so they are not persisted into the nix store
210     (mkRemovedOptionModule [ "services" "matrix-synapse" "macaroon_secret_key" ] "Pass this value via extraConfigFiles instead" )
211     (mkRemovedOptionModule [ "services" "matrix-synapse" "registration_shared_secret" ] "Pass this value via extraConfigFiles instead" )
213   ];
215   options = let
216     listenerType = workerContext: types.submodule ({ config, ... }: {
217       options = {
218         port = mkOption {
219           type = types.nullOr types.port;
220           default = null;
221           example = 8448;
222           description = ''
223             The port to listen for HTTP(S) requests on.
224           '';
225         };
227         bind_addresses = mkOption {
228           type = types.nullOr (types.listOf types.str);
229           default = if config.path != null then null else [
230             "::1"
231             "127.0.0.1"
232           ];
233           defaultText = literalExpression ''
234             if path != null then
235               null
236             else
237               [
238                 "::1"
239                 "127.0.0.1"
240               ]
241           '';
242           example = literalExpression ''
243             [
244               "::"
245               "0.0.0.0"
246             ]
247           '';
248           description = ''
249             IP addresses to bind the listener to.
250           '';
251         };
253         path = mkOption {
254           type = types.nullOr types.path;
255           default = null;
256           description = ''
257             Unix domain socket path to bind this listener to.
259             ::: {.note}
260               This option is incompatible with {option}`bind_addresses`, {option}`port`, {option}`tls`
261               and also does not support the `metrics` and `manhole` listener {option}`type`.
262             :::
263           '';
264         };
266         mode = mkOption {
267           type = types.nullOr (types.strMatching "^[0,2-7]{3,4}$");
268           default = if config.path != null then "660" else null;
269           defaultText = literalExpression ''
270             if path != null then
271               "660"
272             else
273               null
274           '';
275           example = "660";
276           description = ''
277             File permissions on the UNIX domain socket.
278           '';
279           apply = toDecimalFilePermission;
280         };
282         type = mkOption {
283           type = types.enum [
284             "http"
285             "manhole"
286             "metrics"
287             "replication"
288           ];
289           default = "http";
290           example = "metrics";
291           description = ''
292             The type of the listener, usually http.
293           '';
294         };
296         tls = mkOption {
297           type = types.nullOr types.bool;
298           default = if config.path != null then
299             null
300           else
301             !workerContext;
302           defaultText = ''
303             Enabled for the main instance listener, unless it is configured with a UNIX domain socket path.
304           '';
305           example = false;
306           description = ''
307             Whether to enable TLS on the listener socket.
309             ::: {.note}
310               This option will be ignored for UNIX domain sockets.
311             :::
312           '';
313         };
315         x_forwarded = mkOption {
316           type = types.bool;
317           default = config.path != null;
318           defaultText = ''
319             Enabled if the listener is configured with a UNIX domain socket path
320           '';
321           example = true;
322           description = ''
323             Use the X-Forwarded-For (XFF) header as the client IP and not the
324             actual client IP.
325           '';
326         };
328         resources = mkOption {
329           type = types.listOf (types.submodule {
330             options = {
331               names = mkOption {
332                 type = types.listOf (types.enum [
333                   "client"
334                   "consent"
335                   "federation"
336                   "health"
337                   "keys"
338                   "media"
339                   "metrics"
340                   "openid"
341                   "replication"
342                   "static"
343                 ]);
344                 description = ''
345                   List of resources to host on this listener.
346                 '';
347                 example = [
348                   "client"
349                 ];
350               };
351               compress = mkOption {
352                 default = false;
353                 type = types.bool;
354                 description = ''
355                   Whether synapse should compress HTTP responses to clients that support it.
356                   This should be disabled if running synapse behind a load balancer
357                   that can do automatic compression.
358                 '';
359               };
360             };
361           });
362           description = ''
363             List of HTTP resources to serve on this listener.
364           '';
365         };
366       };
367     });
368   in {
369     services.matrix-synapse = {
370       enable = mkEnableOption "matrix.org synapse, the reference homeserver";
372       enableRegistrationScript = mkOption {
373         type = types.bool;
374         default = clientListener.bind_addresses != [];
375         example = false;
376         defaultText = ''
377           Enabled if the client listener uses TCP sockets
378         '';
379         description = ''
380           Whether to install the `register_new_matrix_user` script, that
381           allows account creation on the terminal.
383           ::: {.note}
384             This script does not work when the client listener uses UNIX domain sockets
385           :::
386         '';
387       };
389       serviceUnit = lib.mkOption {
390         type = lib.types.str;
391         readOnly = true;
392         description = ''
393           The systemd unit (a service or a target) for other services to depend on if they
394           need to be started after matrix-synapse.
396           This option is useful as the actual parent unit for all matrix-synapse processes
397           changes when configuring workers.
398         '';
399       };
401       configFile = mkOption {
402         type = types.path;
403         readOnly = true;
404         description = ''
405           Path to the configuration file on the target system. Useful to configure e.g. workers
406           that also need this.
407         '';
408       };
410       package = mkOption {
411         type = types.package;
412         readOnly = true;
413         description = ''
414           Reference to the `matrix-synapse` wrapper with all extras
415           (e.g. for `oidc` or `saml2`) added to the `PYTHONPATH` of all executables.
417           This option is useful to reference the "final" `matrix-synapse` package that's
418           actually used by `matrix-synapse.service`. For instance, when using
419           workers, it's possible to run
420           `''${config.services.matrix-synapse.package}/bin/synapse_worker` and
421           no additional PYTHONPATH needs to be specified for extras or plugins configured
422           via `services.matrix-synapse`.
424           However, this means that this option is supposed to be only declared
425           by the `services.matrix-synapse` module itself and is thus read-only.
426           In order to modify `matrix-synapse` itself, use an overlay to override
427           `pkgs.matrix-synapse-unwrapped`.
428         '';
429       };
431       extras = mkOption {
432         type = types.listOf (types.enum (lib.attrNames pkgs.matrix-synapse-unwrapped.optional-dependencies));
433         default = defaultExtras;
434         example = literalExpression ''
435           [
436             "cache-memory" # Provide statistics about caching memory consumption
437             "jwt"          # JSON Web Token authentication
438             "oidc"         # OpenID Connect authentication
439             "postgres"     # PostgreSQL database backend
440             "redis"        # Redis support for the replication stream between worker processes
441             "saml2"        # SAML2 authentication
442             "sentry"       # Error tracking and performance metrics
443             "systemd"      # Provide the JournalHandler used in the default log_config
444             "url-preview"  # Support for oEmbed URL previews
445             "user-search"  # Support internationalized domain names in user-search
446           ]
447         '';
448         description = ''
449           Explicitly install extras provided by matrix-synapse. Most
450           will require some additional configuration.
452           Extras will automatically be enabled, when the relevant
453           configuration sections are present.
455           Please note that this option is additive: i.e. when adding a new item
456           to this list, the defaults are still kept. To override the defaults as well,
457           use `lib.mkForce`.
458         '';
459       };
461       plugins = mkOption {
462         type = types.listOf types.package;
463         default = [ ];
464         example = literalExpression ''
465           with config.services.matrix-synapse.package.plugins; [
466             matrix-synapse-ldap3
467             matrix-synapse-pam
468           ];
469         '';
470         description = ''
471           List of additional Matrix plugins to make available.
472         '';
473       };
475       withJemalloc = mkOption {
476         type = types.bool;
477         default = false;
478         description = ''
479           Whether to preload jemalloc to reduce memory fragmentation and overall usage.
480         '';
481       };
483       dataDir = mkOption {
484         type = types.str;
485         default = "/var/lib/matrix-synapse";
486         description = ''
487           The directory where matrix-synapse stores its stateful data such as
488           certificates, media and uploads.
489         '';
490       };
492       log = mkOption {
493         type = types.attrsOf format.type;
494         defaultText = literalExpression defaultCommonLogConfigText;
495         description = ''
496           Default configuration for the loggers used by `matrix-synapse` and its workers.
497           The defaults are added with the default priority which means that
498           these will be merged with additional declarations. These additional
499           declarations also take precedence over the defaults when declared
500           with at least normal priority. For instance
501           the log-level for synapse and its workers can be changed like this:
503           ```nix
504           { lib, ... }: {
505             services.matrix-synapse.log.root.level = "WARNING";
506           }
507           ```
509           And another field can be added like this:
511           ```nix
512           {
513             services.matrix-synapse.log = {
514               loggers."synapse.http.matrixfederationclient".level = "DEBUG";
515             };
516           }
517           ```
519           Additionally, the field `handlers.journal.SYSLOG_IDENTIFIER` will be added to
520           each log config, i.e.
521           * `synapse` for `matrix-synapse.service`
522           * `synapse-<worker name>` for `matrix-synapse-worker-<worker name>.service`
524           This is only done if this option has a `handlers.journal` field declared.
526           To discard all settings declared by this option for each worker and synapse,
527           `lib.mkForce` can be used.
529           To discard all settings declared by this option for a single worker or synapse only,
530           [](#opt-services.matrix-synapse.workers._name_.worker_log_config) or
531           [](#opt-services.matrix-synapse.settings.log_config) can be used.
532         '';
533       };
535       settings = mkOption {
536         default = { };
537         description = ''
538           The primary synapse configuration. See the
539           [sample configuration](https://github.com/element-hq/synapse/blob/v${pkgs.matrix-synapse-unwrapped.version}/docs/sample_config.yaml)
540           for possible values.
542           Secrets should be passed in by using the `extraConfigFiles` option.
543         '';
544         type = with types; submodule {
545           freeformType = format.type;
546           options = {
547             # This is a reduced set of popular options and defaults
548             # Do not add every available option here, they can be specified
549             # by the user at their own discretion. This is a freeform type!
551             server_name = mkOption {
552               type = types.str;
553               example = "example.com";
554               default = config.networking.hostName;
555               defaultText = literalExpression "config.networking.hostName";
556               description = ''
557                 The domain name of the server, with optional explicit port.
558                 This is used by remote servers to look up the server address.
559                 This is also the last part of your UserID.
561                 The server_name cannot be changed later so it is important to configure this correctly before you start Synapse.
562               '';
563             };
565             enable_registration = mkOption {
566               type = types.bool;
567               default = false;
568               description = ''
569                 Enable registration for new users.
570               '';
571             };
573             registration_shared_secret = mkOption {
574               type = types.nullOr types.str;
575               default = null;
576               description = ''
577                 If set, allows registration by anyone who also has the shared
578                 secret, even if registration is otherwise disabled.
580                 Secrets should be passed in via `extraConfigFiles`!
581               '';
582             };
584             macaroon_secret_key = mkOption {
585               type = types.nullOr types.str;
586               default = null;
587               description = ''
588                 Secret key for authentication tokens. If none is specified,
589                 the registration_shared_secret is used, if one is given; otherwise,
590                 a secret key is derived from the signing key.
592                 Secrets should be passed in via `extraConfigFiles`!
593               '';
594             };
596             enable_metrics = mkOption {
597               type = types.bool;
598               default = false;
599               description = ''
600                 Enable collection and rendering of performance metrics
601               '';
602             };
604             report_stats = mkOption {
605               type = types.bool;
606               default = false;
607               description = ''
608                 Whether or not to report anonymized homeserver usage statistics.
609               '';
610             };
612             signing_key_path = mkOption {
613               type = types.path;
614               default = "${cfg.dataDir}/homeserver.signing.key";
615               description = ''
616                 Path to the signing key to sign messages with.
617               '';
618             };
620             pid_file = mkOption {
621               type = types.path;
622               default = "/run/matrix-synapse.pid";
623               readOnly = true;
624               description = ''
625                 The file to store the PID in.
626               '';
627             };
629             log_config = mkOption {
630               type = types.path;
631               default = genLogConfigFile "synapse";
632               defaultText = logConfigText "synapse";
633               description = ''
634                 The file that holds the logging configuration.
635               '';
636             };
638             media_store_path = mkOption {
639               type = types.path;
640               default = if lib.versionAtLeast config.system.stateVersion "22.05"
641                 then "${cfg.dataDir}/media_store"
642                 else "${cfg.dataDir}/media";
643               defaultText = "${cfg.dataDir}/media_store for when system.stateVersion is at least 22.05, ${cfg.dataDir}/media when lower than 22.05";
644               description = ''
645                 Directory where uploaded images and attachments are stored.
646               '';
647             };
649             public_baseurl = mkOption {
650               type = types.nullOr types.str;
651               default = null;
652               example = "https://example.com:8448/";
653               description = ''
654                 The public-facing base URL for the client API (not including _matrix/...)
655               '';
656             };
658             tls_certificate_path = mkOption {
659               type = types.nullOr types.str;
660               default = null;
661               example = "/var/lib/acme/example.com/fullchain.pem";
662               description = ''
663                 PEM encoded X509 certificate for TLS.
664                 You can replace the self-signed certificate that synapse
665                 autogenerates on launch with your own SSL certificate + key pair
666                 if you like.  Any required intermediary certificates can be
667                 appended after the primary certificate in hierarchical order.
668               '';
669             };
671             tls_private_key_path = mkOption {
672               type = types.nullOr types.str;
673               default = null;
674               example = "/var/lib/acme/example.com/key.pem";
675               description = ''
676                 PEM encoded private key for TLS. Specify null if synapse is not
677                 speaking TLS directly.
678               '';
679             };
681             presence.enabled = mkOption {
682               type = types.bool;
683               default = true;
684               example = false;
685               description = ''
686                 Whether to enable presence tracking.
688                 Presence tracking allows users to see the state (e.g online/offline)
689                 of other local and remote users.
690               '';
691             };
693             listeners = mkOption {
694               type = types.listOf (listenerType false);
695               default = [{
696                 port = 8008;
697                 bind_addresses = [ "127.0.0.1" ];
698                 type = "http";
699                 tls = false;
700                 x_forwarded = true;
701                 resources = [{
702                   names = [ "client" ];
703                   compress = true;
704                 } {
705                   names = [ "federation" ];
706                   compress = false;
707                 }];
708               }] ++ lib.optional hasWorkers {
709                 path = "/run/matrix-synapse/main_replication.sock";
710                 type = "http";
711                 resources = [{
712                   names = [ "replication" ];
713                   compress = false;
714                 }];
715               };
716               description = ''
717                 List of ports that Synapse should listen on, their purpose and their configuration.
719                 By default, synapse will be configured for client and federation traffic on port 8008, and
720                 use a UNIX domain socket for worker replication. See [`services.matrix-synapse.workers`](#opt-services.matrix-synapse.workers)
721                 for more details.
722               '';
723             };
725             database.name = mkOption {
726               type = types.enum [
727                 "sqlite3"
728                 "psycopg2"
729               ];
730               default = if versionAtLeast config.system.stateVersion "18.03"
731                 then "psycopg2"
732                 else "sqlite3";
733               defaultText = literalExpression ''
734                 if versionAtLeast config.system.stateVersion "18.03"
735                 then "psycopg2"
736                 else "sqlite3"
737               '';
738               description = ''
739                 The database engine name. Can be sqlite3 or psycopg2.
740               '';
741             };
743             database.args.database = mkOption {
744               type = types.str;
745               default = {
746                 sqlite3 = "${cfg.dataDir}/homeserver.db";
747                 psycopg2 = "matrix-synapse";
748               }.${cfg.settings.database.name};
749               defaultText = literalExpression ''
750                 {
751                   sqlite3 = "''${${options.services.matrix-synapse.dataDir}}/homeserver.db";
752                   psycopg2 = "matrix-synapse";
753                 }.''${${options.services.matrix-synapse.settings}.database.name};
754               '';
755               description = ''
756                 Name of the database when using the psycopg2 backend,
757                 path to the database location when using sqlite3.
758               '';
759             };
761             database.args.user = mkOption {
762               type = types.nullOr types.str;
763               default = {
764                 sqlite3 = null;
765                 psycopg2 = "matrix-synapse";
766               }.${cfg.settings.database.name};
767               defaultText = lib.literalExpression ''
768                 {
769                   sqlite3 = null;
770                   psycopg2 = "matrix-synapse";
771                 }.''${cfg.settings.database.name};
772               '';
773               description = ''
774                 Username to connect with psycopg2, set to null
775                 when using sqlite3.
776               '';
777             };
779             url_preview_enabled = mkOption {
780               type = types.bool;
781               default = true;
782               example = false;
783               description = ''
784                 Is the preview URL API enabled?  If enabled, you *must* specify an
785                 explicit url_preview_ip_range_blacklist of IPs that the spider is
786                 denied from accessing.
787               '';
788             };
790             url_preview_ip_range_blacklist = mkOption {
791               type = types.listOf types.str;
792               default = [
793                 "10.0.0.0/8"
794                 "100.64.0.0/10"
795                 "127.0.0.0/8"
796                 "169.254.0.0/16"
797                 "172.16.0.0/12"
798                 "192.0.0.0/24"
799                 "192.0.2.0/24"
800                 "192.168.0.0/16"
801                 "192.88.99.0/24"
802                 "198.18.0.0/15"
803                 "198.51.100.0/24"
804                 "2001:db8::/32"
805                 "203.0.113.0/24"
806                 "224.0.0.0/4"
807                 "::1/128"
808                 "fc00::/7"
809                 "fe80::/10"
810                 "fec0::/10"
811                 "ff00::/8"
812               ];
813               description = ''
814                 List of IP address CIDR ranges that the URL preview spider is denied
815                 from accessing.
816               '';
817             };
819             url_preview_ip_range_whitelist = mkOption {
820               type = types.listOf types.str;
821               default = [ ];
822               description = ''
823                 List of IP address CIDR ranges that the URL preview spider is allowed
824                 to access even if they are specified in url_preview_ip_range_blacklist.
825               '';
826             };
828             url_preview_url_blacklist = mkOption {
829               # FIXME revert to just `listOf (attrsOf str)` after some time(tm).
830               type = types.listOf (
831                 types.coercedTo
832                   types.str
833                   (const (throw ''
834                     Setting `config.services.matrix-synapse.settings.url_preview_url_blacklist`
835                     to a list of strings has never worked. Due to a bug, this was the type accepted
836                     by the module, but in practice it broke on runtime and as a result, no URL
837                     preview worked anywhere if this was set.
839                     See https://element-hq.github.io/synapse/latest/usage/configuration/config_documentation.html#url_preview_url_blacklist
840                     on how to configure it properly.
841                   ''))
842                   (types.attrsOf types.str));
843               default = [ ];
844               example = literalExpression ''
845                 [
846                   { scheme = "http"; } # no http previews
847                   { netloc = "www.acme.com"; path = "/foo"; } # block http(s)://www.acme.com/foo
848                 ]
849               '';
850               description = ''
851                 Optional list of URL matches that the URL preview spider is
852                 denied from accessing.
853               '';
854             };
856             max_upload_size = mkOption {
857               type = types.str;
858               default = "50M";
859               example = "100M";
860               description = ''
861                 The largest allowed upload size in bytes
862               '';
863             };
865             max_image_pixels = mkOption {
866               type = types.str;
867               default = "32M";
868               example = "64M";
869               description = ''
870                 Maximum number of pixels that will be thumbnailed
871               '';
872             };
874             dynamic_thumbnails = mkOption {
875               type = types.bool;
876               default = false;
877               example = true;
878               description = ''
879                 Whether to generate new thumbnails on the fly to precisely match
880                 the resolution requested by the client. If true then whenever
881                 a new resolution is requested by the client the server will
882                 generate a new thumbnail. If false the server will pick a thumbnail
883                 from a precalculated list.
884               '';
885             };
887             turn_uris = mkOption {
888               type = types.listOf types.str;
889               default = [ ];
890               example = [
891                 "turn:turn.example.com:3487?transport=udp"
892                 "turn:turn.example.com:3487?transport=tcp"
893                 "turns:turn.example.com:5349?transport=udp"
894                 "turns:turn.example.com:5349?transport=tcp"
895               ];
896               description = ''
897                 The public URIs of the TURN server to give to clients
898               '';
899             };
900             turn_shared_secret = mkOption {
901               type = types.str;
902               default = "";
903               example = literalExpression ''
904                 config.services.coturn.static-auth-secret
905               '';
906               description = ''
907                 The shared secret used to compute passwords for the TURN server.
909                 Secrets should be passed in via `extraConfigFiles`!
910               '';
911             };
913             trusted_key_servers = mkOption {
914               type = types.listOf (types.submodule {
915                 freeformType = format.type;
916                 options = {
917                   server_name = mkOption {
918                     type = types.str;
919                     example = "matrix.org";
920                     description = ''
921                       Hostname of the trusted server.
922                     '';
923                   };
924                 };
925               });
926               default = [{
927                 server_name = "matrix.org";
928                 verify_keys = {
929                   "ed25519:auto" = "Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw";
930                 };
931               }];
932               description = ''
933                 The trusted servers to download signing keys from.
934               '';
935             };
937             app_service_config_files = mkOption {
938               type = types.listOf types.path;
939               default = [ ];
940               description = ''
941                 A list of application service config file to use
942               '';
943             };
945             redis = lib.mkOption {
946               type = types.submodule {
947                 freeformType = format.type;
948                 options = {
949                   enabled = lib.mkOption {
950                     type = types.bool;
951                     default = false;
952                     description = ''
953                       Whether to use redis support
954                     '';
955                   };
956                 };
957               };
958               default = { };
959               description = ''
960                 Redis configuration for synapse.
962                 See the
963                 [upstream documentation](https://github.com/element-hq/synapse/blob/v${pkgs.matrix-synapse-unwrapped.version}/docs/usage/configuration/config_documentation.md#redis)
964                 for available options.
965               '';
966             };
967           };
968         };
969       };
971       workers = lib.mkOption {
972         default = { };
973         description = ''
974           Options for configuring workers. Worker support will be enabled if at least one worker is configured here.
976           See the [worker documention](https://element-hq.github.io/synapse/latest/workers.html#worker-configuration)
977           for possible options for each worker. Worker-specific options overriding the shared homeserver configuration can be
978           specified here for each worker.
980           ::: {.note}
981             Worker support will add a replication listener on port 9093 to the main synapse process using the default
982             value of [`services.matrix-synapse.settings.listeners`](#opt-services.matrix-synapse.settings.listeners) and configure that
983             listener as `services.matrix-synapse.settings.instance_map.main`.
984             If you set either of those options, make sure to configure a replication listener yourself.
986             A redis server is required for running workers. A local one can be enabled
987             using [`services.matrix-synapse.configureRedisLocally`](#opt-services.matrix-synapse.configureRedisLocally).
989             Workers also require a proper reverse proxy setup to direct incoming requests to the appropriate process. See
990             the [reverse proxy documentation](https://element-hq.github.io/synapse/latest/reverse_proxy.html) for a
991             general reverse proxying setup and
992             the [worker documentation](https://element-hq.github.io/synapse/latest/workers.html#available-worker-applications)
993             for the available endpoints per worker application.
994           :::
995         '';
996         type = types.attrsOf (types.submodule ({name, ...}: {
997           freeformType = format.type;
998           options = {
999             worker_app = lib.mkOption {
1000               type = types.enum [
1001                 "synapse.app.generic_worker"
1002                 "synapse.app.media_repository"
1003               ];
1004               description = "Type of this worker";
1005               default = "synapse.app.generic_worker";
1006             };
1007             worker_listeners = lib.mkOption {
1008               default = [ ];
1009               type = types.listOf (listenerType true);
1010               description = ''
1011                 List of ports that this worker should listen on, their purpose and their configuration.
1012               '';
1013             };
1014             worker_log_config = lib.mkOption {
1015               type = types.path;
1016               default = genLogConfigFile "synapse-${name}";
1017               defaultText = logConfigText "synapse-${name}";
1018               description = ''
1019                 The file for log configuration.
1021                 See the [python documentation](https://docs.python.org/3/library/logging.config.html#configuration-dictionary-schema)
1022                 for the schema and the [upstream repository](https://github.com/element-hq/synapse/blob/v${pkgs.matrix-synapse-unwrapped.version}/docs/sample_log_config.yaml)
1023                 for an example.
1024               '';
1025             };
1026           };
1027         }));
1028         default = { };
1029         example = lib.literalExpression ''
1030           {
1031             "federation_sender" = { };
1032             "federation_receiver" = {
1033               worker_listeners = [
1034                 {
1035                   type = "http";
1036                   port = 8009;
1037                   bind_addresses = [ "127.0.0.1" ];
1038                   tls = false;
1039                   x_forwarded = true;
1040                   resources = [{
1041                     names = [ "federation" ];
1042                   }];
1043                 }
1044               ];
1045             };
1046           }
1047         '';
1048       };
1050       extraConfigFiles = mkOption {
1051         type = types.listOf types.path;
1052         default = [ ];
1053         description = ''
1054           Extra config files to include.
1056           The configuration files will be included based on the command line
1057           argument --config-path. This allows to configure secrets without
1058           having to go through the Nix store, e.g. based on deployment keys if
1059           NixOps is in use.
1060         '';
1061       };
1063       configureRedisLocally = lib.mkOption {
1064         type = types.bool;
1065         default = false;
1066         description = ''
1067           Whether to automatically configure a local redis server for matrix-synapse.
1068         '';
1069       };
1070     };
1071   };
1073   config = mkIf cfg.enable {
1074     assertions = [
1075       {
1076         assertion = clientListener != null;
1077         message = ''
1078           At least one listener which serves the `client` resource via HTTP is required
1079           by synapse in `services.matrix-synapse.settings.listeners` or in one of the workers!
1080         '';
1081       }
1082       {
1083         assertion = hasWorkers -> cfg.settings.redis.enabled;
1084         message = ''
1085           Workers for matrix-synapse require configuring a redis instance. This can be done
1086           automatically by setting `services.matrix-synapse.configureRedisLocally = true`.
1087         '';
1088       }
1089       {
1090         assertion =
1091           let
1092             main = cfg.settings.instance_map.main;
1093             listener = lib.findFirst
1094               (
1095                 listener:
1096                   (
1097                     lib.hasAttr "port" main && listener.port or null == main.port
1098                     || lib.hasAttr "path" main && listener.path or null == main.path
1099                   )
1100                   && listenerSupportsResource "replication" listener
1101                   && (
1102                     lib.hasAttr "host" main &&  lib.any (bind: bind == main.host || bind == "0.0.0.0" || bind == "::") listener.bind_addresses
1103                     || lib.hasAttr "path" main
1104                   )
1105               )
1106               null
1107               cfg.settings.listeners;
1108           in
1109           hasWorkers -> (cfg.settings.instance_map ? main && listener != null);
1110         message = ''
1111           Workers for matrix-synapse require setting `services.matrix-synapse.settings.instance_map.main`
1112           to any listener configured in `services.matrix-synapse.settings.listeners` with a `"replication"`
1113           resource.
1115           This is done by default unless you manually configure either of those settings.
1116         '';
1117       }
1118       {
1119         assertion = cfg.enableRegistrationScript -> clientListener.path == null;
1120         message = ''
1121           The client listener on matrix-synapse is configured to use UNIX domain sockets.
1122           This configuration is incompatible with the `register_new_matrix_user` script.
1124           Disable  `services.matrix-synapse.enableRegistrationScript` to continue.
1125         '';
1126       }
1127     ]
1128     ++ (map (listener: {
1129       assertion = (listener.path == null) != (listener.bind_addresses == null);
1130       message = ''
1131         Listeners require either a UNIX domain socket `path` or `bind_addresses` for a TCP socket.
1132       '';
1133     }) cfg.settings.listeners)
1134     ++ (map (listener: {
1135       assertion = listener.path != null -> (listener.bind_addresses == null && listener.port == null && listener.tls == null);
1136       message = let
1137         formatKeyValue = key: value: lib.optionalString (value != null) "  - ${key}=${toString value}\n";
1138       in ''
1139         Listener configured with UNIX domain socket (${toString listener.path}) ignores the following options:
1140         ${formatKeyValue "bind_addresses" listener.bind_addresses}${formatKeyValue "port" listener.port}${formatKeyValue "tls" listener.tls}
1141       '';
1142     }) cfg.settings.listeners)
1143     ++ (map (listener: {
1144       assertion = listener.path == null || listener.type == "http";
1145       message = ''
1146         Listener configured with UNIX domain socket (${toString listener.path}) only supports the "http" listener type.
1147       '';
1148     }) cfg.settings.listeners);
1150     services.matrix-synapse.settings.redis = lib.mkIf cfg.configureRedisLocally {
1151       enabled = true;
1152       path = config.services.redis.servers.matrix-synapse.unixSocket;
1153     };
1154     services.matrix-synapse.settings.instance_map.main = lib.mkIf hasWorkers (lib.mkDefault {
1155       path = "/run/matrix-synapse/main_replication.sock";
1156     });
1158     services.matrix-synapse.serviceUnit = if hasWorkers then "matrix-synapse.target" else "matrix-synapse.service";
1159     services.matrix-synapse.configFile = configFile;
1160     services.matrix-synapse.package = wrapped;
1162     # default them, so they are additive
1163     services.matrix-synapse.extras = defaultExtras;
1165     services.matrix-synapse.log = mapAttrsRecursive (const mkDefault) defaultCommonLogConfig;
1167     users.users.matrix-synapse = {
1168       group = "matrix-synapse";
1169       home = cfg.dataDir;
1170       createHome = true;
1171       shell = "${pkgs.bash}/bin/bash";
1172       uid = config.ids.uids.matrix-synapse;
1173     };
1175     users.groups.matrix-synapse = {
1176       gid = config.ids.gids.matrix-synapse;
1177     };
1179     systemd.targets.matrix-synapse = lib.mkIf hasWorkers {
1180       description = "Synapse Matrix parent target";
1181       wants = [ "network-online.target" ];
1182       after = [ "network-online.target" ] ++ optional hasLocalPostgresDB "postgresql.service";
1183       wantedBy = [ "multi-user.target" ];
1184     };
1186     systemd.services =
1187       let
1188         targetConfig =
1189           if hasWorkers
1190           then {
1191             partOf = [ "matrix-synapse.target" ];
1192             wantedBy = [ "matrix-synapse.target" ];
1193             unitConfig.ReloadPropagatedFrom = "matrix-synapse.target";
1194             requires = optional hasLocalPostgresDB "postgresql.service";
1195           }
1196           else {
1197             wants = [ "network-online.target" ];
1198             after = [ "network-online.target" ] ++ optional hasLocalPostgresDB "postgresql.service";
1199             requires = optional hasLocalPostgresDB "postgresql.service";
1200             wantedBy = [ "multi-user.target" ];
1201           };
1202         baseServiceConfig = {
1203           environment = optionalAttrs (cfg.withJemalloc) {
1204             LD_PRELOAD = "${pkgs.jemalloc}/lib/libjemalloc.so";
1205           };
1206           serviceConfig = {
1207             Type = "notify";
1208             User = "matrix-synapse";
1209             Group = "matrix-synapse";
1210             WorkingDirectory = cfg.dataDir;
1211             RuntimeDirectory = "matrix-synapse";
1212             RuntimeDirectoryPreserve = true;
1213             ExecReload = "${pkgs.util-linux}/bin/kill -HUP $MAINPID";
1214             Restart = "on-failure";
1215             UMask = "0077";
1217             # Security Hardening
1218             # Refer to systemd.exec(5) for option descriptions.
1219             CapabilityBoundingSet = [ "" ];
1220             LockPersonality = true;
1221             NoNewPrivileges = true;
1222             PrivateDevices = true;
1223             PrivateTmp = true;
1224             PrivateUsers = true;
1225             ProcSubset = "pid";
1226             ProtectClock = true;
1227             ProtectControlGroups = true;
1228             ProtectHome = true;
1229             ProtectHostname = true;
1230             ProtectKernelLogs = true;
1231             ProtectKernelModules = true;
1232             ProtectKernelTunables = true;
1233             ProtectProc = "invisible";
1234             ProtectSystem = "strict";
1235             ReadWritePaths = [ cfg.dataDir cfg.settings.media_store_path ] ++
1236               (map (listener: dirOf listener.path) (filter (listener: listener.path != null) cfg.settings.listeners));
1237             RemoveIPC = true;
1238             RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
1239             RestrictNamespaces = true;
1240             RestrictRealtime = true;
1241             RestrictSUIDSGID = true;
1242             SystemCallArchitectures = "native";
1243             SystemCallFilter = [ "@system-service" "~@resources" "~@privileged" ];
1244           };
1245         }
1246         // targetConfig;
1247         genWorkerService = name: workerCfg:
1248           let
1249             finalWorkerCfg = workerCfg // { worker_name = name; };
1250             workerConfigFile = format.generate "worker-${name}.yaml" finalWorkerCfg;
1251           in
1252           {
1253             name = "matrix-synapse-worker-${name}";
1254             value = lib.mkMerge [
1255               baseServiceConfig
1256               {
1257                 description = "Synapse Matrix worker ${name}";
1258                 # make sure the main process starts first for potential database migrations
1259                 after = [ "matrix-synapse.service" ];
1260                 requires = [ "matrix-synapse.service" ];
1261                 serviceConfig = {
1262                   ExecStart = ''
1263                     ${cfg.package}/bin/synapse_worker \
1264                       ${ concatMapStringsSep "\n  " (x: "--config-path ${x} \\") ([ configFile workerConfigFile ] ++ cfg.extraConfigFiles) }
1265                       --keys-directory ${cfg.dataDir}
1266                   '';
1267                 };
1268               }
1269             ];
1270           };
1271       in
1272       {
1273         matrix-synapse = lib.mkMerge [
1274           baseServiceConfig
1275           {
1276             description = "Synapse Matrix homeserver";
1277             preStart = ''
1278               ${cfg.package}/bin/synapse_homeserver \
1279                 --config-path ${configFile} \
1280                 --keys-directory ${cfg.dataDir} \
1281                 --generate-keys
1282             '';
1283             serviceConfig = {
1284               ExecStartPre = [
1285                 ("+" + (pkgs.writeShellScript "matrix-synapse-fix-permissions" ''
1286                   chown matrix-synapse:matrix-synapse ${cfg.settings.signing_key_path}
1287                   chmod 0600 ${cfg.settings.signing_key_path}
1288                 ''))
1289               ];
1290               ExecStart = ''
1291                 ${cfg.package}/bin/synapse_homeserver \
1292                   ${ concatMapStringsSep "\n  " (x: "--config-path ${x} \\") ([ configFile ] ++ cfg.extraConfigFiles) }
1293                   --keys-directory ${cfg.dataDir}
1294               '';
1295             };
1296           }
1297         ];
1298       }
1299       // (lib.mapAttrs' genWorkerService cfg.workers);
1301     services.redis.servers.matrix-synapse = lib.mkIf cfg.configureRedisLocally {
1302       enable = true;
1303       user = "matrix-synapse";
1304     };
1306     environment.systemPackages = lib.optionals cfg.enableRegistrationScript [
1307       registerNewMatrixUser
1308     ];
1309   };
1311   meta = {
1312     buildDocsInSandbox = false;
1313     doc = ./synapse.md;
1314     maintainers = teams.matrix.members;
1315   };