base16-schemes: unstable-2024-06-21 -> unstable-2024-11-12
[NixPkgs.git] / nixos / modules / services / matrix / mautrix-signal.nix
blobb4a838612633b5d4dfac5a8a82c1546caecd0ebe
2   lib,
3   config,
4   pkgs,
5   ...
6 }:
7 let
8   cfg = config.services.mautrix-signal;
9   dataDir = "/var/lib/mautrix-signal";
10   registrationFile = "${dataDir}/signal-registration.yaml";
11   settingsFile = "${dataDir}/config.yaml";
12   settingsFileUnsubstituted = settingsFormat.generate "mautrix-signal-config-unsubstituted.json" cfg.settings;
13   settingsFormat = pkgs.formats.json { };
14   appservicePort = 29328;
16   # to be used with a list of lib.mkIf values
17   optOneOf = lib.lists.findFirst (value: value.condition) (lib.mkIf false null);
18   mkDefaults = lib.mapAttrsRecursive (n: v: lib.mkDefault v);
19   defaultConfig = {
20     network = {
21       displayname_template = "{{or .ProfileName .PhoneNumber \"Unknown user\"}}";
22     };
23     bridge = {
24       command_prefix = "!signal";
25       relay.enabled = true;
26       permissions."*" = "relay";
27     };
28     database = {
29       type = "sqlite3";
30       uri = "file:${dataDir}/mautrix-signal.db?_txlock=immediate";
31     };
32     homeserver.address = "http://localhost:8448";
33     appservice = {
34       hostname = "[::]";
35       port = appservicePort;
36       id = "signal";
37       bot = {
38         username = "signalbot";
39         displayname = "Signal Bridge Bot";
40       };
41       as_token = "";
42       hs_token = "";
43       username_template = "signal_{{.}}";
44     };
45     double_puppet = {
46       servers = { };
47       secrets = { };
48     };
49     # By default, the following keys/secrets are set to `generate`. This would break when the service
50     # is restarted, since the previously generated configuration will be overwritten everytime.
51     # If encryption is enabled, it's recommended to set those keys via `environmentFile`.
52     encryption.pickle_key = "";
53     provisioning.shared_secret = "";
54     public_media.signing_key = "";
55     direct_media.server_key = "";
56     logging = {
57       min_level = "info";
58       writers = lib.singleton {
59         type = "stdout";
60         format = "pretty-colored";
61         time_format = " ";
62       };
63     };
64   };
68   options.services.mautrix-signal = {
69     enable = lib.mkEnableOption "mautrix-signal, a Matrix-Signal puppeting bridge";
71     settings = lib.mkOption {
72       apply = lib.recursiveUpdate defaultConfig;
73       type = settingsFormat.type;
74       default = defaultConfig;
75       description = ''
76         {file}`config.yaml` configuration as a Nix attribute set.
77         Configuration options should match those described in the example configuration.
78         Get an example configuration by executing `mautrix-signal -c example.yaml --generate-example-config`
79         Secret tokens should be specified using {option}`environmentFile`
80         instead of this world-readable attribute set.
81       '';
82       example = {
83         bridge = {
84           private_chat_portal_meta = true;
85           mute_only_on_create = false;
86           permissions = {
87             "example.com" = "user";
88           };
89         };
90         database = {
91           type = "postgres";
92           uri = "postgresql:///mautrix_signal?host=/run/postgresql";
93         };
94         homeserver = {
95           address = "http://[::1]:8008";
96           domain = "my-domain.tld";
97         };
98         appservice = {
99           id = "signal";
100           ephemeral_events = false;
101         };
102         matrix.message_status_events = true;
103         provisioning = {
104           shared_secret = "disable";
105         };
106         backfill.enabled = true;
107         encryption = {
108           allow = true;
109           default = true;
110           require = true;
111           pickle_key = "$ENCRYPTION_PICKLE_KEY";
112         };
113       };
114     };
116     environmentFile = lib.mkOption {
117       type = lib.types.nullOr lib.types.path;
118       default = null;
119       description = ''
120         File containing environment variables to be passed to the mautrix-signal service.
121         If an environment variable `MAUTRIX_SIGNAL_BRIDGE_LOGIN_SHARED_SECRET` is set,
122         then its value will be used in the configuration file for the option
123         `double_puppet.secrets` without leaking it to the store, using the configured
124         `homeserver.domain` as key.
125       '';
126     };
128     serviceDependencies = lib.mkOption {
129       type = with lib.types; listOf str;
130       default =
131         (lib.optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnit)
132         ++ (lib.optional config.services.matrix-conduit.enable "conduit.service");
133       defaultText = lib.literalExpression ''
134         (optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnit)
135         ++ (optional config.services.matrix-conduit.enable "conduit.service")
136       '';
137       description = ''
138         List of systemd units to require and wait for when starting the application service.
139       '';
140     };
142     registerToSynapse = lib.mkOption {
143       type = lib.types.bool;
144       default = config.services.matrix-synapse.enable;
145       defaultText = lib.literalExpression ''
146         config.services.matrix-synapse.enable
147       '';
148       description = ''
149         Whether to add the bridge's app service registration file to
150         `services.matrix-synapse.settings.app_service_config_files`.
151       '';
152     };
153   };
155   config = lib.mkIf cfg.enable {
157     users.users.mautrix-signal = {
158       isSystemUser = true;
159       group = "mautrix-signal";
160       home = dataDir;
161       description = "Mautrix-Signal bridge user";
162     };
164     users.groups.mautrix-signal = { };
166     services.matrix-synapse = lib.mkIf cfg.registerToSynapse {
167       settings.app_service_config_files = [ registrationFile ];
168     };
169     systemd.services.matrix-synapse = lib.mkIf cfg.registerToSynapse {
170       serviceConfig.SupplementaryGroups = [ "mautrix-signal" ];
171     };
173     # Note: this is defined here to avoid the docs depending on `config`
174     services.mautrix-signal.settings.homeserver = optOneOf (
175       with config.services;
176       [
177         (lib.mkIf matrix-synapse.enable (mkDefaults {
178           domain = matrix-synapse.settings.server_name;
179         }))
180         (lib.mkIf matrix-conduit.enable (mkDefaults {
181           domain = matrix-conduit.settings.global.server_name;
182           address = "http://localhost:${toString matrix-conduit.settings.global.port}";
183         }))
184       ]
185     );
187     systemd.services.mautrix-signal = {
188       description = "mautrix-signal, a Matrix-Signal puppeting bridge.";
190       wantedBy = [ "multi-user.target" ];
191       wants = [ "network-online.target" ] ++ cfg.serviceDependencies;
192       after = [ "network-online.target" ] ++ cfg.serviceDependencies;
193       # ffmpeg is required for conversion of voice messages
194       path = [ pkgs.ffmpeg-headless ];
196       preStart = ''
197         # substitute the settings file by environment variables
198         # in this case read from EnvironmentFile
199         test -f '${settingsFile}' && rm -f '${settingsFile}'
200         old_umask=$(umask)
201         umask 0177
202         ${pkgs.envsubst}/bin/envsubst \
203           -o '${settingsFile}' \
204           -i '${settingsFileUnsubstituted}'
205         umask $old_umask
207         # generate the appservice's registration file if absent
208         if [ ! -f '${registrationFile}' ]; then
209           ${pkgs.mautrix-signal}/bin/mautrix-signal \
210             --generate-registration \
211             --config='${settingsFile}' \
212             --registration='${registrationFile}'
213         fi
214         chmod 640 ${registrationFile}
216         umask 0177
217         # 1. Overwrite registration tokens in config
218         # 2. If environment variable MAUTRIX_SIGNAL_BRIDGE_LOGIN_SHARED_SECRET
219         #    is set, set it as the login shared secret value for the configured
220         #    homeserver domain.
221         ${pkgs.yq}/bin/yq -s '.[0].appservice.as_token = .[1].as_token
222           | .[0].appservice.hs_token = .[1].hs_token
223           | .[0]
224           | if env.MAUTRIX_SIGNAL_BRIDGE_LOGIN_SHARED_SECRET then .double_puppet.secrets.[.homeserver.domain] = env.MAUTRIX_SIGNAL_BRIDGE_LOGIN_SHARED_SECRET else . end' \
225           '${settingsFile}' '${registrationFile}' > '${settingsFile}.tmp'
226         mv '${settingsFile}.tmp' '${settingsFile}'
227         umask $old_umask
228       '';
230       serviceConfig = {
231         User = "mautrix-signal";
232         Group = "mautrix-signal";
233         EnvironmentFile = cfg.environmentFile;
234         StateDirectory = baseNameOf dataDir;
235         WorkingDirectory = dataDir;
236         ExecStart = ''
237           ${pkgs.mautrix-signal}/bin/mautrix-signal \
238           --config='${settingsFile}' \
239           --registration='${registrationFile}'
240         '';
241         LockPersonality = true;
242         MemoryDenyWriteExecute = true;
243         NoNewPrivileges = true;
244         PrivateDevices = true;
245         PrivateTmp = true;
246         PrivateUsers = true;
247         ProtectClock = true;
248         ProtectControlGroups = true;
249         ProtectHome = true;
250         ProtectHostname = true;
251         ProtectKernelLogs = true;
252         ProtectKernelModules = true;
253         ProtectKernelTunables = true;
254         ProtectSystem = "strict";
255         Restart = "on-failure";
256         RestartSec = "30s";
257         RestrictRealtime = true;
258         RestrictSUIDSGID = true;
259         SystemCallArchitectures = "native";
260         SystemCallErrorNumber = "EPERM";
261         SystemCallFilter = [ "@system-service" ];
262         Type = "simple";
263         UMask = 27;
264       };
265       restartTriggers = [ settingsFileUnsubstituted ];
266     };
267   };
268   meta = {
269     buildDocsInSandbox = false;
270     doc = ./mautrix-signal.md;
271     maintainers = with lib.maintainers; [
272       niklaskorz
273       frederictobiasc
274     ];
275   };