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