{ povSelf, config, lib, pkgs, ... }: let inherit (lib) types; cfg = lib.getAttrFromPath povSelf config; forEachInstance = f: lib.flip lib.mapAttrs' cfg (name: cfg: lib.nameValuePair "mautrixBridge-${name}" (f name cfg)); in { option = { default = { }; type = types.attrsOf ( types.submodule ({ options = { enable = lib.mkOption { type = types.bool; default = false; }; package = lib.mkOption { type = types.package; }; settings = lib.mkOption { type = (pkgs.formats.json { }).type; default = { }; }; environmentFile = lib.mkOption { type = with types; nullOr path; default = null; }; serviceDependencies = lib.mkOption { type = with types; listOf str; default = [ ]; }; }; }) ); }; config = { modules.filesystem.impermanence.system.dirs = ( (lib.attrNames cfg) |> lib.map (element: "/var/lib/private/mautrix-${element}") ); 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 { enable = cfg.enable; description = "mautrixBridge-${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 = "mautrixBridge-${name}"; Group = "mautrixBridge-${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" ]; }; } ); }; }