about summary refs log tree commit diff
path: root/nixos/modules/services/matrix
diff options
context:
space:
mode:
authorMartin Weinelt <hexa@darmstadt.ccc.de>2024-02-03 23:19:58 +0100
committerMartin Weinelt <hexa@darmstadt.ccc.de>2024-02-09 16:05:05 +0100
commit143d266f0db386af987d19e0de79128bc8669714 (patch)
treee92edb8babb273402298c0e9b5e3409f4e4abcfc /nixos/modules/services/matrix
parent2565ab7b53ae6966bd08e68203a89645961849b3 (diff)
nixos/matrix-synapse: add UNIX domain socket listener support
Exposes two options, `path` and `mode`, to configure the location and
permissions on the socket file.

The `mode` needs to be specified as string in octal and will be converted
into a decimal integer, so it correctly passes through the YAML parser
and arrives at the `os.chmod` call in the Twisted codebase. What a fun
detour.

Adds an assertion, that either `path` or `bind_addresses` and `port` are
configured on every listener.

Migrates the default replication listener of the main instance to a UNIX
domain socket, because it is more efficient.

Introduces the `enableRegistrationScript` option, to gracefully disable
the user registration script, when the client listener listens on a UNIX
domain socket, which is something the script does not support.
Diffstat (limited to 'nixos/modules/services/matrix')
-rw-r--r--nixos/modules/services/matrix/synapse.md5
-rw-r--r--nixos/modules/services/matrix/synapse.nix156
2 files changed, 140 insertions, 21 deletions
diff --git a/nixos/modules/services/matrix/synapse.md b/nixos/modules/services/matrix/synapse.md
index f270be8c8d781..9c9c025fc5f54 100644
--- a/nixos/modules/services/matrix/synapse.md
+++ b/nixos/modules/services/matrix/synapse.md
@@ -126,8 +126,9 @@ then enable `services.matrix-synapse.settings.enable_registration = true;`.
 Otherwise, or you can generate a registration secret with
 {command}`pwgen -s 64 1` and set it with
 [](#opt-services.matrix-synapse.settings.registration_shared_secret).
-To create a new user or admin, run the following after you have set the secret
-and have rebuilt NixOS:
+To create a new user or admin from the terminal your client listener
+must be configured to use TCP sockets. Then you can run the following
+after you have set the secret and have rebuilt NixOS:
 ```ShellSession
 $ nix-shell -p matrix-synapse
 $ register_new_matrix_user -k your-registration-shared-secret http://localhost:8008
diff --git a/nixos/modules/services/matrix/synapse.nix b/nixos/modules/services/matrix/synapse.nix
index 4c1c396eac056..a6304d3533871 100644
--- a/nixos/modules/services/matrix/synapse.nix
+++ b/nixos/modules/services/matrix/synapse.nix
@@ -105,6 +105,19 @@ let
         SYSLOG_IDENTIFIER = logName;
       };
     });
+
+  toIntBase8 = str:
+    lib.pipe str [
+      lib.stringToCharacters
+      (map lib.toInt)
+      (lib.foldl (acc: digit: acc * 8 + digit) 0)
+    ];
+
+  toDecimalFilePermission = value:
+    if value == null then
+      null
+    else
+      toIntBase8 value;
 in {
 
   imports = [
@@ -192,10 +205,11 @@ in {
   ];
 
   options = let
-    listenerType = workerContext: types.submodule {
+    listenerType = workerContext: types.submodule ({ config, ... }: {
       options = {
         port = mkOption {
-          type = types.port;
+          type = types.nullOr types.port;
+          default = null;
           example = 8448;
           description = lib.mdDoc ''
             The port to listen for HTTP(S) requests on.
@@ -203,11 +217,20 @@ in {
         };
 
         bind_addresses = mkOption {
-          type = types.listOf types.str;
-          default = [
+          type = types.nullOr (types.listOf types.str);
+          default = if config.path != null then null else [
             "::1"
             "127.0.0.1"
           ];
+          defaultText = literalExpression ''
+            if path != null then
+              null
+            else
+              [
+                "::1"
+                "127.0.0.1"
+              ]
+          '';
           example = literalExpression ''
             [
               "::"
@@ -219,6 +242,35 @@ in {
           '';
         };
 
+        path = mkOption {
+          type = types.nullOr types.path;
+          default = null;
+          description = ''
+            Unix domain socket path to bind this listener to.
+
+            ::: {.note}
+              This option is incompatible with {option}`bind_addresses`, {option}`port`, {option}`tls`
+              and also does not support the `metrics` and `manhole` listener {option}`type`.
+            :::
+          '';
+        };
+
+        mode = mkOption {
+          type = types.nullOr (types.strMatching "^[0,2-7]{3,4}$");
+          default = if config.path != null then "660" else null;
+          defaultText = literalExpression ''
+            if path != null then
+              "660"
+            else
+              null
+          '';
+          example = "660";
+          description = ''
+            File permissions on the UNIX domain socket.
+          '';
+          apply = toDecimalFilePermission;
+        };
+
         type = mkOption {
           type = types.enum [
             "http"
@@ -234,17 +286,30 @@ in {
         };
 
         tls = mkOption {
-          type = types.bool;
-          default = !workerContext;
+          type = types.nullOr types.bool;
+          default = if config.path != null then
+            null
+          else
+            !workerContext;
+          defaultText = ''
+            Enabled for the main instance listener, unless it is configured with a UNIX domain socket path.
+          '';
           example = false;
           description = lib.mdDoc ''
             Whether to enable TLS on the listener socket.
+
+            ::: {.note}
+              This option will be ignored for UNIX domain sockets.
+            :::
           '';
         };
 
         x_forwarded = mkOption {
           type = types.bool;
-          default = false;
+          default = config.path != null;
+          defaultText = ''
+            Enabled if the listener is configured with a UNIX domain socket path
+          '';
           example = true;
           description = lib.mdDoc ''
             Use the X-Forwarded-For (XFF) header as the client IP and not the
@@ -291,11 +356,28 @@ in {
           '';
         };
       };
-    };
+    });
   in {
     services.matrix-synapse = {
       enable = mkEnableOption (lib.mdDoc "matrix.org synapse");
 
+      enableRegistrationScript = mkOption {
+        type = types.bool;
+        default = clientListener.bind_addresses != [];
+        example = false;
+        defaultText = ''
+          Enabled if the client listener uses TCP sockets
+        '';
+        description = ''
+          Whether to install the `register_new_matrix_user` script, that
+          allows account creation on the terminal.
+
+          ::: {.note}
+            This script does not work when the client listener uses UNIX domain sockets
+          :::
+        '';
+      };
+
       serviceUnit = lib.mkOption {
         type = lib.types.str;
         readOnly = true;
@@ -616,11 +698,8 @@ in {
                   compress = false;
                 }];
               }] ++ lib.optional hasWorkers {
-                port = 9093;
-                bind_addresses = [ "127.0.0.1" ];
+                path = "/run/matrix-synapse/main_replication.sock";
                 type = "http";
-                tls = false;
-                x_forwarded = false;
                 resources = [{
                   names = [ "replication" ];
                   compress = false;
@@ -630,7 +709,7 @@ in {
                 List of ports that Synapse should listen on, their purpose and their configuration.
 
                 By default, synapse will be configured for client and federation traffic on port 8008, and
-                for worker replication traffic on port 9093. See [`services.matrix-synapse.workers`](#opt-services.matrix-synapse.workers)
+                use a UNIX domain socket for worker replication. See [`services.matrix-synapse.workers`](#opt-services.matrix-synapse.workers)
                 for more details.
               '';
             };
@@ -1006,9 +1085,15 @@ in {
             listener = lib.findFirst
               (
                 listener:
-                  listener.port == main.port
+                  (
+                    lib.hasAttr "port" main && listener.port or null == main.port
+                    || lib.hasAttr "path" main && listener.path or null == main.path
+                  )
                   && listenerSupportsResource "replication" listener
-                  && (lib.any (bind: bind == main.host || bind == "0.0.0.0" || bind == "::") listener.bind_addresses)
+                  && (
+                    lib.hasAttr "host" main &&  lib.any (bind: bind == main.host || bind == "0.0.0.0" || bind == "::") listener.bind_addresses
+                    || lib.hasAttr "path" main
+                  )
               )
               null
               cfg.settings.listeners;
@@ -1022,15 +1107,44 @@ in {
           This is done by default unless you manually configure either of those settings.
         '';
       }
-    ];
+      {
+        assertion = cfg.enableRegistrationScript -> clientListener.path == null;
+        message = ''
+          The client listener on matrix-synapse is configured to use UNIX domain sockets.
+          This configuration is incompatible with the `register_new_matrix_user` script.
+
+          Disable  `services.mastrix-synapse.enableRegistrationScript` to continue.
+        '';
+      }
+    ]
+    ++ (map (listener: {
+      assertion = (listener.path == null) != (listener.bind_addresses == null);
+      message = ''
+        Listeners require either a UNIX domain socket `path` or `bind_addresses` for a TCP socket.
+      '';
+    }) cfg.settings.listeners)
+    ++ (map (listener: {
+      assertion = listener.path != null -> (listener.bind_addresses == null && listener.port == null && listener.tls == null);
+      message = let
+        formatKeyValue = key: value: lib.optionalString (value != null) "  - ${key}=${toString value}\n";
+      in ''
+        Listener configured with UNIX domain socket (${toString listener.path}) ignores the following options:
+        ${formatKeyValue "bind_addresses" listener.bind_addresses}${formatKeyValue "port" listener.port}${formatKeyValue "tls" listener.tls}
+      '';
+    }) cfg.settings.listeners)
+    ++ (map (listener: {
+      assertion = listener.path == null || listener.type == "http";
+      message = ''
+        Listener configured with UNIX domain socket (${toString listener.path}) only supports the "http" listener type.
+      '';
+    }) cfg.settings.listeners);
 
     services.matrix-synapse.settings.redis = lib.mkIf cfg.configureRedisLocally {
       enabled = true;
       path = config.services.redis.servers.matrix-synapse.unixSocket;
     };
     services.matrix-synapse.settings.instance_map.main = lib.mkIf hasWorkers (lib.mkDefault {
-      host = "127.0.0.1";
-      port = 9093;
+      path = "/run/matrix-synapse/main_replication.sock";
     });
 
     services.matrix-synapse.serviceUnit = if hasWorkers then "matrix-synapse.target" else "matrix-synapse.service";
@@ -1086,6 +1200,8 @@ in {
             User = "matrix-synapse";
             Group = "matrix-synapse";
             WorkingDirectory = cfg.dataDir;
+            RuntimeDirectory = "matrix-synapse";
+            RuntimeDirectoryPreserve = true;
             ExecReload = "${pkgs.util-linux}/bin/kill -HUP $MAINPID";
             Restart = "on-failure";
             UMask = "0077";
@@ -1178,7 +1294,9 @@ in {
       user = "matrix-synapse";
     };
 
-    environment.systemPackages = [ registerNewMatrixUser ];
+    environment.systemPackages = lib.optionals cfg.enableRegistrationScript [
+      registerNewMatrixUser
+    ];
   };
 
   meta = {