1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.matrix-synapse;
7 logConfigFile = pkgs.writeText "log_config.yaml" cfg.logConfig;
8 mkResource = r: ''{names: ${builtins.toJSON r.names}, compress: ${boolToString r.compress}}'';
9 mkListener = l: ''{port: ${toString l.port}, bind_address: "${l.bind_address}", type: ${l.type}, tls: ${boolToString l.tls}, x_forwarded: ${boolToString l.x_forwarded}, resources: [${concatStringsSep "," (map mkResource l.resources)}]}'';
10 configFile = pkgs.writeText "homeserver.yaml" ''
11${optionalString (cfg.tls_certificate_path != null) ''
12tls_certificate_path: "${cfg.tls_certificate_path}"
13''}
14${optionalString (cfg.tls_private_key_path != null) ''
15tls_private_key_path: "${cfg.tls_private_key_path}"
16''}
17${optionalString (cfg.tls_dh_params_path != null) ''
18tls_dh_params_path: "${cfg.tls_dh_params_path}"
19''}
20no_tls: ${boolToString cfg.no_tls}
21${optionalString (cfg.bind_port != null) ''
22bind_port: ${toString cfg.bind_port}
23''}
24${optionalString (cfg.unsecure_port != null) ''
25unsecure_port: ${toString cfg.unsecure_port}
26''}
27${optionalString (cfg.bind_host != null) ''
28bind_host: "${cfg.bind_host}"
29''}
30server_name: "${cfg.server_name}"
31pid_file: "/var/run/matrix-synapse.pid"
32web_client: ${boolToString cfg.web_client}
33${optionalString (cfg.public_baseurl != null) ''
34public_baseurl: "${cfg.public_baseurl}"
35''}
36listeners: [${concatStringsSep "," (map mkListener cfg.listeners)}]
37database: {
38 name: "${cfg.database_type}",
39 args: {
40 ${concatStringsSep ",\n " (
41 mapAttrsToList (n: v: "\"${n}\": ${v}") cfg.database_args
42 )}
43 }
44}
45event_cache_size: "${cfg.event_cache_size}"
46verbose: ${cfg.verbose}
47log_file: "/var/log/matrix-synapse/homeserver.log"
48log_config: "${logConfigFile}"
49rc_messages_per_second: ${cfg.rc_messages_per_second}
50rc_message_burst_count: ${cfg.rc_message_burst_count}
51federation_rc_window_size: ${cfg.federation_rc_window_size}
52federation_rc_sleep_limit: ${cfg.federation_rc_sleep_limit}
53federation_rc_sleep_delay: ${cfg.federation_rc_sleep_delay}
54federation_rc_reject_limit: ${cfg.federation_rc_reject_limit}
55federation_rc_concurrent: ${cfg.federation_rc_concurrent}
56media_store_path: "/var/lib/matrix-synapse/media"
57uploads_path: "/var/lib/matrix-synapse/uploads"
58max_upload_size: "${cfg.max_upload_size}"
59max_image_pixels: "${cfg.max_image_pixels}"
60dynamic_thumbnails: ${boolToString cfg.dynamic_thumbnails}
61url_preview_enabled: ${boolToString cfg.url_preview_enabled}
62${optionalString (cfg.url_preview_enabled == true) ''
63url_preview_ip_range_blacklist: ${builtins.toJSON cfg.url_preview_ip_range_blacklist}
64url_preview_ip_range_whitelist: ${builtins.toJSON cfg.url_preview_ip_range_whitelist}
65url_preview_url_blacklist: ${builtins.toJSON cfg.url_preview_url_blacklist}
66''}
67recaptcha_private_key: "${cfg.recaptcha_private_key}"
68recaptcha_public_key: "${cfg.recaptcha_public_key}"
69enable_registration_captcha: ${boolToString cfg.enable_registration_captcha}
70turn_uris: ${builtins.toJSON cfg.turn_uris}
71turn_shared_secret: "${cfg.turn_shared_secret}"
72enable_registration: ${boolToString cfg.enable_registration}
73${optionalString (cfg.registration_shared_secret != null) ''
74registration_shared_secret: "${cfg.registration_shared_secret}"
75''}
76recaptcha_siteverify_api: "https://www.google.com/recaptcha/api/siteverify"
77turn_user_lifetime: "${cfg.turn_user_lifetime}"
78user_creation_max_duration: ${cfg.user_creation_max_duration}
79bcrypt_rounds: ${cfg.bcrypt_rounds}
80allow_guest_access: ${boolToString cfg.allow_guest_access}
81trusted_third_party_id_servers: ${builtins.toJSON cfg.trusted_third_party_id_servers}
82room_invite_state_types: ${builtins.toJSON cfg.room_invite_state_types}
83${optionalString (cfg.macaroon_secret_key != null) ''
84 macaroon_secret_key: "${cfg.macaroon_secret_key}"
85''}
86expire_access_token: ${boolToString cfg.expire_access_token}
87enable_metrics: ${boolToString cfg.enable_metrics}
88report_stats: ${boolToString cfg.report_stats}
89signing_key_path: "/var/lib/matrix-synapse/homeserver.signing.key"
90key_refresh_interval: "${cfg.key_refresh_interval}"
91perspectives:
92 servers: {
93 ${concatStringsSep "},\n" (mapAttrsToList (n: v: ''
94 "${n}": {
95 "verify_keys": {
96 ${concatStringsSep "},\n" (mapAttrsToList (n: v: ''
97 "${n}": {
98 "key": "${v}"
99 }'') v)}
100 }
101 '') cfg.servers)}
102 }
103 }
104app_service_config_files: ${builtins.toJSON cfg.app_service_config_files}
105
106${cfg.extraConfig}
107'';
108in {
109 options = {
110 services.matrix-synapse = {
111 enable = mkEnableOption "matrix.org synapse";
112 package = mkOption {
113 type = types.package;
114 default = pkgs.matrix-synapse;
115 defaultText = "pkgs.matrix-synapse";
116 description = ''
117 Overridable attribute of the matrix synapse server package to use.
118 '';
119 };
120 no_tls = mkOption {
121 type = types.bool;
122 default = false;
123 description = ''
124 Don't bind to the https port
125 '';
126 };
127 bind_port = mkOption {
128 type = types.nullOr types.int;
129 default = null;
130 example = 8448;
131 description = ''
132 DEPRECATED: Use listeners instead.
133 The port to listen for HTTPS requests on.
134 For when matrix traffic is sent directly to synapse.
135 '';
136 };
137 unsecure_port = mkOption {
138 type = types.nullOr types.int;
139 default = null;
140 example = 8008;
141 description = ''
142 DEPRECATED: Use listeners instead.
143 The port to listen for HTTP requests on.
144 For when matrix traffic passes through loadbalancer that unwraps TLS.
145 '';
146 };
147 bind_host = mkOption {
148 type = types.nullOr types.str;
149 default = null;
150 description = ''
151 DEPRECATED: Use listeners instead.
152 Local interface to listen on.
153 The empty string will cause synapse to listen on all interfaces.
154 '';
155 };
156 tls_certificate_path = mkOption {
157 type = types.nullOr types.str;
158 default = null;
159 example = "/var/lib/matrix-synapse/homeserver.tls.crt";
160 description = ''
161 PEM encoded X509 certificate for TLS.
162 You can replace the self-signed certificate that synapse
163 autogenerates on launch with your own SSL certificate + key pair
164 if you like. Any required intermediary certificates can be
165 appended after the primary certificate in hierarchical order.
166 '';
167 };
168 tls_private_key_path = mkOption {
169 type = types.nullOr types.str;
170 default = null;
171 example = "/var/lib/matrix-synapse/homeserver.tls.key";
172 description = ''
173 PEM encoded private key for TLS. Specify null if synapse is not
174 speaking TLS directly.
175 '';
176 };
177 tls_dh_params_path = mkOption {
178 type = types.nullOr types.str;
179 default = null;
180 example = "/var/lib/matrix-synapse/homeserver.tls.dh";
181 description = ''
182 PEM dh parameters for ephemeral keys
183 '';
184 };
185 server_name = mkOption {
186 type = types.str;
187 example = "example.com";
188 description = ''
189 The domain name of the server, with optional explicit port.
190 This is used by remote servers to connect to this server,
191 e.g. matrix.org, localhost:8080, etc.
192 This is also the last part of your UserID.
193 '';
194 };
195 web_client = mkOption {
196 type = types.bool;
197 default = false;
198 description = ''
199 Whether to serve a web client from the HTTP/HTTPS root resource.
200 '';
201 };
202 public_baseurl = mkOption {
203 type = types.nullOr types.str;
204 default = null;
205 example = "https://example.com:8448/";
206 description = ''
207 The public-facing base URL for the client API (not including _matrix/...)
208 '';
209 };
210 listeners = mkOption {
211 type = types.listOf (types.submodule {
212 options = {
213 port = mkOption {
214 type = types.int;
215 example = 8448;
216 description = ''
217 The port to listen for HTTP(S) requests on.
218 '';
219 };
220 bind_address = mkOption {
221 type = types.str;
222 default = "";
223 example = "203.0.113.42";
224 description = ''
225 Local interface to listen on.
226 The empty string will cause synapse to listen on all interfaces.
227 '';
228 };
229 type = mkOption {
230 type = types.str;
231 default = "http";
232 description = ''
233 Type of listener.
234 '';
235 };
236 tls = mkOption {
237 type = types.bool;
238 default = true;
239 description = ''
240 Whether to listen for HTTPS connections rather than HTTP.
241 '';
242 };
243 x_forwarded = mkOption {
244 type = types.bool;
245 default = false;
246 description = ''
247 Use the X-Forwarded-For (XFF) header as the client IP and not the
248 actual client IP.
249 '';
250 };
251 resources = mkOption {
252 type = types.listOf (types.submodule {
253 options = {
254 names = mkOption {
255 type = types.listOf types.str;
256 description = ''
257 List of resources to host on this listener.
258 '';
259 example = ["client" "webclient" "federation"];
260 };
261 compress = mkOption {
262 type = types.bool;
263 description = ''
264 Should synapse compress HTTP responses to clients that support it?
265 This should be disabled if running synapse behind a load balancer
266 that can do automatic compression.
267 '';
268 };
269 };
270 });
271 description = ''
272 List of HTTP resources to serve on this listener.
273 '';
274 };
275 };
276 });
277 default = [{
278 port = 8448;
279 bind_address = "";
280 type = "http";
281 tls = true;
282 x_forwarded = false;
283 resources = [
284 { names = ["client" "webclient"]; compress = true; }
285 { names = ["federation"]; compress = false; }
286 ];
287 }];
288 description = ''
289 List of ports that Synapse should listen on, their purpose and their configuration.
290 '';
291 };
292 verbose = mkOption {
293 type = types.str;
294 default = "0";
295 description = "Logging verbosity level.";
296 };
297 rc_messages_per_second = mkOption {
298 type = types.str;
299 default = "0.2";
300 description = "Number of messages a client can send per second";
301 };
302 rc_message_burst_count = mkOption {
303 type = types.str;
304 default = "10.0";
305 description = "Number of message a client can send before being throttled";
306 };
307 federation_rc_window_size = mkOption {
308 type = types.str;
309 default = "1000";
310 description = "The federation window size in milliseconds";
311 };
312 federation_rc_sleep_limit = mkOption {
313 type = types.str;
314 default = "10";
315 description = ''
316 The number of federation requests from a single server in a window
317 before the server will delay processing the request.
318 '';
319 };
320 federation_rc_sleep_delay = mkOption {
321 type = types.str;
322 default = "500";
323 description = ''
324 The duration in milliseconds to delay processing events from
325 remote servers by if they go over the sleep limit.
326 '';
327 };
328 federation_rc_reject_limit = mkOption {
329 type = types.str;
330 default = "50";
331 description = ''
332 The maximum number of concurrent federation requests allowed
333 from a single server
334 '';
335 };
336 federation_rc_concurrent = mkOption {
337 type = types.str;
338 default = "3";
339 description = "The number of federation requests to concurrently process from a single server";
340 };
341 database_type = mkOption {
342 type = types.enum [ "sqlite3" "psycopg2" ];
343 default = "sqlite3";
344 description = ''
345 The database engine name. Can be sqlite or psycopg2.
346 '';
347 };
348 database_args = mkOption {
349 type = types.attrs;
350 default = {
351 database = "/var/lib/matrix-synapse/homeserver.db";
352 };
353 description = ''
354 Arguments to pass to the engine.
355 '';
356 };
357 event_cache_size = mkOption {
358 type = types.str;
359 default = "10K";
360 description = "Number of events to cache in memory.";
361 };
362 url_preview_enabled = mkOption {
363 type = types.bool;
364 default = false;
365 description = ''
366 Is the preview URL API enabled? If enabled, you *must* specify an
367 explicit url_preview_ip_range_blacklist of IPs that the spider is
368 denied from accessing.
369 '';
370 };
371 url_preview_ip_range_blacklist = mkOption {
372 type = types.listOf types.str;
373 default = [];
374 description = ''
375 List of IP address CIDR ranges that the URL preview spider is denied
376 from accessing.
377 '';
378 };
379 url_preview_ip_range_whitelist = mkOption {
380 type = types.listOf types.str;
381 default = [];
382 description = ''
383 List of IP address CIDR ranges that the URL preview spider is allowed
384 to access even if they are specified in
385 url_preview_ip_range_blacklist.
386 '';
387 };
388 url_preview_url_blacklist = mkOption {
389 type = types.listOf types.str;
390 default = [
391 "127.0.0.0/8"
392 "10.0.0.0/8"
393 "172.16.0.0/12"
394 "192.168.0.0/16"
395 "100.64.0.0/10"
396 "169.254.0.0/16"
397 ];
398 description = ''
399 Optional list of URL matches that the URL preview spider is
400 denied from accessing.
401 '';
402 };
403 recaptcha_private_key = mkOption {
404 type = types.str;
405 default = "";
406 description = ''
407 This Home Server's ReCAPTCHA private key.
408 '';
409 };
410 recaptcha_public_key = mkOption {
411 type = types.str;
412 default = "";
413 description = ''
414 This Home Server's ReCAPTCHA public key.
415 '';
416 };
417 enable_registration_captcha = mkOption {
418 type = types.bool;
419 default = false;
420 description = ''
421 Enables ReCaptcha checks when registering, preventing signup
422 unless a captcha is answered. Requires a valid ReCaptcha
423 public/private key.
424 '';
425 };
426 turn_uris = mkOption {
427 type = types.listOf types.str;
428 default = [];
429 description = ''
430 The public URIs of the TURN server to give to clients
431 '';
432 };
433 turn_shared_secret = mkOption {
434 type = types.str;
435 default = "";
436 description = ''
437 The shared secret used to compute passwords for the TURN server
438 '';
439 };
440 turn_user_lifetime = mkOption {
441 type = types.str;
442 default = "1h";
443 description = "How long generated TURN credentials last";
444 };
445 enable_registration = mkOption {
446 type = types.bool;
447 default = false;
448 description = ''
449 Enable registration for new users.
450 '';
451 };
452 registration_shared_secret = mkOption {
453 type = types.nullOr types.str;
454 default = null;
455 description = ''
456 If set, allows registration by anyone who also has the shared
457 secret, even if registration is otherwise disabled.
458 '';
459 };
460 enable_metrics = mkOption {
461 type = types.bool;
462 default = false;
463 description = ''
464 Enable collection and rendering of performance metrics
465 '';
466 };
467 report_stats = mkOption {
468 type = types.bool;
469 default = false;
470 description = ''
471 '';
472 };
473 servers = mkOption {
474 type = types.attrsOf (types.attrsOf types.str);
475 default = {
476 "matrix.org" = {
477 "ed25519:auto" = "Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw";
478 };
479 };
480 description = ''
481 The trusted servers to download signing keys from.
482 '';
483 };
484 max_upload_size = mkOption {
485 type = types.str;
486 default = "10M";
487 description = "The largest allowed upload size in bytes";
488 };
489 max_image_pixels = mkOption {
490 type = types.str;
491 default = "32M";
492 description = "Maximum number of pixels that will be thumbnailed";
493 };
494 dynamic_thumbnails = mkOption {
495 type = types.bool;
496 default = false;
497 description = ''
498 Whether to generate new thumbnails on the fly to precisely match
499 the resolution requested by the client. If true then whenever
500 a new resolution is requested by the client the server will
501 generate a new thumbnail. If false the server will pick a thumbnail
502 from a precalculated list.
503 '';
504 };
505 user_creation_max_duration = mkOption {
506 type = types.str;
507 default = "1209600000";
508 description = ''
509 Sets the expiry for the short term user creation in
510 milliseconds. The default value is two weeks.
511 '';
512 };
513 bcrypt_rounds = mkOption {
514 type = types.str;
515 default = "12";
516 description = ''
517 Set the number of bcrypt rounds used to generate password hash.
518 Larger numbers increase the work factor needed to generate the hash.
519 '';
520 };
521 allow_guest_access = mkOption {
522 type = types.bool;
523 default = false;
524 description = ''
525 Allows users to register as guests without a password/email/etc, and
526 participate in rooms hosted on this server which have been made
527 accessible to anonymous users.
528 '';
529 };
530 trusted_third_party_id_servers = mkOption {
531 type = types.listOf types.str;
532 default = ["matrix.org"];
533 description = ''
534 The list of identity servers trusted to verify third party identifiers by this server.
535 '';
536 };
537 room_invite_state_types = mkOption {
538 type = types.listOf types.str;
539 default = ["m.room.join_rules" "m.room.canonical_alias" "m.room.avatar" "m.room.name"];
540 description = ''
541 A list of event types that will be included in the room_invite_state
542 '';
543 };
544 macaroon_secret_key = mkOption {
545 type = types.nullOr types.str;
546 default = null;
547 description = ''
548 Secret key for authentication tokens
549 '';
550 };
551 expire_access_token = mkOption {
552 type = types.bool;
553 default = false;
554 description = ''
555 Whether to enable access token expiration.
556 '';
557 };
558 key_refresh_interval = mkOption {
559 type = types.str;
560 default = "1d";
561 description = ''
562 How long key response published by this server is valid for.
563 Used to set the valid_until_ts in /key/v2 APIs.
564 Determines how quickly servers will query to check which keys
565 are still valid.
566 '';
567 };
568 app_service_config_files = mkOption {
569 type = types.listOf types.path;
570 default = [ ];
571 description = ''
572 A list of application service config file to use
573 '';
574 };
575 extraConfig = mkOption {
576 type = types.lines;
577 default = "";
578 description = ''
579 Extra config options for matrix-synapse.
580 '';
581 };
582 logConfig = mkOption {
583 type = types.lines;
584 default = readFile ./matrix-synapse-log_config.yaml;
585 description = ''
586 A yaml python logging config file
587 '';
588 };
589 };
590 };
591
592 config = mkIf cfg.enable {
593 users.extraUsers = [
594 { name = "matrix-synapse";
595 group = "matrix-synapse";
596 home = "/var/lib/matrix-synapse";
597 createHome = true;
598 shell = "${pkgs.bash}/bin/bash";
599 uid = config.ids.uids.matrix-synapse;
600 } ];
601
602 users.extraGroups = [
603 { name = "matrix-synapse";
604 gid = config.ids.gids.matrix-synapse;
605 } ];
606
607 systemd.services.matrix-synapse = {
608 description = "Synapse Matrix homeserver";
609 after = [ "network.target" ];
610 wantedBy = [ "multi-user.target" ];
611 preStart = ''
612 ${cfg.package}/bin/homeserver \
613 --config-path ${configFile} \
614 --keys-directory /var/lib/matrix-synapse \
615 --generate-keys
616 '';
617 serviceConfig = {
618 Type = "simple";
619 User = "matrix-synapse";
620 Group = "matrix-synapse";
621 WorkingDirectory = "/var/lib/matrix-synapse";
622 PermissionsStartOnly = true;
623 ExecStart = "${cfg.package}/bin/homeserver --config-path ${configFile} --keys-directory /var/lib/matrix-synapse";
624 Restart = "on-failure";
625 };
626 };
627 };
628}