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