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