{ config, lib, pkgs, ... }: let forEachInstance = f: lib.flip lib.mapAttrs' config.services.mautrix-bridge ( name: cfg: lib.nameValuePair "mautrix-bridge-${name}" (f name cfg) ); in { options.services.mautrix-bridge = lib.mkOption { default = {}; type = lib.types.attrsOf ( lib.types.submodule ({ options = { package = lib.mkOption { type = lib.types.package; }; settings = lib.mkOption { type = (pkgs.formats.json {}).type; default = {}; }; environmentFile = lib.mkOption { type = lib.types.nullOr lib.types.path; default = null; }; serviceDependencies = lib.mkOption { type = lib.types.listOf lib.types.str; default = []; }; }; }) ); }; config.systemd.services = forEachInstance (name: cfg: let dataDir = "/var/lib/mautrix-${name}"; registrationFile = "${dataDir}/registration.yaml"; settingsFile = "${dataDir}/config.yaml"; settingsFileUnsubstituted = (pkgs.formats.json {}).generate "mautrix-${name}-config-unsubstituted.json" cfg.settings; in { description = "mautrix-bridge-${name}, a matrix puppeting bridge."; restartTriggers = [ settingsFileUnsubstituted ]; wantedBy = [ "multi-user.target" ]; wants = [ "network-online.target" ] ++ cfg.serviceDependencies; after = [ "network-online.target" ] ++ cfg.serviceDependencies; path = [ pkgs.ffmpeg-headless ]; preStart = '' # substitute the settings file by environment variables # in this case read from EnvironmentFile test -f '${settingsFile}' && rm -f '${settingsFile}' old_umask=$(umask) umask 0177 ${pkgs.envsubst}/bin/envsubst \ -o '${settingsFile}' \ -i '${settingsFileUnsubstituted}' umask $old_umask # generate the appservice's registration file if absent if [ ! -f '${registrationFile}' ]; then ${lib.getExe cfg.package} \ --generate-registration \ --config='${settingsFile}' \ --registration='${registrationFile}' fi chmod 640 ${registrationFile} umask 0177 ${pkgs.yq}/bin/yq -s ' .[0].appservice.as_token = (.[0].appservice.as_token // .[1].as_token) | .[0].appservice.hs_token = (.[0].appservice.hs_token // .[1].hs_token) ${lib.optionalString (name == "telegram") " | .[0].network.api_id = (.[0].network.api_id | tonumber) "} | .[0]' \ '${settingsFile}' '${registrationFile}' > '${settingsFile}.tmp' mv '${settingsFile}.tmp' '${settingsFile}' umask $old_umask ''; serviceConfig = { Type = "exec"; DynamicUser = true; User = "mautrix-bridge-${name}"; Group = "mautrix-bridge-${name}"; EnvironmentFile = cfg.environmentFile; StateDirectory = baseNameOf dataDir; WorkingDirectory = dataDir; UMask = 27; ExecStart = '' ${lib.getExe cfg.package} \ --no-update \ --config='${settingsFile}' ''; Restart = "on-failure"; RestartSec = "30s"; LockPersonality = true; NoNewPrivileges = true; MemoryDenyWriteExecute = lib.mkIf (name != "signal") true; PrivateDevices = true; PrivateTmp = true; PrivateUsers = true; ProtectSystem = "strict"; ProtectClock = true; ProtectHome = true; ProtectHostname = true; ProtectKernelLogs = true; ProtectKernelModules = true; ProtectKernelTunables = true; ProtectControlGroups = true; RestrictRealtime = true; RestrictSUIDSGID = true; SystemCallArchitectures = "native"; SystemCallErrorNumber = "EPERM"; SystemCallFilter = [ "@system-service" ]; }; }); }