1 { config, pkgs, lib, ... }:
4 settingsFormat = pkgs.formats.yaml {};
7 cfg = config.services.mautrix-meta;
10 fullDataDir = cfg: "/var/lib/${cfg.dataDir}";
12 settingsFile = cfg: "${fullDataDir cfg}/config.yaml";
13 settingsFileUnsubstituted = cfg: settingsFormat.generate "mautrix-meta-config.yaml" cfg.settings;
15 metaName = name: "mautrix-meta-${name}";
17 enabledInstances = lib.filterAttrs (name: config: config.enable) config.services.mautrix-meta.instances;
18 registerToSynapseInstances = lib.filterAttrs (name: config: config.enable && config.registerToSynapse) config.services.mautrix-meta.instances;
21 services.mautrix-meta = {
23 package = lib.mkPackageOption pkgs "mautrix-meta" { };
25 instances = lib.mkOption {
26 type = lib.types.attrsOf (lib.types.submodule ({ config, name, ... }: {
30 enable = lib.mkEnableOption "Mautrix-Meta, a Matrix <-> Facebook and Matrix <-> Instagram hybrid puppeting/relaybot bridge";
32 dataDir = lib.mkOption {
34 default = metaName name;
36 Path to the directory with database, registration, and other data for the bridge service.
37 This path is relative to `/var/lib`, it cannot start with `../` (it cannot be outside of `/var/lib`).
41 registrationFile = lib.mkOption {
42 type = lib.types.path;
45 Path to the yaml registration file of the appservice.
49 registerToSynapse = lib.mkOption {
50 type = lib.types.bool;
53 Whether to add registration file to `services.matrix-synapse.settings.app_service_config_files` and
54 make Synapse wait for registration service.
58 settings = lib.mkOption rec {
59 apply = lib.recursiveUpdate default;
60 inherit (settingsFormat) type;
63 software = "standard";
76 hostname = "localhost";
78 address = "http://${config.settings.appservice.hostname}:${toString config.settings.appservice.port}";
86 type = "sqlite3-fk-wal";
87 uri = "file:${fullDataDir config}/mautrix-meta.db?_txlock=immediate";
90 # Enable encryption by default to make the bridge more secure
96 # Recommended options from mautrix documentation
97 # for additional security.
99 dont_store_outbound = true;
100 ratchet_on_decrypt = true;
101 delete_fully_used_on_decrypt = true;
102 delete_prev_on_new_session = true;
103 delete_on_device_delete = true;
104 periodically_delete_expired = true;
105 delete_outdated_inbound = true;
108 # TODO: This effectively disables encryption. But this is the value provided when a <0.4 config is migrated. Changing it will corrupt the database.
109 # https://github.com/mautrix/meta/blob/f5440b05aac125b4c95b1af85635a717cbc6dd0e/cmd/mautrix-meta/legacymigrate.go#L24
110 # If you wish to encrypt the local database you should set this to an environment variable substitution and reset the bridge or somehow migrate the DB.
111 pickle_key = "mautrix.bridge.e2ee";
113 verification_levels = {
114 receive = "cross-signed-tofu";
115 send = "cross-signed-tofu";
116 share = "cross-signed-tofu";
122 writers = lib.singleton {
124 format = "pretty-colored";
136 software = "standard";
137 address = "https://''${config.settings.homeserver.domain}";
142 type = "sqlite3-fk-wal";
143 uri = "file:''${fullDataDir config}/mautrix-meta.db?_txlock=immediate";
146 hostname = "localhost";
148 address = "http://''${config.settings.appservice.hostname}:''${toString config.settings.appservice.port}";
152 # Require encryption by default to make the bridge more secure
158 # Recommended options from mautrix documentation
159 # for optimal security.
161 dont_store_outbound = true;
162 ratchet_on_decrypt = true;
163 delete_fully_used_on_decrypt = true;
164 delete_prev_on_new_session = true;
165 delete_on_device_delete = true;
166 periodically_delete_expired = true;
167 delete_outdated_inbound = true;
170 verification_levels = {
171 receive = "cross-signed-tofu";
172 send = "cross-signed-tofu";
173 share = "cross-signed-tofu";
180 writers = lib.singleton {
182 format = "pretty-colored";
189 {file}`config.yaml` configuration as a Nix attribute set.
190 Configuration options should match those described in
191 [example-config.yaml](https://github.com/mautrix/meta/blob/main/example-config.yaml).
193 Secret tokens should be specified using {option}`environmentFile`
198 environmentFile = lib.mkOption {
199 type = lib.types.nullOr lib.types.path;
202 File containing environment variables to substitute when copying the configuration
203 out of Nix store to the `services.mautrix-meta.dataDir`.
205 Can be used for storing the secrets without making them available in the Nix store.
207 For example, you can set `services.mautrix-meta.settings.appservice.as_token = "$MAUTRIX_META_APPSERVICE_AS_TOKEN"`
208 and then specify `MAUTRIX_META_APPSERVICE_AS_TOKEN="{token}"` in the environment file.
209 This value will get substituted into the configuration file as as token.
213 serviceDependencies = lib.mkOption {
214 type = lib.types.listOf lib.types.str;
216 [ config.registrationServiceUnit ] ++
217 (lib.lists.optional upperConfig.services.matrix-synapse.enable upperConfig.services.matrix-synapse.serviceUnit) ++
218 (lib.lists.optional upperConfig.services.matrix-conduit.enable "matrix-conduit.service") ++
219 (lib.lists.optional upperConfig.services.dendrite.enable "dendrite.service");
222 [ config.registrationServiceUnit ] ++
223 (lib.lists.optional upperConfig.services.matrix-synapse.enable upperConfig.services.matrix-synapse.serviceUnit) ++
224 (lib.lists.optional upperConfig.services.matrix-conduit.enable "matrix-conduit.service") ++
225 (lib.lists.optional upperConfig.services.dendrite.enable "dendrite.service");
228 List of Systemd services to require and wait for when starting the application service.
232 serviceUnit = lib.mkOption {
233 type = lib.types.str;
236 The systemd unit (a service or a target) for other services to depend on if they
237 need to be started after matrix-synapse.
239 This option is useful as the actual parent unit for all matrix-synapse processes
240 changes when configuring workers.
244 registrationServiceUnit = lib.mkOption {
245 type = lib.types.str;
248 The registration service that generates the registration file.
250 Systemd unit (a service or a target) for other services to depend on if they
251 need to be started after mautrix-meta registration service.
253 This option is useful as the actual parent unit for all matrix-synapse processes
254 changes when configuring workers.
260 serviceUnit = (metaName name) + ".service";
261 registrationServiceUnit = (metaName name) + "-registration.service";
262 registrationFile = (fullDataDir config) + "/meta-registration.yaml";
267 Configuration of multiple `mautrix-meta` instances.
268 `services.mautrix-meta.instances.facebook` and `services.mautrix-meta.instances.instagram`
269 come preconfigured with network.mode, appservice.id, bot username, display name and avatar.
277 homeserver.domain = "example.com";
284 homeserver.domain = "example.com";
291 network.mode = "messenger";
292 homeserver.domain = "example.com";
296 username = "messengerbot";
297 displayname = "Messenger bridge bot";
298 avatar = "mxc://maunium.net/ygtkteZsXnGJLJHRchUwYWak";
309 config = lib.mkMerge [
310 (lib.mkIf (enabledInstances != {}) {
311 assertions = lib.mkMerge (lib.attrValues (lib.mapAttrs (name: cfg: [
313 assertion = cfg.settings.homeserver.domain != "" && cfg.settings.homeserver.address != "";
315 The options with information about the homeserver:
316 `services.mautrix-meta.instances.${name}.settings.homeserver.domain` and
317 `services.mautrix-meta.instances.${name}.settings.homeserver.address` have to be set.
321 assertion = builtins.elem cfg.settings.network.mode [ "facebook" "facebook-tor" "messenger" "instagram" ];
323 The option `services.mautrix-meta.instances.${name}.settings.network.mode` has to be set
324 to one of: facebook, facebook-tor, messenger, instagram.
325 This configures the mode of the bridge.
329 assertion = cfg.settings.bridge.permissions != {};
331 The option `services.mautrix-meta.instances.${name}.settings.bridge.permissions` has to be set.
335 assertion = cfg.settings.appservice.id != "";
337 The option `services.mautrix-meta.instances.${name}.settings.appservice.id` has to be set.
341 assertion = cfg.settings.appservice.bot.username != "";
343 The option `services.mautrix-meta.instances.${name}.settings.appservice.bot.username` has to be set.
347 assertion = !(cfg.settings ? bridge.disable_xma);
349 The option `bridge.disable_xma` has been moved to `network.disable_xma_always`. Please [migrate your configuration](https://github.com/mautrix/meta/releases/tag/v0.4.0). You may wish to use [the auto-migration code](https://github.com/mautrix/meta/blob/f5440b05aac125b4c95b1af85635a717cbc6dd0e/cmd/mautrix-meta/legacymigrate.go#L23) for reference.
353 assertion = !(cfg.settings ? bridge.displayname_template);
355 The option `bridge.displayname_template` has been moved to `network.displayname_template`. Please [migrate your configuration](https://github.com/mautrix/meta/releases/tag/v0.4.0). You may wish to use [the auto-migration code](https://github.com/mautrix/meta/blob/f5440b05aac125b4c95b1af85635a717cbc6dd0e/cmd/mautrix-meta/legacymigrate.go#L23) for reference.
359 assertion = !(cfg.settings ? meta);
361 The options in `meta` have been moved to `network`. Please [migrate your configuration](https://github.com/mautrix/meta/releases/tag/v0.4.0). You may wish to use [the auto-migration code](https://github.com/mautrix/meta/blob/f5440b05aac125b4c95b1af85635a717cbc6dd0e/cmd/mautrix-meta/legacymigrate.go#L23) for reference.
364 ]) enabledInstances));
366 users.users = lib.mapAttrs' (name: cfg: lib.nameValuePair "mautrix-meta-${name}" {
368 group = "mautrix-meta";
369 extraGroups = [ "mautrix-meta-registration" ];
370 description = "Mautrix-Meta-${name} bridge user";
373 users.groups.mautrix-meta = {};
374 users.groups.mautrix-meta-registration = {
375 members = lib.lists.optional config.services.matrix-synapse.enable "matrix-synapse";
378 services.matrix-synapse = lib.mkIf (config.services.matrix-synapse.enable) (let
379 registrationFiles = lib.attrValues
380 (lib.mapAttrs (name: cfg: cfg.registrationFile) registerToSynapseInstances);
382 settings.app_service_config_files = registrationFiles;
385 systemd.services = lib.mkMerge [
387 matrix-synapse = lib.mkIf (config.services.matrix-synapse.enable) (let
388 registrationServices = lib.attrValues
389 (lib.mapAttrs (name: cfg: cfg.registrationServiceUnit) registerToSynapseInstances);
391 wants = registrationServices;
392 after = registrationServices;
396 (lib.mapAttrs' (name: cfg: lib.nameValuePair "${metaName name}-registration" {
397 description = "Mautrix-Meta registration generation service - ${metaName name}";
406 # substitute the settings file by environment variables
407 # in this case read from EnvironmentFile
408 rm -f '${settingsFile cfg}'
412 -o '${settingsFile cfg}' \
413 -i '${settingsFileUnsubstituted cfg}'
415 config_has_tokens=$(yq '.appservice | has("as_token") and has("hs_token")' '${settingsFile cfg}')
416 registration_already_exists=$([[ -f '${cfg.registrationFile}' ]] && echo "true" || echo "false")
418 echo "There are tokens in the config: $config_has_tokens"
419 echo "Registration already existed: $registration_already_exists"
421 # tokens not configured from config/environment file, and registration file
422 # is already generated, override tokens in config to make sure they are not lost
423 if [[ $config_has_tokens == "false" && $registration_already_exists == "true" ]]; then
424 echo "Copying as_token, hs_token from registration into configuration"
425 yq -sY '.[0].appservice.as_token = .[1].as_token
426 | .[0].appservice.hs_token = .[1].hs_token
427 | .[0]' '${settingsFile cfg}' '${cfg.registrationFile}' \
428 > '${settingsFile cfg}.tmp'
429 mv '${settingsFile cfg}.tmp' '${settingsFile cfg}'
432 # make sure --generate-registration does not affect config.yaml
433 cp '${settingsFile cfg}' '${settingsFile cfg}.tmp'
435 echo "Generating registration file"
437 --generate-registration \
438 --config='${settingsFile cfg}.tmp' \
439 --registration='${cfg.registrationFile}'
441 rm '${settingsFile cfg}.tmp'
443 # no tokens configured, and new were just generated by generate registration for first time
444 if [[ $config_has_tokens == "false" && $registration_already_exists == "false" ]]; then
445 echo "Copying newly generated as_token, hs_token from registration into configuration"
446 yq -sY '.[0].appservice.as_token = .[1].as_token
447 | .[0].appservice.hs_token = .[1].hs_token
448 | .[0]' '${settingsFile cfg}' '${cfg.registrationFile}' \
449 > '${settingsFile cfg}.tmp'
450 mv '${settingsFile cfg}.tmp' '${settingsFile cfg}'
453 # Make sure correct tokens are in the registration file
454 if [[ $config_has_tokens == "true" || $registration_already_exists == "true" ]]; then
455 echo "Copying as_token, hs_token from configuration to the registration file"
456 yq -sY '.[1].as_token = .[0].appservice.as_token
457 | .[1].hs_token = .[0].appservice.hs_token
458 | .[1]' '${settingsFile cfg}' '${cfg.registrationFile}' \
459 > '${cfg.registrationFile}.tmp'
460 mv '${cfg.registrationFile}.tmp' '${cfg.registrationFile}'
465 chown :mautrix-meta-registration '${cfg.registrationFile}'
466 chmod 640 '${cfg.registrationFile}'
473 User = "mautrix-meta-${name}";
474 Group = "mautrix-meta";
476 SystemCallFilter = [ "@system-service" ];
478 ProtectSystem = "strict";
481 ReadWritePaths = fullDataDir cfg;
482 StateDirectory = cfg.dataDir;
483 EnvironmentFile = cfg.environmentFile;
486 restartTriggers = [ (settingsFileUnsubstituted cfg) ];
489 (lib.mapAttrs' (name: cfg: lib.nameValuePair "${metaName name}" {
490 description = "Mautrix-Meta bridge - ${metaName name}";
491 wantedBy = [ "multi-user.target" ];
492 wants = [ "network-online.target" ] ++ cfg.serviceDependencies;
493 after = [ "network-online.target" ] ++ cfg.serviceDependencies;
498 User = "mautrix-meta-${name}";
499 Group = "mautrix-meta";
502 LockPersonality = true;
503 MemoryDenyWriteExecute = true;
504 NoNewPrivileges = true;
505 PrivateDevices = true;
508 ProtectControlGroups = true;
510 ProtectHostname = true;
511 ProtectKernelLogs = true;
512 ProtectKernelModules = true;
513 ProtectKernelTunables = true;
514 ProtectSystem = "strict";
515 Restart = "on-failure";
517 RestrictRealtime = true;
518 RestrictSUIDSGID = true;
519 SystemCallArchitectures = "native";
520 SystemCallErrorNumber = "EPERM";
521 SystemCallFilter = ["@system-service"];
524 WorkingDirectory = fullDataDir cfg;
525 ReadWritePaths = fullDataDir cfg;
526 StateDirectory = cfg.dataDir;
527 EnvironmentFile = cfg.environmentFile;
529 ExecStart = lib.escapeShellArgs [
530 (lib.getExe upperCfg.package)
531 "--config=${settingsFile cfg}"
534 restartTriggers = [ (settingsFileUnsubstituted cfg) ];
539 services.mautrix-meta.instances = let
540 inherit (lib.modules) mkDefault;
544 network.mode = mkDefault "instagram";
547 id = mkDefault "instagram";
548 port = mkDefault 29320;
550 username = mkDefault "instagrambot";
551 displayname = mkDefault "Instagram bridge bot";
552 avatar = mkDefault "mxc://maunium.net/JxjlbZUlCPULEeHZSwleUXQv";
554 username_template = mkDefault "instagram_{{.}}";
560 network.mode = mkDefault "facebook";
563 id = mkDefault "facebook";
564 port = mkDefault 29321;
566 username = mkDefault "facebookbot";
567 displayname = mkDefault "Facebook bridge bot";
568 avatar = mkDefault "mxc://maunium.net/ygtkteZsXnGJLJHRchUwYWak";
570 username_template = mkDefault "facebook_{{.}}";
578 meta.maintainers = with lib.maintainers; [ ];