at 25.11-pre 9.4 kB view raw
1{ 2 lib, 3 config, 4 pkgs, 5 ... 6}: 7let 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; 15 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 }; 65 66in 67{ 68 options.services.mautrix-signal = { 69 enable = lib.mkEnableOption "mautrix-signal, a Matrix-Signal puppeting bridge"; 70 71 package = lib.mkPackageOption pkgs "mautrix-signal" { }; 72 73 settings = lib.mkOption { 74 apply = lib.recursiveUpdate defaultConfig; 75 type = settingsFormat.type; 76 default = defaultConfig; 77 description = '' 78 {file}`config.yaml` configuration as a Nix attribute set. 79 Configuration options should match those described in the example configuration. 80 Get an example configuration by executing `mautrix-signal -c example.yaml --generate-example-config` 81 Secret tokens should be specified using {option}`environmentFile` 82 instead of this world-readable attribute set. 83 ''; 84 example = { 85 bridge = { 86 private_chat_portal_meta = true; 87 mute_only_on_create = false; 88 permissions = { 89 "example.com" = "user"; 90 }; 91 }; 92 database = { 93 type = "postgres"; 94 uri = "postgresql:///mautrix_signal?host=/run/postgresql"; 95 }; 96 homeserver = { 97 address = "http://[::1]:8008"; 98 domain = "my-domain.tld"; 99 }; 100 appservice = { 101 id = "signal"; 102 ephemeral_events = false; 103 }; 104 matrix.message_status_events = true; 105 provisioning = { 106 shared_secret = "disable"; 107 }; 108 backfill.enabled = true; 109 encryption = { 110 allow = true; 111 default = true; 112 require = true; 113 pickle_key = "$ENCRYPTION_PICKLE_KEY"; 114 }; 115 }; 116 }; 117 118 environmentFile = lib.mkOption { 119 type = lib.types.nullOr lib.types.path; 120 default = null; 121 description = '' 122 File containing environment variables to be passed to the mautrix-signal service. 123 If an environment variable `MAUTRIX_SIGNAL_BRIDGE_LOGIN_SHARED_SECRET` is set, 124 then its value will be used in the configuration file for the option 125 `double_puppet.secrets` without leaking it to the store, using the configured 126 `homeserver.domain` as key. 127 ''; 128 }; 129 130 serviceDependencies = lib.mkOption { 131 type = with lib.types; listOf str; 132 default = 133 (lib.optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnit) 134 ++ (lib.optional config.services.matrix-conduit.enable "conduit.service"); 135 defaultText = lib.literalExpression '' 136 (optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnit) 137 ++ (optional config.services.matrix-conduit.enable "conduit.service") 138 ''; 139 description = '' 140 List of systemd units to require and wait for when starting the application service. 141 ''; 142 }; 143 144 registerToSynapse = lib.mkOption { 145 type = lib.types.bool; 146 default = config.services.matrix-synapse.enable; 147 defaultText = lib.literalExpression '' 148 config.services.matrix-synapse.enable 149 ''; 150 description = '' 151 Whether to add the bridge's app service registration file to 152 `services.matrix-synapse.settings.app_service_config_files`. 153 ''; 154 }; 155 }; 156 157 config = lib.mkIf cfg.enable { 158 159 users.users.mautrix-signal = { 160 isSystemUser = true; 161 group = "mautrix-signal"; 162 home = dataDir; 163 description = "Mautrix-Signal bridge user"; 164 }; 165 166 users.groups.mautrix-signal = { }; 167 168 services.matrix-synapse = lib.mkIf cfg.registerToSynapse { 169 settings.app_service_config_files = [ registrationFile ]; 170 }; 171 systemd.services.matrix-synapse = lib.mkIf cfg.registerToSynapse { 172 serviceConfig.SupplementaryGroups = [ "mautrix-signal" ]; 173 }; 174 175 # Note: this is defined here to avoid the docs depending on `config` 176 services.mautrix-signal.settings.homeserver = optOneOf ( 177 with config.services; 178 [ 179 (lib.mkIf matrix-synapse.enable (mkDefaults { 180 domain = matrix-synapse.settings.server_name; 181 })) 182 (lib.mkIf matrix-conduit.enable (mkDefaults { 183 domain = matrix-conduit.settings.global.server_name; 184 address = "http://localhost:${toString matrix-conduit.settings.global.port}"; 185 })) 186 ] 187 ); 188 189 systemd.services.mautrix-signal = { 190 description = "mautrix-signal, a Matrix-Signal puppeting bridge."; 191 192 wantedBy = [ "multi-user.target" ]; 193 wants = [ "network-online.target" ] ++ cfg.serviceDependencies; 194 after = [ "network-online.target" ] ++ cfg.serviceDependencies; 195 # ffmpeg is required for conversion of voice messages 196 path = [ pkgs.ffmpeg-headless ]; 197 198 preStart = '' 199 # substitute the settings file by environment variables 200 # in this case read from EnvironmentFile 201 test -f '${settingsFile}' && rm -f '${settingsFile}' 202 old_umask=$(umask) 203 umask 0177 204 ${pkgs.envsubst}/bin/envsubst \ 205 -o '${settingsFile}' \ 206 -i '${settingsFileUnsubstituted}' 207 umask $old_umask 208 209 # generate the appservice's registration file if absent 210 if [ ! -f '${registrationFile}' ]; then 211 ${cfg.package}/bin/mautrix-signal \ 212 --generate-registration \ 213 --config='${settingsFile}' \ 214 --registration='${registrationFile}' 215 fi 216 chmod 640 ${registrationFile} 217 218 umask 0177 219 # 1. Overwrite registration tokens in config 220 # 2. If environment variable MAUTRIX_SIGNAL_BRIDGE_LOGIN_SHARED_SECRET 221 # is set, set it as the login shared secret value for the configured 222 # homeserver domain. 223 ${pkgs.yq}/bin/yq -s '.[0].appservice.as_token = .[1].as_token 224 | .[0].appservice.hs_token = .[1].hs_token 225 | .[0] 226 | if env.MAUTRIX_SIGNAL_BRIDGE_LOGIN_SHARED_SECRET then .double_puppet.secrets.[.homeserver.domain] = env.MAUTRIX_SIGNAL_BRIDGE_LOGIN_SHARED_SECRET else . end' \ 227 '${settingsFile}' '${registrationFile}' > '${settingsFile}.tmp' 228 mv '${settingsFile}.tmp' '${settingsFile}' 229 umask $old_umask 230 ''; 231 232 serviceConfig = { 233 User = "mautrix-signal"; 234 Group = "mautrix-signal"; 235 EnvironmentFile = cfg.environmentFile; 236 StateDirectory = baseNameOf dataDir; 237 WorkingDirectory = dataDir; 238 ExecStart = '' 239 ${cfg.package}/bin/mautrix-signal \ 240 --config='${settingsFile}' \ 241 --registration='${registrationFile}' 242 ''; 243 LockPersonality = true; 244 NoNewPrivileges = true; 245 PrivateDevices = true; 246 PrivateTmp = true; 247 PrivateUsers = true; 248 ProtectClock = true; 249 ProtectControlGroups = true; 250 ProtectHome = true; 251 ProtectHostname = true; 252 ProtectKernelLogs = true; 253 ProtectKernelModules = true; 254 ProtectKernelTunables = true; 255 ProtectSystem = "strict"; 256 Restart = "on-failure"; 257 RestartSec = "30s"; 258 RestrictRealtime = true; 259 RestrictSUIDSGID = true; 260 SystemCallArchitectures = "native"; 261 SystemCallErrorNumber = "EPERM"; 262 SystemCallFilter = [ "@system-service" ]; 263 Type = "simple"; 264 UMask = 27; 265 }; 266 restartTriggers = [ settingsFileUnsubstituted ]; 267 }; 268 }; 269 meta = { 270 buildDocsInSandbox = false; 271 doc = ./mautrix-signal.md; 272 maintainers = with lib.maintainers; [ 273 alyaeanyx 274 frederictobiasc 275 ]; 276 }; 277}