diff --git a/lib/default.nix b/lib/default.nix
index ccae0bbc3ab418..952277b2e18936 100644
--- a/lib/default.nix
+++ b/lib/default.nix
@@ -97,7 +97,7 @@ let
getName getVersion
nameFromURL enableFeature enableFeatureAs withFeature
withFeatureAs fixedWidthString fixedWidthNumber isStorePath
- toInt readPathsFromFile fileContents;
+ toFloat toInt readPathsFromFile fileContents;
inherit (self.stringsWithDeps) textClosureList textClosureMap
noDepEntry fullDepEntry packEntry stringAfter;
inherit (self.customisation) overrideDerivation makeOverridable
diff --git a/lib/strings.nix b/lib/strings.nix
index 49fa0196a0b266..9c807799cd65c2 100644
--- a/lib/strings.nix
+++ b/lib/strings.nix
@@ -15,6 +15,7 @@ rec {
filter
fromJSON
head
+ isFloat
isInt
isList
isString
@@ -674,6 +675,25 @@ rec {
else
false;
+ /* Parse a string as a float.
+
+ Type: string -> float
+
+ Example:
+ toFloat "1.2"
+ => 1.2
+ toFloat "-4.2"
+ => -4
+ toFloat "1"
+ => error: Could not convert 1 to float.
+ */
+ # Obviously, it is a bit hacky to use fromJSON this way.
+ toFloat = str:
+ let mayBeFloat = fromJSON str; in
+ if isFloat mayBeFloat
+ then mayBeFloat
+ else throw "Could not convert ${str} to float.";
+
/* Parse a string as an int.
Type: string -> int
diff --git a/nixos/modules/services/misc/matrix-synapse.nix b/nixos/modules/services/misc/matrix-synapse.nix
index dff587453042da..7b04bf278a83c9 100644
--- a/nixos/modules/services/misc/matrix-synapse.nix
+++ b/nixos/modules/services/misc/matrix-synapse.nix
@@ -7,118 +7,108 @@ let
pg = config.services.postgresql;
usePostgresql = cfg.database_type == "psycopg2";
logConfigFile = pkgs.writeText "log_config.yaml" cfg.logConfig;
- mkResource = r: ''{names: ${builtins.toJSON r.names}, compress: ${boolToString r.compress}}'';
- 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)}]}'';
pluginsEnv = cfg.package.python.buildEnv.override {
extraLibs = cfg.plugins;
};
- configFile = pkgs.writeText "homeserver.yaml" ''
-${optionalString (cfg.tls_certificate_path != null) ''
-tls_certificate_path: "${cfg.tls_certificate_path}"
-''}
-${optionalString (cfg.tls_private_key_path != null) ''
-tls_private_key_path: "${cfg.tls_private_key_path}"
-''}
-${optionalString (cfg.tls_dh_params_path != null) ''
-tls_dh_params_path: "${cfg.tls_dh_params_path}"
-''}
-no_tls: ${boolToString cfg.no_tls}
-${optionalString (cfg.bind_port != null) ''
-bind_port: ${toString cfg.bind_port}
-''}
-${optionalString (cfg.unsecure_port != null) ''
-unsecure_port: ${toString cfg.unsecure_port}
-''}
-${optionalString (cfg.bind_host != null) ''
-bind_host: "${cfg.bind_host}"
-''}
-server_name: "${cfg.server_name}"
-pid_file: "/run/matrix-synapse.pid"
-${optionalString (cfg.public_baseurl != null) ''
-public_baseurl: "${cfg.public_baseurl}"
-''}
-listeners: [${concatStringsSep "," (map mkListener cfg.listeners)}]
-database: {
- name: "${cfg.database_type}",
- args: {
- ${concatStringsSep ",\n " (
- mapAttrsToList (n: v: "\"${n}\": ${builtins.toJSON v}") cfg.database_args
- )}
- }
-}
-event_cache_size: "${cfg.event_cache_size}"
-verbose: ${cfg.verbose}
-log_config: "${logConfigFile}"
-rc_messages_per_second: ${cfg.rc_messages_per_second}
-rc_message_burst_count: ${cfg.rc_message_burst_count}
-federation_rc_window_size: ${cfg.federation_rc_window_size}
-federation_rc_sleep_limit: ${cfg.federation_rc_sleep_limit}
-federation_rc_sleep_delay: ${cfg.federation_rc_sleep_delay}
-federation_rc_reject_limit: ${cfg.federation_rc_reject_limit}
-federation_rc_concurrent: ${cfg.federation_rc_concurrent}
-media_store_path: "${cfg.dataDir}/media"
-uploads_path: "${cfg.dataDir}/uploads"
-max_upload_size: "${cfg.max_upload_size}"
-max_image_pixels: "${cfg.max_image_pixels}"
-dynamic_thumbnails: ${boolToString cfg.dynamic_thumbnails}
-url_preview_enabled: ${boolToString cfg.url_preview_enabled}
-${optionalString (cfg.url_preview_enabled == true) ''
-url_preview_ip_range_blacklist: ${builtins.toJSON cfg.url_preview_ip_range_blacklist}
-url_preview_ip_range_whitelist: ${builtins.toJSON cfg.url_preview_ip_range_whitelist}
-url_preview_url_blacklist: ${builtins.toJSON cfg.url_preview_url_blacklist}
-''}
-recaptcha_private_key: "${cfg.recaptcha_private_key}"
-recaptcha_public_key: "${cfg.recaptcha_public_key}"
-enable_registration_captcha: ${boolToString cfg.enable_registration_captcha}
-turn_uris: ${builtins.toJSON cfg.turn_uris}
-turn_shared_secret: "${cfg.turn_shared_secret}"
-enable_registration: ${boolToString cfg.enable_registration}
-${optionalString (cfg.registration_shared_secret != null) ''
-registration_shared_secret: "${cfg.registration_shared_secret}"
-''}
-recaptcha_siteverify_api: "https://www.google.com/recaptcha/api/siteverify"
-turn_user_lifetime: "${cfg.turn_user_lifetime}"
-user_creation_max_duration: ${cfg.user_creation_max_duration}
-bcrypt_rounds: ${cfg.bcrypt_rounds}
-allow_guest_access: ${boolToString cfg.allow_guest_access}
-
-account_threepid_delegates:
- ${optionalString (cfg.account_threepid_delegates.email != null) "email: ${cfg.account_threepid_delegates.email}"}
- ${optionalString (cfg.account_threepid_delegates.msisdn != null) "msisdn: ${cfg.account_threepid_delegates.msisdn}"}
-
-room_prejoin_state:
- disable_default_event_types: ${boolToString cfg.room_prejoin_state.disable_default_event_types}
- additional_event_types: ${builtins.toJSON cfg.room_prejoin_state.additional_event_types}
-${optionalString (cfg.macaroon_secret_key != null) ''
- macaroon_secret_key: "${cfg.macaroon_secret_key}"
-''}
-expire_access_token: ${boolToString cfg.expire_access_token}
-enable_metrics: ${boolToString cfg.enable_metrics}
-report_stats: ${boolToString cfg.report_stats}
-signing_key_path: "${cfg.dataDir}/homeserver.signing.key"
-key_refresh_interval: "${cfg.key_refresh_interval}"
-perspectives:
- servers: {
- ${concatStringsSep "},\n" (mapAttrsToList (n: v: ''
- "${n}": {
- "verify_keys": {
- ${concatStringsSep "},\n" (mapAttrsToList (n: v: ''
- "${n}": {
- "key": "${v}"
- }'') v)}
- }
- '') cfg.servers)}
- }
- }
-redaction_retention_period: ${toString cfg.redaction_retention_period}
-app_service_config_files: ${builtins.toJSON cfg.app_service_config_files}
-${cfg.extraConfig}
-'';
+ # This is organized to match the sections in
+ # https://github.com/matrix-org/synapse/blob/develop/docs/sample_config.yaml
+ # Not everything in the config is configurable directly via Nix, so
+ # use extraConfig or settings to extend.
+ yamlConfig = {
+ # Server
+ inherit (cfg) server_name public_baseurl listeners;
+ inherit (cfg) bind_port unsecure_port bind_host; # deprecated, but keeping for backwards compatibility
+ pid_file = "/run/matrix-synapse.pid";
+
+ # Homeserver blocking
+ inherit (cfg) redaction_retention_period;
+
+ # TLS
+ inherit (cfg) tls_certificate_path tls_private_key_path no_tls;
+
+ # Federation
+
+ # Caching
+ inherit (cfg) event_cache_size;
+
+ # Database
+ database = {
+ name = cfg.database_type;
+ args = cfg.database_args;
+ };
+
+ # Logging
+ inherit (cfg) verbose;
+ log_config = logConfigFile;
+
+ # Ratelimiting
+ inherit (cfg) rc_message rc_federation;
+
+ # Media Store
+ inherit (cfg) max_upload_size max_image_pixels dynamic_thumbnails;
+ media_store_path = "${cfg.dataDir}/media";
+ } // optionalAttrs cfg.url_preview_enabled {
+ url_preview_enabled = true;
+ url_preview_ip_range_blacklist = cfg.url_preview_ip_range_blacklist;
+ url_preview_ip_range_whitelist = cfg.url_preview_ip_range_whitelist;
+ url_preview_url_blacklist = cfg.url_preview_url_blacklist;
+ } // {
+
+ # Captcha
+ inherit (cfg) recaptcha_private_key recaptcha_public_key enable_registration_captcha;
+ recaptcha_siteverify_api = "https://www.google.com/recaptcha/api/siteverify";
+
+ # TURN
+ inherit (cfg) turn_uris turn_shared_secret turn_user_lifetime;
+
+ # Registration
+ inherit (cfg) enable_registration registration_shared_secret bcrypt_rounds allow_guest_access;
+
+ account_threepid_delegates = filterAttrs (_: v: v != null) {
+ inherit (cfg.account_threepid_delegates) email msisdn;
+ };
+
+ # Account Validity
+
+ # Metrics
+ inherit (cfg) enable_metrics report_stats;
+
+ # API Configuration
+ inherit (cfg) macaroon_secret_key app_service_config_files room_prejoin_state;
+
+ # Signing Keys
+ inherit (cfg) key_refresh_interval;
+ signing_key_path = "${cfg.dataDir}/homeserver.signing.key";
+
+ perspectives = {
+ servers = mapAttrs
+ (name: value: {
+ verify_keys = mapAttrs (name: value: { key = value; }) value;
+ })
+ cfg.servers;
+ };
+
+ # Single sign-on integration
+ # Push
+ # Rooms
+ # Opentracing
+ # Workers
+ };
+
+ configFile = pkgs.writeText "homeserver.yaml" (
+ generators.toYAML { } (filterAttrs (_: v: v != null)
+ (fold recursiveUpdate { } [ yamlConfig cfg.settings ])));
+
+ extraConfigFile = pkgs.writeText "extra-homeserver.yaml" cfg.extraConfig;
hasLocalPostgresDB = let args = cfg.database_args; in
usePostgresql && (!(args ? host) || (elem args.host [ "localhost" "127.0.0.1" "::1" ]));
-in {
+
+ configFiles = [ configFile extraConfigFile ] ++ cfg.extraConfigFiles;
+ configFilesArgString = concatMapStringsSep " " (x: "--config-path ${x}") configFiles;
+in
+{
options = {
services.matrix-synapse = {
enable = mkEnableOption "matrix.org synapse";
@@ -200,14 +190,6 @@ in {
speaking TLS directly.
'';
};
- tls_dh_params_path = mkOption {
- type = types.nullOr types.str;
- default = null;
- example = "${cfg.dataDir}/homeserver.tls.dh";
- description = ''
- PEM dh parameters for ephemeral keys
- '';
- };
server_name = mkOption {
type = types.str;
example = "example.com";
@@ -310,53 +292,72 @@ in {
'';
};
verbose = mkOption {
- type = types.str;
- default = "0";
+ type = with types; coercedTo string toInt int;
+ default = 0;
description = "Logging verbosity level.";
};
- rc_messages_per_second = mkOption {
- type = types.str;
- default = "0.2";
- description = "Number of messages a client can send per second";
- };
- rc_message_burst_count = mkOption {
- type = types.str;
- default = "10.0";
- description = "Number of message a client can send before being throttled";
- };
- federation_rc_window_size = mkOption {
- type = types.str;
- default = "1000";
- description = "The federation window size in milliseconds";
- };
- federation_rc_sleep_limit = mkOption {
- type = types.str;
- default = "10";
- description = ''
- The number of federation requests from a single server in a window
- before the server will delay processing the request.
- '';
- };
- federation_rc_sleep_delay = mkOption {
- type = types.str;
- default = "500";
- description = ''
- The duration in milliseconds to delay processing events from
- remote servers by if they go over the sleep limit.
- '';
- };
- federation_rc_reject_limit = mkOption {
- type = types.str;
- default = "50";
+ rc_message = mkOption {
+ type = types.submodule {
+ options.per_second = mkOption {
+ type = types.float;
+ default = 0.2;
+ description = ''
+ The number of requests a client can send per second.
+ '';
+ };
+ options.burst_count = mkOption {
+ type = types.float;
+ default = 10.0;
+ description = ''
+ The number of requests a client can send before being throttled.
+ '';
+ };
+ };
description = ''
- The maximum number of concurrent federation requests allowed
- from a single server
+ Rate limits for sending based on the account the client is using.
'';
};
- federation_rc_concurrent = mkOption {
- type = types.str;
- default = "3";
- description = "The number of federation requests to concurrently process from a single server";
+ rc_federation = mkOption {
+ type = types.submodule {
+ options.window_size = mkOption {
+ type = types.int;
+ default = 1000;
+ description = "The federation window size in milliseconds";
+ };
+ options.sleep_limit = mkOption {
+ type = types.int;
+ default = 10;
+ description = ''
+ The number of federation requests from a single server in a window
+ before the server will delay processing the request.
+ '';
+ };
+ options.sleep_delay = mkOption {
+ type = types.int;
+ default = 500;
+ description = ''
+ The duration in milliseconds to delay processing events from
+ remote servers by if they go over the sleep limit.
+ '';
+ };
+ options.reject_limit = mkOption {
+ type = types.int;
+ default = 50;
+ description = ''
+ The maximum number of concurrent federation requests allowed
+ from a single server.
+ '';
+ };
+ options.concurrent = mkOption {
+ type = types.int;
+ default = 3;
+ description = ''
+ The number of federation requests to concurrently process from a
+ single server.
+ '';
+ };
+ };
+ description = "Ratelimiting settings for incoming federation.";
};
database_type = mkOption {
type = types.enum [ "sqlite3" "psycopg2" ];
@@ -540,17 +541,9 @@ in {
from a precalculated list.
'';
};
- user_creation_max_duration = mkOption {
- type = types.str;
- default = "1209600000";
- description = ''
- Sets the expiry for the short term user creation in
- milliseconds. The default value is two weeks.
- '';
- };
bcrypt_rounds = mkOption {
- type = types.str;
- default = "12";
+ type = with types; coercedTo string toInt int;
+ default = 12;
description = ''
Set the number of bcrypt rounds used to generate password hash.
Larger numbers increase the work factor needed to generate the hash.
@@ -610,13 +603,6 @@ in {
Secret key for authentication tokens
'';
};
- expire_access_token = mkOption {
- type = types.bool;
- default = false;
- description = ''
- Whether to enable access token expiration.
- '';
- };
key_refresh_interval = mkOption {
type = types.str;
default = "1d";
@@ -645,7 +631,8 @@ in {
type = types.lines;
default = "";
description = ''
- Extra config options for matrix-synapse.
+ Extra config options for matrix-synapse. The options are included via
+ an extra configuration file with the `--config-path` argument.
'';
};
extraConfigFiles = mkOption {
@@ -660,6 +647,20 @@ in {
NixOPS is in use.
'';
};
+ settings = mkOption {
+ type = (pkgs.formats.yaml { }).type;
+ default = { };
+ description = ''
+ Extra Synapse settings. Refer to
+
+ for details on supported values.
+ '';
+ example = literalExample ''
+ {
+ metrics_flags.known_servers = true;
+ }
+ '';
+ };
logConfig = mkOption {
type = types.lines;
default = readFile ./matrix-synapse-log_config.yaml;
@@ -716,7 +717,7 @@ in {
wantedBy = [ "multi-user.target" ];
preStart = ''
${cfg.package}/bin/homeserver \
- --config-path ${configFile} \
+ ${configFilesArgString} \
--keys-directory ${cfg.dataDir} \
--generate-keys
'';
@@ -732,7 +733,7 @@ in {
'')) ];
ExecStart = ''
${cfg.package}/bin/homeserver \
- ${ concatMapStringsSep "\n " (x: "--config-path ${x} \\") ([ configFile ] ++ cfg.extraConfigFiles) }
+ ${configFilesArgString} \
--keys-directory ${cfg.dataDir}
'';
ExecReload = "${pkgs.util-linux}/bin/kill -HUP $MAINPID";
@@ -742,17 +743,53 @@ in {
};
};
- imports = [
- (mkRemovedOptionModule [ "services" "matrix-synapse" "trusted_third_party_id_servers" ] ''
+ imports = let
+ optionPath = path: [ "services" "matrix-synapse" ] ++ (toList path);
+ stringToType = typeConverter: oldPath: newPath:
+ (mkChangedOptionModule oldPath newPath (config:
+ let value = getAttrFromPath oldPath config;
+ in if builtins.isString value then typeConverter value else value));
+ stringToFloat = stringToType toFloat;
+ stringToInt = stringToType toInt;
+ in [
+ # Rate limiting options are now in submodules. See #120260
+ (stringToFloat (optionPath "rc_messages_per_second")
+ (optionPath [ "rc_message" "per_second" ]))
+ (stringToFloat (optionPath "rc_message_burst_count")
+ (optionPath [ "rc_message" "burst_count" ]))
+ (stringToInt (optionPath "federation_rc_window_size")
+ (optionPath [ "rc_federation" "window_size" ]))
+ (stringToInt (optionPath "federation_rc_sleep_limit")
+ (optionPath [ "rc_federation" "sleep_limit" ]))
+ (stringToInt (optionPath "federation_rc_sleep_delay")
+ (optionPath [ "rc_federation" "sleep_delay" ]))
+ (stringToInt (optionPath "federation_rc_reject_limit")
+ (optionPath [ "rc_federation" "reject_limit" ]))
+ (stringToInt (optionPath "federation_rc_concurrent")
+ (optionPath [ "rc_federation" "concurrent" ]))
+
+ # Removed Options
+ (mkRemovedOptionModule (optionPath "user_creation_max_duration") ''
+ The `user_creation_max_duration` option has been removed.
+ '')
+ (mkRemovedOptionModule (optionPath "tls_dh_params_path") ''
+ The `tls_dh_params_path` option was been removed in `matrix-synapse` v0.99.0
+ since configuring and generating dh_params is no longer required.
+ '')
+ (mkRemovedOptionModule (optionPath "expire_access_token") ''
+ The `expire_access_token` option was been removed in `matrix-synapse` v1.3.0
+ since it was non-functional.
+ '')
+ (mkRemovedOptionModule (optionPath "trusted_third_party_id_servers") ''
The `trusted_third_party_id_servers` option as been removed in `matrix-synapse` v1.4.0
as the behavior is now obsolete.
'')
- (mkRemovedOptionModule [ "services" "matrix-synapse" "create_local_database" ] ''
+ (mkRemovedOptionModule (optionPath "create_local_database") ''
Database configuration must be done manually. An exemplary setup is demonstrated in
'')
- (mkRemovedOptionModule [ "services" "matrix-synapse" "web_client" ] "")
- (mkRemovedOptionModule [ "services" "matrix-synapse" "room_invite_state_types" ] ''
+ (mkRemovedOptionModule (optionPath "web_client") "")
+ (mkRemovedOptionModule (optionPath "room_invite_state_types") ''
You may add additional event types via
`services.matrix-synapse.room_prejoin_state.additional_event_types` and
disable the default events via