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