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