diff --git a/nixos/roles/matrix-signal/default.nix b/nixos/roles/matrix-signal/default.nix new file mode 100644 index 00000000..576075ef --- /dev/null +++ b/nixos/roles/matrix-signal/default.nix @@ -0,0 +1,32 @@ +{ pkgs, config, lib, ... }: +let + synapse-port = 8008; + +in +{ + imports = [ + ./signald-module.nix + ./mautrix-signal-module.nix + ]; + + services.signald.enable = true; + + services.mautrix-signal = { + enable = true; + environmentFile = pkgs.privateFile "mautrix-signal.env"; + settings = { + homeserver = { + address = "http://localhost:${builtins.toString synapse-port}"; + domain = "maralorn.de"; + }; + bridge = { + public_portals = false; + federate_rooms = false; + permissions."@maralorn:maralorn.de" = "admin"; + contact_list_names = "allow"; + autocreate_contact_portal = false; + autocreate_group_portal = true; + }; + }; + }; +} diff --git a/nixos/roles/matrix-signal/mautrix-signal-module.nix b/nixos/roles/matrix-signal/mautrix-signal-module.nix new file mode 100644 index 00000000..71cd7e75 --- /dev/null +++ b/nixos/roles/matrix-signal/mautrix-signal-module.nix @@ -0,0 +1,174 @@ +{ config, pkgs, lib, ... }: + +with lib; + +let + dataDir = "/var/lib/mautrix-signal"; + registrationFile = "${dataDir}/signal-registration.yaml"; + cfg = config.services.mautrix-signal; + settingsFormat = pkgs.formats.json { }; + settingsFileUnsubstituted = settingsFormat.generate "mautrix-signal-config-unsubstituted.json" cfg.settings; + settingsFile = "${dataDir}/config.json"; + +in +{ + options = { + services.mautrix-signal = { + enable = mkEnableOption "Mautrix-Signal, a Matrix-Signal hybrid puppeting bridge"; + + settings = mkOption rec { + apply = recursiveUpdate default; + inherit (settingsFormat) type; + default = { + appservice = rec { + database = "postgresql:///mautrix-signal?host=/run/postgresql"; + database_opts = { }; + hostname = "0.0.0.0"; + port = 29328; + address = "http://localhost:${toString port}"; + }; + + bridge = { + permissions = { }; + double_puppet_server_map = { }; + login_shared_secret_map = { }; + }; + + logging = { + version = 1; + + formatters.precise.format = "[%(levelname)s@%(name)s] %(message)s"; + + handlers.console = { + class = "logging.StreamHandler"; + formatter = "precise"; + }; + + loggers = { + mau.level = "INFO"; + telethon.level = "INFO"; + + # prevent tokens from leaking in the logs: + # https://github.com/tulir/mautrix-signal/issues/351 + aiohttp.level = "WARNING"; + }; + + # log to console/systemd instead of file + root = { + level = "INFO"; + handlers = [ "console" ]; + }; + }; + }; + example = literalExample '' + { + homeserver = { + address = "http://localhost:8008"; + domain = "public-domain.tld"; + }; + + appservice.public = { + prefix = "/public"; + external = "https://public-appservice-address/public"; + }; + + bridge.permissions = { + "example.com" = "full"; + "@admin:example.com" = "admin"; + }; + } + ''; + description = '' + config.yaml configuration as a Nix attribute set. + Configuration options should match those described in + + example-config.yaml. + + + + Secret tokens should be specified using + instead of this world-readable attribute set. + ''; + }; + + environmentFile = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + File containing environment variables to be passed to the mautrix-signal service, + in which secret tokens can be specified securely by defining values for + MAUTRIX_SIGNAL_APPSERVICE_AS_TOKEN, + MAUTRIX_SIGNAL_APPSERVICE_HS_TOKEN, + ''; + }; + + serviceDependencies = mkOption { + type = with types; listOf str; + default = optional config.services.matrix-synapse.enable "matrix-synapse.service"; + description = '' + List of Systemd services to require and wait for when starting the application service. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + systemd.services.mautrix-signal = { + description = "Mautrix-Signal, a Matrix-Signal hybrid puppeting/relaybot bridge."; + + wantedBy = [ "multi-user.target" ]; + wants = [ "network-online.target" "signald.target" ]; + after = [ "network-online.target" "signald.target" ]; + + preStart = '' + # Not all secrets can be passed as environment variable (yet) + [ -f ${settingsFile} ] && rm -f ${settingsFile} + old_umask=$(umask) + umask 0277 + ${pkgs.envsubst}/bin/envsubst \ + -o ${settingsFile} \ + -i ${settingsFileUnsubstituted} + umask $old_umask + + # generate the appservice's registration file if absent + if [ ! -f '${registrationFile}' ]; then + ${pkgs.mautrix-signal}/bin/mautrix-signal \ + --generate-registration \ + --base-config='${pkgs.mautrix-signal}/${pkgs.mautrix-signal.pythonModule.sitePackages}/mautrix_signal/example-config.yaml' \ + --config='${settingsFile}' \ + --registration='${registrationFile}' + fi + ''; + + serviceConfig = { + Type = "simple"; + Restart = "always"; + + ProtectSystem = "full"; + ProtectHome = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectControlGroups = true; + + DynamicUser = true; + SupplementaryGroups = [ "signald" ]; + BindPaths = "/var/lib/signald"; + StateDirectory = baseNameOf dataDir; + UMask = 0023; + EnvironmentFile = cfg.environmentFile; + + ExecStart = '' + ${pkgs.mautrix-signal}/bin/mautrix-signal \ + --config='${settingsFile}' + ''; + }; + unitConfig = { + JoinsNamespaceOf = "signald.service"; + }; + + restartTriggers = [ settingsFileUnsubstituted ]; + }; + }; + + meta.maintainers = with maintainers; [ expipiplus1 ]; +} diff --git a/nixos/roles/matrix-signal/signald-module.nix b/nixos/roles/matrix-signal/signald-module.nix new file mode 100644 index 00000000..3c9ba7c9 --- /dev/null +++ b/nixos/roles/matrix-signal/signald-module.nix @@ -0,0 +1,71 @@ +{ config, pkgs, lib, ... }: + +with lib; + +let + cfg = config.services.signald; + +in +{ + options = { + services.signald = { + enable = mkEnableOption "Signald, an unofficial daemon for interacting with Signal"; + + socketFile = mkOption { + type = types.nullOr types.path; + default = null; + example = "/var/run/signald/signald.sock"; + description = '' + When started, signald will create a unix socket at this location. To + interact with it, connect to that socket and send new line (\n) + terminated JSON strings. + ''; + }; + }; + }; + + + config = mkIf cfg.enable { + users.users."signald" = { isSystemUser = true; }; + users.groups."signald" = { }; + + systemd.services.signald = { + description = "A daemon for interacting with the Signal Private Messenger"; + + wantedBy = [ "multi-user.target" ]; + wants = [ "network-online.target" ]; + after = [ "network-online.target" ]; + + serviceConfig = { + Type = "simple"; + Restart = "always"; + PermissionsStartOnly = true; + RuntimeDirectory = "signald"; + + ProtectSystem = "full"; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectControlGroups = true; + + DynamicUser = false; + PrivateTmp = true; + Group = "signald"; + StateDirectory = "signald"; + UMask = 0027; + + ExecStart = '' + ${pkgs.signald}/bin/signald \ + ${optionalString (cfg.socketFile != null) "--socket ${cfg.socketFile}"} \ + --data=''${STATE_DIRECTORY} \ + --database=jdbc:sqlite:''${STATE_DIRECTORY}/signald.db + ''; + }; + unitConfig = { + JoinsNamespaceOf = "mautrix-signal.service"; + }; + }; + }; + + meta.maintainers = with maintainers; [ expipiplus1 ]; +} + diff --git a/private b/private index 1b26e6a7..6c202579 160000 --- a/private +++ b/private @@ -1 +1 @@ -Subproject commit 1b26e6a7d466e08decd8cb951b0156cf109cfe73 +Subproject commit 6c202579a32c220a1ba0a103dd6809e98ca1169f