{ lib, config, pkgs, ... }: let cfg = config.services.mautrix-mattermost; dataDir = cfg.dataDir; format = pkgs.formats.yaml { }; registrationFile = "${dataDir}/mattermost-registration.yaml"; settingsFile = "${dataDir}/config.yaml"; settingsFileUnformatted = format.generate "mattermost-config-unsubstituted.yaml" cfg.settings; in { options = { services.mautrix-mattermost = { enable = lib.mkEnableOption "Mautrix-Mattermost, a Matrix-Mattermost puppeting/relay-bot bridge"; package = lib.mkOption { type = lib.types.package; default = pkgs.mautrix-mattermost; defaultText = lib.literalExpression "pkgs.mautrix-mattermost"; description = "The mautrix-mattermost package to use."; }; settings = lib.mkOption { type = lib.types.submodule { freeformType = format.type; config = { _module.args = { inherit cfg lib; }; }; options = { homeserver = lib.mkOption { type = lib.types.attrs; default = { software = "standard"; status_endpoint = null; message_send_checkpoint_endpoint = null; async_media = false; websocket = false; ping_interval_seconds = 0; }; description = '' Homeserver configuration. See the mautrix-mattermost example-config.yaml for more information. ''; }; appservice = lib.mkOption { type = lib.types.attrs; default = { address = "http://localhost:29335"; hostname = "0.0.0.0"; port = 29335; database = { type = "sqlite3"; uri = "file:${dataDir}/mautrix-mattermost.db?_txlock=immediate"; max_open_conns = 20; max_idle_conns = 2; max_conn_idle_time = null; max_conn_lifetime = null; }; id = "mattermost"; bot = { username = "mattermostbot"; displayname = "Mattermost bridge bot"; avatar = ""; }; ephemeral_events = true; async_transactions = false; as_token = "This value is generated when generating the registration"; hs_token = "This value is generated when generating the registration"; }; description = '' Appservice configuration. See the mautrix-mattermost example-config.yaml for more information. ''; }; bridge = lib.mkOption { type = lib.types.attrs; default = { username_template = "mattermost_{{.}}"; command_prefix = "!mm"; double_puppet_server_map = { }; double_puppet_allow_discovery = false; login_shared_secret_map = { }; management_room_text = { welcome = "Hello, I'm a Mattermost bridge bot."; welcome_connected = "Use `help` for help."; welcome_unconnected = "Use `help` for help or `login` to log in."; additional_help = ""; }; encryption = { allow = false; default = false; appservice = false; require = false; allow_key_sharing = false; plaintext_mentions = false; delete_keys = { delete_outbound_on_ack = false; dont_store_outbound = false; ratchet_on_decrypt = false; delete_fully_used_on_decrypt = false; delete_prev_on_new_session = false; delete_on_device_delete = false; periodically_delete_expired = false; delete_outdated_inbound = false; }; verification_levels = { receive = "unverified"; send = "unverified"; share = "cross-signed-tofu"; }; rotation = { enable_custom = false; milliseconds = 604800000; messages = 100; disable_device_change_key_rotation = false; }; }; provisioning = { prefix = "/_matrix/provision"; shared_secret = "generate"; debug_endpoints = false; }; permissions = { "*" = "relay"; }; }; description = '' Bridge configuration. See the mautrix-mattermost example-config.yaml for more information. ''; }; logging = lib.mkOption { type = lib.types.attrs; default = { min_level = "info"; writers = lib.singleton { type = "stdout"; format = "pretty-colored"; time_format = " "; }; }; description = '' Logging configuration. See the mautrix-mattermost example-config.yaml for more information. ''; }; }; }; default = { }; description = '' {file}`config.yaml` configuration as a Nix attribute set. ''; }; registerToSynapse = lib.mkOption { type = lib.types.bool; default = config.services.matrix-synapse.enable; defaultText = lib.literalExpression "config.services.matrix-synapse.enable"; description = '' Whether to add the bridge's app service registration file to `services.matrix-synapse.settings.app_service_config_files`. ''; }; dataDir = lib.mkOption { type = lib.types.path; default = "/var/lib/mautrix-mattermost"; description = '' Directory to store the bridge's configuration and database files. ''; }; environmentFile = lib.mkOption { type = lib.types.nullOr lib.types.path; default = null; description = '' File containing environment variables to substitute when copying the configuration out of Nix store to the `services.mautrix-mattermost.dataDir`. Can be used for storing the secrets without making them available in the Nix store. ''; }; serviceUnit = lib.mkOption { type = lib.types.str; readOnly = true; default = "mautrix-mattermost.service"; description = "The systemd unit for the bridge service."; }; registrationServiceUnit = lib.mkOption { type = lib.types.str; readOnly = true; default = "mautrix-mattermost-registration.service"; description = "The registration service that generates the registration file."; }; serviceDependencies = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ cfg.registrationServiceUnit ] ++ (lib.lists.optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnit) ++ (lib.lists.optional (config.services ? matrix-conduit && config.services.matrix-conduit.enable) "matrix-conduit.service") ++ (lib.lists.optional (config.services ? dendrite && config.services.dendrite.enable) "dendrite.service"); description = '' List of Systemd services to require and wait for when starting the application service. ''; }; }; }; config = lib.mkIf cfg.enable { assertions = [ { assertion = cfg.settings.homeserver.domain or "" != "" && cfg.settings.homeserver.address or "" != ""; message = '' The options with information about the homeserver: `services.mautrix-mattermost.settings.homeserver.domain` and `services.mautrix-mattermost.settings.homeserver.address` have to be set. ''; } { assertion = cfg.settings.bridge.permissions or { } != { }; message = '' The option `services.mautrix-mattermost.settings.bridge.permissions` has to be set. ''; } ]; users.users.mautrix-mattermost = { isSystemUser = true; group = "mautrix-mattermost"; extraGroups = [ "mautrix-mattermost-registration" ]; home = dataDir; description = "Mautrix-Mattermost bridge user"; }; users.groups.mautrix-mattermost = { }; users.groups.mautrix-mattermost-registration = { members = lib.lists.optional config.services.matrix-synapse.enable "matrix-synapse"; }; services.matrix-synapse = lib.mkIf cfg.registerToSynapse { settings.app_service_config_files = [ registrationFile ]; }; systemd.tmpfiles.rules = [ "d ${cfg.dataDir} 770 mautrix-mattermost mautrix-mattermost -" ]; systemd.services = { matrix-synapse = lib.mkIf cfg.registerToSynapse { serviceConfig.SupplementaryGroups = [ "mautrix-mattermost-registration" ]; wants = [ "mautrix-mattermost-registration.service" ]; after = [ "mautrix-mattermost-registration.service" ]; }; mautrix-mattermost-registration = { description = "Mautrix-Mattermost registration generation service"; wantedBy = lib.mkIf cfg.registerToSynapse [ "multi-user.target" ]; before = lib.mkIf cfg.registerToSynapse [ "matrix-synapse.service" ]; path = [ pkgs.yq pkgs.envsubst cfg.package ]; script = '' # substitute the settings file by environment variables # in this case read from EnvironmentFile rm -f '${settingsFile}' old_umask=$(umask) umask 0177 envsubst \ -o '${settingsFile}' \ -i '${settingsFileUnformatted}' config_has_tokens=$(yq '.appservice | has("as_token") and has("hs_token")' '${settingsFile}') registration_already_exists=$([[ -f '${registrationFile}' ]] && echo "true" || echo "false") echo "There are tokens in the config: $config_has_tokens" echo "Registration already existed: $registration_already_exists" # tokens not configured from config/environment file, and registration file # is already generated, override tokens in config to make sure they are not lost if [[ $config_has_tokens == "false" && $registration_already_exists == "true" ]]; then echo "Copying as_token, hs_token from registration into configuration" yq -sY '.[0].appservice.as_token = .[1].as_token | .[0].appservice.hs_token = .[1].hs_token | .[0]' '${settingsFile}' '${registrationFile}' \ > '${settingsFile}.tmp' mv '${settingsFile}.tmp' '${settingsFile}' fi # make sure --generate-registration does not affect config.yaml cp '${settingsFile}' '${settingsFile}.tmp' echo "Generating registration file" mautrix-mattermost \ --generate-registration \ --config='${settingsFile}.tmp' \ --registration='${registrationFile}' rm '${settingsFile}.tmp' # no tokens configured, and new were just generated by generate registration for first time if [[ $config_has_tokens == "false" && $registration_already_exists == "false" ]]; then echo "Copying newly generated as_token, hs_token from registration into configuration" yq -sY '.[0].appservice.as_token = .[1].as_token | .[0].appservice.hs_token = .[1].hs_token | .[0]' '${settingsFile}' '${registrationFile}' \ > '${settingsFile}.tmp' mv '${settingsFile}.tmp' '${settingsFile}' fi # Make sure correct tokens are in the registration file if [[ $config_has_tokens == "true" || $registration_already_exists == "true" ]]; then echo "Copying as_token, hs_token from configuration to the registration file" yq -sY '.[1].as_token = .[0].appservice.as_token | .[1].hs_token = .[0].appservice.hs_token | .[1]' '${settingsFile}' '${registrationFile}' \ > '${registrationFile}.tmp' mv '${registrationFile}.tmp' '${registrationFile}' fi umask $old_umask chown :mautrix-mattermost-registration '${registrationFile}' chmod 640 '${registrationFile}' ''; serviceConfig = { Type = "oneshot"; RemainAfterExit = true; UMask = 27; User = "mautrix-mattermost"; Group = "mautrix-mattermost"; SystemCallFilter = [ "@system-service" ]; ProtectSystem = "strict"; ProtectHome = true; ReadWritePaths = [ dataDir ]; StateDirectory = "mautrix-mattermost"; EnvironmentFile = cfg.environmentFile; }; restartTriggers = [ settingsFileUnformatted ]; }; mautrix-mattermost = { description = "Mautrix-Mattermost, a Matrix-Mattermost puppeting/relaybot bridge"; wantedBy = [ "multi-user.target" ]; wants = [ "network-online.target" ] ++ cfg.serviceDependencies; after = [ "network-online.target" ] ++ cfg.serviceDependencies; serviceConfig = { Type = "simple"; User = "mautrix-mattermost"; Group = "mautrix-mattermost"; PrivateUsers = true; Restart = "on-failure"; RestartSec = 30; WorkingDirectory = dataDir; ExecStart = '' ${lib.getExe cfg.package} \ --config='${settingsFile}' ''; EnvironmentFile = cfg.environmentFile; ProtectSystem = "strict"; ProtectHome = true; ProtectKernelTunables = true; ProtectKernelModules = true; ProtectControlGroups = true; PrivateDevices = true; PrivateTmp = true; RestrictSUIDSGID = true; RestrictRealtime = true; LockPersonality = true; ProtectKernelLogs = true; ProtectHostname = true; ProtectClock = true; SystemCallArchitectures = "native"; SystemCallErrorNumber = "EPERM"; SystemCallFilter = "@system-service"; ReadWritePaths = [ cfg.dataDir ]; }; restartTriggers = [ settingsFileUnformatted ]; }; }; }; }