1
2
mirror of https://github.com/dali99/nixos-matrix-modules.git synced 2026-01-19 05:58:22 +01:00

24 Commits

Author SHA1 Message Date
82959f612f Merge pull request #10 from h7x4/additional-nixpkgs-2511-stuff
Additional fixes for 25.11
2025-12-08 18:16:53 +01:00
51665e27e2 tests/nginx-pipeline: move to checks, fix nix flake show 2025-12-09 01:39:12 +09:00
700aa1b8a6 flake.nix: bump nixpkgs target from 23.11 -> 25.11 2025-12-09 01:38:41 +09:00
a82c7e2d94 treewide: toGNUCommandLineShell -> toCommandLineShellGNU 2025-12-09 01:34:24 +09:00
8493e635fa synapse-module: source kill from coreutils 2025-12-09 01:33:07 +09:00
25b9f31ef1 Update MIGRATIONS.MD for version 0.8.0 changes
Added migration notes for version 0.8.0 regarding saml2 deprecation and its alternatives.
2025-12-04 11:28:15 +01:00
19c690bb4f Remove 'saml2' from extras as it is broken 2025-12-01 01:10:31 +01:00
099db715d1 synapse: Remove removed extra feature 2025-07-22 22:35:55 +02:00
da9dc0479f sliding-sync: remove 2025-01-02 23:34:05 +01:00
ff787d410c Add documentation for new sliding-sync setup and upgrade info 2024-09-27 06:21:37 +02:00
f8843835e2 sliding-sync: deprecate 2024-09-27 06:09:23 +02:00
f4e20d0360 Update README.MD 2024-08-29 10:32:38 +02:00
d7dc42c9bb sliding-sync: make enableAcme lib.mkDefault to match synapse 2024-06-01 11:39:05 +02:00
61b366f5f6 migrate to ensureDBOwnership 2024-06-01 11:11:14 +02:00
6c9b67974b fix generating multiple upstreams of same type 2024-03-13 07:39:59 +01:00
19b85a2562 Merge pull request #7 from dali99/refactor-nginx-upstream-generation
refactor nginx upstream generation, add support for unix sockets
2024-03-13 06:23:07 +01:00
d48997cfb4 generate only one upstream per worker 2024-03-13 06:22:24 +01:00
b8d7c76a7e treewide: add support for unix sockets 2024-01-27 07:52:26 +01:00
19d50fae63 nginx-pipeline: add basic test 2024-01-27 07:52:26 +01:00
18d3b34406 nginx: refactor upstream generation 2024-01-27 07:52:25 +01:00
85804fce8d lib: fix bug where lib.throw does not exist 2024-01-27 07:40:40 +01:00
046194cdad v0.5.0
This is mostly a maintainance release to be compatible with nixos-23.11 but comes with some small improvements as well
2023-12-02 09:58:52 +01:00
3f92b5f197 use nixpkgs sliding sync package 2023-12-02 09:49:03 +01:00
a24a5e5da4 update to 23.11 2023-12-02 09:44:45 +01:00
14 changed files with 327 additions and 286 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
result

View File

@@ -2,9 +2,20 @@
This is a best effort document descibing neccecary changes you might have to do when updating This is a best effort document descibing neccecary changes you might have to do when updating
## 0.5.0 UNRELEASED ## 0.8.0
The module has been renamed from `synapse` to `default`
`saml2` is no longer enabled, as it depends on vulnerable dependencies and isnt really built in nixpks anymore.
If you need to authenticate with saml, you should deploy some sort of saml to openid bridge, instead.
## 0.6.1
enableSlidingSync, and setting matrix-synapse.sliding-sync.environmentFile (or any other sliding-sync setting)
is no longer needed for a sliding-sync setup. Upgrading will force relogins for all users.
## 0.5.0
* The module has been renamed from `synapse` to `default`
* The synapse module now expects a wrapper-style package. This means the module is now incompatible with nixpkgs < 23.11.

View File

@@ -1,3 +1,5 @@
For support and requests feel free to join [#nixos-matrix-modules:dodsorf.as](https://matrix.to/#/#nixos-matrix-modules:dodsorf.as), [uri](matrix:r/nixos-matrix-modules:dodsorf.as)
With matrix.YOURDOMAIN pointing at the server: With matrix.YOURDOMAIN pointing at the server:
``` ```
@@ -36,19 +38,3 @@ With matrix.YOURDOMAIN pointing at the server:
``` ```
is ~enough to get a functional matrix-server running with some workers is ~enough to get a functional matrix-server running with some workers
## Sliding Sync (Element X)
Just add the following to your config and point `slidingsync.YOURDOMAIN` at the server
```
services.matrix-synapse-next = {
enableSlidingSync = true;
};
services.matrix-synapse.sliding-sync.environmentFile = "/some/file/containing/SYNCV3_SECRET=<some secret>";
```
If using [well-known delagation](https://matrix-org.github.io/synapse/v1.37/delegate.html) make sure `YOURDOMAIN/.well-known/matrix/client` matches
what's in `matrix.YOURDOMAIN/.well-known/matrix/client`

20
flake.lock generated
View File

@@ -1,23 +1,23 @@
{ {
"nodes": { "nodes": {
"nixpkgs-lib": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1673743903, "lastModified": 1764983851,
"narHash": "sha256-sloY6KYyVOozJ1CkbgJPpZ99TKIjIvM+04V48C04sMQ=", "narHash": "sha256-y7RPKl/jJ/KAP/VKLMghMgXTlvNIJMHKskl8/Uuar7o=",
"owner": "nix-community", "owner": "NixOS",
"repo": "nixpkgs.lib", "repo": "nixpkgs",
"rev": "7555e2dfcbac1533f047021f1744ac8871150f9f", "rev": "d9bc5c7dceb30d8d6fafa10aeb6aa8a48c218454",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "nix-community", "id": "nixpkgs",
"repo": "nixpkgs.lib", "ref": "nixos-25.11",
"type": "github" "type": "indirect"
} }
}, },
"root": { "root": {
"inputs": { "inputs": {
"nixpkgs-lib": "nixpkgs-lib" "nixpkgs": "nixpkgs"
} }
} }
}, },

View File

@@ -2,13 +2,31 @@
description = "NixOS modules for matrix related services"; description = "NixOS modules for matrix related services";
inputs = { inputs = {
nixpkgs-lib.url = github:nix-community/nixpkgs.lib; nixpkgs.url = "nixpkgs/nixos-25.11";
}; };
outputs = { self, nixpkgs-lib }: { outputs = { self, nixpkgs }: {
nixosModules = { nixosModules = {
default = import ./module.nix; default = import ./module.nix;
}; };
lib = import ./lib.nix { lib = nixpkgs-lib.lib; };
lib = import ./lib.nix { lib = nixpkgs.lib; };
checks = let
forAllSystems = f:
nixpkgs.lib.genAttrs [
"x86_64-linux"
"aarch64-linux"
"x86_64-darwin"
"aarch64-darwin"
] (system: f nixpkgs.legacyPackages.${system});
in forAllSystems (pkgs: let
tests = import ./tests {
inherit nixpkgs pkgs;
matrix-lib = self.lib;
};
in {
inherit (tests) nginx-pipeline-eval;
});
}; };
} }

62
lib.nix
View File

@@ -4,7 +4,7 @@ rec {
isListenerType = type: l: lib.any (r: lib.any (n: n == type) r.names) l.resources; isListenerType = type: l: lib.any (r: lib.any (n: n == type) r.names) l.resources;
# Get the first listener that includes the given resource from worker # Get the first listener that includes the given resource from worker
firstListenerOfType = type: ls: lib.lists.findFirst (isListenerType type) firstListenerOfType = type: ls: lib.lists.findFirst (isListenerType type)
(lib.throw "No listener with resource: ${type} configured") (throw "No listener with resource: ${type} configured")
ls; ls;
# Get an attrset of the host and port from a listener # Get an attrset of the host and port from a listener
connectionInfo = l: { connectionInfo = l: {
@@ -17,4 +17,64 @@ rec {
l = firstListenerOfType r w.settings.worker_listeners; l = firstListenerOfType r w.settings.worker_listeners;
in connectionInfo l; in connectionInfo l;
mapWorkersToUpstreamsByType = workerInstances:
lib.pipe workerInstances [
lib.attrValues
# Index by worker type
(lib.foldl (acc: worker: acc // {
${worker.type} = (acc.${worker.type} or [ ]) ++ [ worker ];
}) { })
# Subindex by resource names, listener types, and convert to upstreams
(lib.mapAttrs (_: workers: lib.pipe workers [
(lib.concatMap (worker: [ (lib.lists.head worker.settings.worker_listeners) ]))
lib.flatten
mapListenersToUpstreamsByType
]))
];
mapListenersToUpstreamsByType = listenerInstances:
lib.pipe listenerInstances [
# Index by resource names
(lib.concatMap (listener: lib.pipe listener [
(listener: let
allResourceNames = lib.pipe listener.resources [
(map (resource: resource.names))
lib.flatten
lib.unique
];
in if allResourceNames == [ ]
then { "empty" = listener; }
else lib.genAttrs allResourceNames (_: listener))
lib.attrsToList
]))
(lib.foldl (acc: listener: acc // {
${listener.name} = (acc.${listener.name} or [ ]) ++ [ listener.value ];
}) { })
# Index by listener type
(lib.mapAttrs (_:
(lib.foldl (acc: listener: acc // {
${listener.type} = (acc.${listener.type} or [ ]) ++ [ listener ];
}) { })
))
# Convert listeners to upstream URIs
(lib.mapAttrs (_:
(lib.mapAttrs (_: listeners:
lib.pipe listeners [
(lib.concatMap (listener:
if listener.path != null
then [ "unix:${listener.path}" ]
else (map (addr: "${addr}:${toString listener.port}") listener.bind_addresses)
))
# NOTE: Adding ` = { }` to every upstream might seem unnecessary in isolation,
# but it makes it easier to set upstreams in the nginx module.
(uris: lib.genAttrs uris (_: { }))
]
))
))
];
} }

View File

@@ -1,8 +1,14 @@
{ ... }: { lib, ... }:
{ {
imports = [ imports = [
./synapse-module ./synapse-module
./sliding-sync
# TODO: Remove after 25.05
(lib.mkRemovedOptionModule [ "services" "matrix-synapse" "sliding-sync" ] ''
`services.matrix-synapse.sliding-sync` is no longer necessary to use sliding-sync with synapse.
As synapse now includes this in itself, if you have a manually managed `.well-known/matrix/client` file
remove the proxy url from it.
'')
]; ];
} }

View File

@@ -1,37 +0,0 @@
{ lib
, buildGoModule
, fetchFromGitHub
}:
buildGoModule rec {
pname = "matrix-sliding-sync";
version = "0.99.11";
src = fetchFromGitHub {
owner = "matrix-org";
repo = "sliding-sync";
rev = "refs/tags/v${version}";
hash = "sha256-Wd/nnJhKg+BDyOIz42zEScjzQRrpEq6YG9/9Tk24hgg=";
};
vendorHash = "sha256-0QSyYhOht1j1tWNxHQh+NUZA/W1xy7ANu+29H/gusOE=";
subPackages = [ "cmd/syncv3" ];
ldflags = [
"-s"
"-w"
"-X main.GitCommit=${src.rev}"
];
# requires a running matrix-synapse
doCheck = false;
meta = with lib; {
description = "A sliding sync implementation of MSC3575 for matrix";
homepage = "https://github.com/matrix-org/sliding-sync";
license = with licenses; [ asl20 ];
maintainers = with maintainers; [ emilylange ];
mainProgram = "syncv3";
};
}

View File

@@ -1,117 +0,0 @@
{ config, lib, pkgs, ... }:
let
cfg = config.services.matrix-synapse.sliding-sync;
in
{
disabledModules = [ "services/matrix/matrix-sliding-sync.nix" ];
options.services.matrix-synapse.sliding-sync = {
enable = lib.mkEnableOption (lib.mdDoc "sliding sync");
package = lib.mkOption {
type = lib.types.package;
default = pkgs.callPackage ../pkgs/matrix-sliding-sync { };
description = "What package to use for the sliding-sync proxy.";
};
enableNginx = lib.mkEnableOption (lib.mdDoc "autogenerated nginx config");
publicBaseUrl = lib.mkOption {
type = lib.types.str;
description = "The domain where clients connect, only has an effect with enableNginx";
example = "slidingsync.matrix.org";
};
settings = lib.mkOption {
type = lib.types.submodule {
freeformType = with lib.types; attrsOf str;
options = {
SYNCV3_SERVER = lib.mkOption {
type = lib.types.str;
description = lib.mdDoc ''
The destination homeserver to talk to not including `/_matrix/` e.g `https://matrix.example.org`.
'';
};
SYNCV3_DB = lib.mkOption {
type = lib.types.str;
default = "postgresql:///matrix-sliding-sync?host=/run/postgresql";
description = lib.mdDoc ''
The postgres connection string.
Refer to <https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING>.
'';
};
SYNCV3_BINDADDR = lib.mkOption {
type = lib.types.str;
default = "127.0.0.1:8009";
example = "[::]:8008";
description = lib.mdDoc "The interface and port to listen on.";
};
SYNCV3_LOG_LEVEL = lib.mkOption {
type = lib.types.enum [ "trace" "debug" "info" "warn" "error" "fatal" ];
default = "info";
description = lib.mdDoc "The level of verbosity for messages logged.";
};
};
};
default = { };
description = ''
Freeform environment variables passed to the sliding sync proxy.
Refer to <https://github.com/matrix-org/sliding-sync#setup> for all supported values.
'';
};
createDatabase = lib.mkOption {
type = lib.types.bool;
default = true;
description = lib.mdDoc ''
Whether to enable and configure `services.postgres` to ensure that the database user `matrix-sliding-sync`
and the database `matrix-sliding-sync` exist.
'';
};
environmentFile = lib.mkOption {
type = lib.types.str;
description = lib.mdDoc ''
Environment file as defined in {manpage}`systemd.exec(5)`.
This must contain the {env}`SYNCV3_SECRET` variable which should
be generated with {command}`openssl rand -hex 32`.
'';
};
};
config = lib.mkIf cfg.enable {
services.postgresql = lib.optionalAttrs cfg.createDatabase {
enable = true;
ensureDatabases = [ "matrix-sliding-sync" ];
ensureUsers = [ rec {
name = "matrix-sliding-sync";
ensurePermissions."DATABASE \"${name}\"" = "ALL PRIVILEGES";
} ];
};
systemd.services.matrix-sliding-sync = {
after = lib.optional cfg.createDatabase "postgresql.service";
wantedBy = [ "multi-user.target" ];
environment = cfg.settings;
serviceConfig = {
DynamicUser = true;
EnvironmentFile = cfg.environmentFile;
ExecStart = lib.getExe cfg.package;
StateDirectory = "matrix-sliding-sync";
WorkingDirectory = "%S/matrix-sliding-sync";
};
};
services.nginx.virtualHosts.${cfg.publicBaseUrl} = lib.mkIf cfg.enableNginx {
enableACME = true;
forceSSL = true;
locations."/" = {
proxyPass = lib.replaceStrings [ "0.0.0.0" "::" ] [ "127.0.0.1" "::1" ] "http://${cfg.settings.SYNCV3_BINDADDR}";
};
};
};
}

View File

@@ -10,9 +10,23 @@ let
wcfgText = "config.services.matrix-synapse-next.workers"; wcfgText = "config.services.matrix-synapse-next.workers";
format = pkgs.formats.yaml {}; format = pkgs.formats.yaml {};
matrix-synapse-common-config = format.generate "matrix-synapse-common-config.yaml" cfg.settings; matrix-synapse-common-config = format.generate "matrix-synapse-common-config.yaml" (cfg.settings // {
pluginsEnv = cfg.package.python.buildEnv.override { listeners = map (lib.filterAttrsRecursive (_: v: v != null)) cfg.settings.listeners;
extraLibs = cfg.plugins; });
# TODO: Align better with the upstream module
wrapped = cfg.package.override {
inherit (cfg) plugins;
extras = [
"postgres"
"oidc"
"systemd"
"url-preview"
"sentry"
"jwt"
"redis"
"cache-memory"
];
}; };
inherit (lib) inherit (lib)
@@ -33,7 +47,7 @@ in
imports = [ imports = [
./nginx.nix ./nginx.nix
(import ./workers.nix { (import ./workers.nix {
inherit matrix-lib throw' format matrix-synapse-common-config pluginsEnv; inherit matrix-lib throw' format matrix-synapse-common-config wrapped;
}) })
]; ];
@@ -65,6 +79,15 @@ in
''; '';
}; };
socketDir = mkOption {
type = types.path;
default = "/run/matrix-synapse";
description = ''
The directory where matrix-synapse by default stores the sockets of
all listeners that bind to UNIX sockets.
'';
};
enableNginx = mkEnableOption "The synapse module managing nginx"; enableNginx = mkEnableOption "The synapse module managing nginx";
public_baseurl = mkOption { public_baseurl = mkOption {
@@ -121,14 +144,42 @@ in
type = types.listOf (types.submodule { type = types.listOf (types.submodule {
options = { options = {
port = mkOption { port = mkOption {
type = types.port; type = with types; nullOr types.port;
description = "The TCP port to bind to"; default = null;
description = ''
The TCP port to bind to.
::: {.note}
This option will be ignored if {option}`path` is set to a non-null value.
:::
'';
example = 8448; example = 8448;
}; };
path = mkOption {
type = with types; nullOr path;
default = null;
description = ''
The UNIX socket to bind to.
::: {.note}
This option will override {option}`bind_addresses` and {option}`port`
if set to a non-null value.
:::
'';
example = literalExpression ''''${${cfgText}.socketDir}/matrix-synapse.sock'';
};
bind_addresses = mkOption { bind_addresses = mkOption {
type = types.listOf types.str; type = types.listOf types.str;
description = "A list of local addresses to listen on"; default = [ ];
description = ''
A list of local addresses to listen on.
::: {.note}
This option will be ignored if {option}`path` is set to a non-null value.
:::
'';
}; };
type = mkOption { type = mkOption {
@@ -187,16 +238,14 @@ in
# TODO: add defaultText # TODO: add defaultText
default = [ default = [
{ {
port = 8008; path = "${cfg.socketDir}/matrix-synapse.sock";
bind_addresses = [ "127.0.0.1" ];
resources = [ resources = [
{ names = [ "client" ]; compress = true; } { names = [ "client" ]; compress = true; }
{ names = [ "federation" ]; compress = false; } { names = [ "federation" ]; compress = false; }
]; ];
} }
(mkIf (wcfg.instances != { }) { (mkIf (wcfg.instances != { }) {
port = 9093; path = "${cfg.socketDir}/matrix-synapse-replication.sock";
bind_addresses = [ "127.0.0.1" ];
resources = [ resources = [
{ names = [ "replication" ]; } { names = [ "replication" ]; }
]; ];
@@ -338,6 +387,15 @@ in
}; };
config = mkIf cfg.enable { config = mkIf cfg.enable {
assertions = [ ]
++ (map (l: {
assertion = l.path == null -> (l.bind_addresses != [ ] && l.port != null);
message = "Some listeners are missing either a socket path or a bind_address + port to listen on";
}) cfg.settings.listeners);
warnings = [ ] ++ lib.optional cfg.enableSlidingSync
"the option services.matrix-synapse-next.enableSlidingSync no longer has any effect (and is enabled by default)";
users.users.matrix-synapse = { users.users.matrix-synapse = {
group = "matrix-synapse"; group = "matrix-synapse";
home = cfg.dataDir; home = cfg.dataDir;
@@ -369,46 +427,31 @@ in
wantedBy = [ "matrix-synapse.target" ]; wantedBy = [ "matrix-synapse.target" ];
preStart = let preStart = let
flags = lib.cli.toGNUCommandLineShell {} { flags = lib.cli.toCommandLineShellGNU {} {
config-path = [ matrix-synapse-common-config ] ++ cfg.extraConfigFiles; config-path = [ matrix-synapse-common-config ] ++ cfg.extraConfigFiles;
keys-directory = cfg.dataDir; keys-directory = cfg.dataDir;
generate-keys = true; generate-keys = true;
}; };
in "${cfg.package}/bin/synapse_homeserver ${flags}"; in "${cfg.package}/bin/synapse_homeserver ${flags}";
environment.PYTHONPATH =
lib.makeSearchPathOutput "lib" cfg.package.python.sitePackages [ pluginsEnv ];
serviceConfig = { serviceConfig = {
Type = "notify"; Type = "notify";
User = "matrix-synapse"; User = "matrix-synapse";
Group = "matrix-synapse"; Group = "matrix-synapse";
Slice = "system-matrix-synapse.slice"; Slice = "system-matrix-synapse.slice";
WorkingDirectory = cfg.dataDir; WorkingDirectory = cfg.dataDir;
StateDirectory = "matrix-synapse";
RuntimeDirectory = "matrix-synapse";
ExecStart = let ExecStart = let
flags = lib.cli.toGNUCommandLineShell {} { flags = lib.cli.toCommandLineShellGNU {} {
config-path = [ matrix-synapse-common-config ] ++ cfg.extraConfigFiles; config-path = [ matrix-synapse-common-config ] ++ cfg.extraConfigFiles;
keys-directory = cfg.dataDir; keys-directory = cfg.dataDir;
}; };
in "${cfg.package}/bin/synapse_homeserver ${flags}"; in "${wrapped}/bin/synapse_homeserver ${flags}";
ExecReload = "${pkgs.utillinux}/bin/kill -HUP $MAINPID"; ExecReload = "${lib.getExe' pkgs.coreutils "kill"} -HUP $MAINPID";
Restart = "on-failure"; Restart = "on-failure";
}; };
}; };
}; };
services.matrix-synapse-next.settings.extra_well_known_client_content."org.matrix.msc3575.proxy" = mkIf cfg.enableSlidingSync {
url = "https://${config.services.matrix-synapse.sliding-sync.publicBaseUrl}";
};
services.matrix-synapse.sliding-sync = mkIf cfg.enableSlidingSync {
enable = true;
enableNginx = lib.mkDefault cfg.enableNginx;
publicBaseUrl = lib.mkDefault "slidingsync.${cfg.settings.server_name}";
settings = {
SYNCV3_SERVER = lib.mkDefault "https://${cfg.public_baseurl}";
SYNCV3_PROM = lib.mkIf cfg.settings.enable_metrics (lib.mkDefault "127.0.0.1:9001");
};
};
}; };
} }

View File

@@ -2,13 +2,10 @@
let let
cfg = config.services.matrix-synapse-next; cfg = config.services.matrix-synapse-next;
getWorkersOfType = type: lib.filterAttrs (_: w: w.type == type) cfg.workers.instances; matrix-lib = (import ../lib.nix { inherit lib; });
isListenerType = type: listener: lib.lists.any (r: lib.lists.any (n: n == type) r.names) listener.resources;
firstListenerOfType = type: worker: lib.lists.findFirst (isListenerType type) (throw "No federation endpoint on receiver") worker.settings.worker_listeners; workerUpstreams = matrix-lib.mapWorkersToUpstreamsByType cfg.workers.instances;
wAddressOfType = type: w: lib.lists.findFirst (_: true) (throw "No address in receiver") (firstListenerOfType type w).bind_addresses; listenerUpstreams = matrix-lib.mapListenersToUpstreamsByType cfg.settings.listeners;
wPortOfType = type: w: (firstListenerOfType type w).port;
wSocketAddressOfType = type: w: "${wAddressOfType type w}:${builtins.toString (wPortOfType type w)}";
generateSocketAddresses = type: workers: lib.mapAttrsToList (_: w: "${wSocketAddressOfType type w}") workers;
in in
{ {
config = lib.mkIf cfg.enableNginx { config = lib.mkIf cfg.enableNginx {
@@ -138,24 +135,17 @@ in
''; '';
services.nginx.upstreams.synapse_master.servers = let services.nginx.upstreams.synapse_master.servers = let
isMainListener = l: isListenerType "client" l && isListenerType "federation" l; mainListeners = builtins.intersectAttrs
firstMainListener = lib.findFirst isMainListener (listenerUpstreams.client.http or { })
(throw "No catch-all listener configured") cfg.settings.listeners; (listenerUpstreams.federation.http or { });
address = lib.findFirst (_: true) (throw "No address in main listener") firstMainListener.bind_addresses; in
port = firstMainListener.port; assert lib.assertMsg (mainListeners != { })
socketAddress = "${address}:${builtins.toString port}"; "No catch-all listener configured, or listener is not bound to an address";
in { mainListeners;
"${socketAddress}" = { };
};
services.nginx.upstreams.synapse_worker_federation = { services.nginx.upstreams.synapse_worker_federation = {
servers = let servers = workerUpstreams.fed-receiver.federation.http or config.services.nginx.upstreams.synapse_master.servers;
fedReceivers = getWorkersOfType "fed-receiver";
socketAddresses = generateSocketAddresses "federation" fedReceivers;
in if fedReceivers != { } then
lib.genAttrs socketAddresses (_: { })
else config.services.nginx.upstreams.synapse_master.servers;
extraConfig = '' extraConfig = ''
ip_hash; ip_hash;
''; '';
@@ -163,12 +153,7 @@ in
services.nginx.upstreams.synapse_worker_initial_sync = { services.nginx.upstreams.synapse_worker_initial_sync = {
servers = let servers = workerUpstreams.initial-sync.client.http or config.services.nginx.upstreams.synapse_master.servers;
initialSyncers = getWorkersOfType "initial-sync";
socketAddresses = generateSocketAddresses "client" initialSyncers;
in if initialSyncers != { } then
lib.genAttrs socketAddresses (_: { })
else config.services.nginx.upstreams.synapse_master.servers;
extraConfig = '' extraConfig = ''
hash $mxid_localpart consistent; hash $mxid_localpart consistent;
''; '';
@@ -176,12 +161,7 @@ in
services.nginx.upstreams.synapse_worker_normal_sync = { services.nginx.upstreams.synapse_worker_normal_sync = {
servers = let servers = workerUpstreams.normal-sync.client.http or config.services.nginx.upstreams.synapse_master.servers;
normalSyncers = getWorkersOfType "normal-sync";
socketAddresses = generateSocketAddresses "client" normalSyncers;
in if normalSyncers != { } then
lib.genAttrs socketAddresses (_: { })
else config.services.nginx.upstreams.synapse_master.servers;
extraConfig = '' extraConfig = ''
hash $mxid_localpart consistent; hash $mxid_localpart consistent;
''; '';
@@ -189,12 +169,7 @@ in
services.nginx.upstreams.synapse_worker_user-dir = { services.nginx.upstreams.synapse_worker_user-dir = {
servers = let servers = workerUpstreams.user-dir.client.http or config.services.nginx.upstreams.synapse_master.servers;
workers = getWorkersOfType "user-dir";
socketAddresses = generateSocketAddresses "client" workers;
in if workers != { } then
lib.genAttrs socketAddresses (_: { })
else config.services.nginx.upstreams.synapse_master.servers;
}; };
services.nginx.virtualHosts."${cfg.public_baseurl}" = { services.nginx.virtualHosts."${cfg.public_baseurl}" = {

View File

@@ -1,6 +1,6 @@
{ matrix-synapse-common-config, { matrix-synapse-common-config,
matrix-lib, matrix-lib,
pluginsEnv, wrapped,
throw', throw',
format format
}: }:
@@ -86,10 +86,17 @@ in {
}; };
port = mkOption { port = mkOption {
type = types.port; type = with types; nullOr port;
default = null;
description = "The TCP port to bind to"; description = "The TCP port to bind to";
}; };
path = mkOption {
type = with types; nullOr path;
default = null;
description = "The UNIX socket to bind to";
};
bind_addresses = mkOption { bind_addresses = mkOption {
type = with types; listOf str; type = with types; listOf str;
description = "A list of local addresses to listen on"; description = "A list of local addresses to listen on";
@@ -161,7 +168,7 @@ in {
}; };
in { in {
mainReplicationHost = mkOption { mainReplicationHost = mkOption {
type = types.str; type = with types; nullOr str;
default = let default = let
host = (matrix-lib.connectionInfo mainReplicationListener).host; host = (matrix-lib.connectionInfo mainReplicationListener).host;
in in
@@ -174,18 +181,32 @@ in {
}; };
mainReplicationPort = mkOption { mainReplicationPort = mkOption {
type = types.port; type = with types; nullOr port;
default = mainReplicationListener.port; default = mainReplicationListener.port;
# TODO: add defaultText # TODO: add defaultText
description = "Port for the main synapse instance's replication listener"; description = "Port for the main synapse instance's replication listener";
}; };
mainReplicationPath = mkOption {
type = with types; nullOr path;
default = mainReplicationListener.path;
# TODO: add defaultText
description = "Path to the UNIX socket of the main synapse instance's replication listener";
};
defaultListenerAddress = mkOption { defaultListenerAddress = mkOption {
type = types.str; type = types.str;
default = "127.0.0.1"; default = "127.0.0.1";
description = "The default listener address for the worker"; description = "The default listener address for the worker";
}; };
workersUsePath = mkOption {
type = types.bool;
description = "Whether to enable UNIX sockets for all automatically generated workers";
default = true;
example = false;
};
workerStartingPort = mkOption { workerStartingPort = mkOption {
type = types.port; type = types.port;
description = "What port should the automatically configured workers start enumerating from"; description = "What port should the automatically configured workers start enumerating from";
@@ -233,22 +254,32 @@ in {
}; };
config = { config = {
assertions = [ ]
++ (lib.concatMap (worker:
(map (l: {
assertion = l.path == null -> (l.bind_addresses != [ ] && l.port != null);
message = "At least one worker listener is missing either a socket path or a bind_address + port to listen on";
}) worker.settings.worker_listeners)
) (lib.attrValues wcfg.instances));
services.matrix-synapse-next.settings = { services.matrix-synapse-next.settings = {
federation_sender_instances = federation_sender_instances =
lib.genList (i: "auto-fed-sender${toString (i + 1)}") wcfg.federationSenders; lib.genList (i: "auto-fed-sender${toString (i + 1)}") wcfg.federationSenders;
instance_map = (lib.mkIf (cfg.workers.instances != { }) ({ instance_map = (lib.mkIf (cfg.workers.instances != { }) ({
main = let main = if wcfg.mainReplicationPath != null then {
host = lib.head mainReplicationListener.bind_addresses; path = wcfg.mainReplicationPath;
in { } else {
host = if builtins.elem host [ "0.0.0.0" "::"] then "127.0.0.1" else host; host = wcfg.mainReplicationHost;
port = mainReplicationListener.port; port = wcfg.mainReplicationPort;
}; };
} // genAttrs' (lib.lists.range 1 wcfg.eventPersisters) } // genAttrs' (lib.lists.range 1 wcfg.eventPersisters)
(i: "auto-event-persist${toString i}") (i: "auto-event-persist${toString i}")
(i: let (i: let
wRL = matrix-lib.firstListenerOfType "replication" wcfg.instances."auto-event-persist${toString i}".settings.worker_listeners; wRL = matrix-lib.firstListenerOfType "replication" wcfg.instances."auto-event-persist${toString i}".settings.worker_listeners;
in matrix-lib.connectionInfo wRL))); in if wRL.path != null then {
inherit (wRL) path;
} else matrix-lib.connectionInfo wRL)));
stream_writers.events = stream_writers.events =
mkIf (wcfg.eventPersisters > 0) mkIf (wcfg.eventPersisters > 0)
@@ -260,10 +291,15 @@ in {
services.matrix-synapse-next.workers.instances = let services.matrix-synapse-next.workers.instances = let
sum = lib.foldl lib.add 0; sum = lib.foldl lib.add 0;
workerListenersWithMetrics = portOffset: workerListenersWithMetrics = portOffset: name:
lib.singleton ({ [(if wcfg.workersUsePath
port = wcfg.workerStartingPort + portOffset - 1; then {
}) path = "${cfg.socketDir}/matrix-synapse-worker-${name}.sock";
}
else {
port = wcfg.workerStartingPort + portOffset - 1;
}
)]
++ lib.optional wcfg.enableMetrics { ++ lib.optional wcfg.enableMetrics {
port = wcfg.metricsStartingPort + portOffset; port = wcfg.metricsStartingPort + portOffset;
resources = [ { names = [ "metrics" ]; } ]; resources = [ { names = [ "metrics" ]; } ];
@@ -274,7 +310,7 @@ in {
numberOfWorkers, numberOfWorkers,
portOffset ? 0, portOffset ? 0,
nameFn ? i: "auto-${type}${toString i}", nameFn ? i: "auto-${type}${toString i}",
workerListenerFn ? i: workerListenersWithMetrics (portOffset + i) workerListenerFn ? i: name: workerListenersWithMetrics (portOffset + i) name
}: genAttrs' }: genAttrs'
(lib.lists.range 1 numberOfWorkers) (lib.lists.range 1 numberOfWorkers)
nameFn nameFn
@@ -282,7 +318,7 @@ in {
isAuto = true; isAuto = true;
inherit type; inherit type;
index = i; index = i;
settings.worker_listeners = workerListenerFn i; settings.worker_listeners = workerListenerFn i (nameFn i);
}); });
workerInstances = { workerInstances = {
@@ -323,8 +359,13 @@ in {
systemd.services = let systemd.services = let
workerList = lib.mapAttrsToList lib.nameValuePair wcfg.instances; workerList = lib.mapAttrsToList lib.nameValuePair wcfg.instances;
workerConfig = worker: format.generate "matrix-synapse-worker-${worker.name}-config.yaml" workerConfig = worker:
(worker.value.settings // { worker_name = worker.name; }); format.generate "matrix-synapse-worker-${worker.name}-config.yaml"
(worker.value.settings // {
worker_name = worker.name;
worker_listeners =
map (lib.filterAttrsRecursive (_: v: v != null)) worker.value.settings.worker_listeners;
});
in builtins.listToAttrs (lib.flip map workerList (worker: { in builtins.listToAttrs (lib.flip map workerList (worker: {
name = "matrix-synapse-worker-${worker.name}"; name = "matrix-synapse-worker-${worker.name}";
value = { value = {
@@ -333,17 +374,14 @@ in {
wantedBy = [ "matrix-synapse.target" ]; wantedBy = [ "matrix-synapse.target" ];
after = [ "matrix-synapse.service" ]; after = [ "matrix-synapse.service" ];
requires = [ "matrix-synapse.service" ]; requires = [ "matrix-synapse.service" ];
environment = {
PYTHONPATH = lib.makeSearchPathOutput "lib" cfg.package.python.sitePackages [
pluginsEnv
];
};
serviceConfig = { serviceConfig = {
Type = "notify"; Type = "notify";
User = "matrix-synapse"; User = "matrix-synapse";
Group = "matrix-synapse"; Group = "matrix-synapse";
Slice = "system-matrix-synapse.slice"; Slice = "system-matrix-synapse.slice";
WorkingDirectory = cfg.dataDir; WorkingDirectory = cfg.dataDir;
RuntimeDirectory = "matrix-synapse";
StateDirectory = "matrix-synapse";
ExecStartPre = pkgs.writers.writeBash "wait-for-synapse" '' ExecStartPre = pkgs.writers.writeBash "wait-for-synapse" ''
# From https://md.darmstadt.ccc.de/synapse-at-work # From https://md.darmstadt.ccc.de/synapse-at-work
while ! systemctl is-active -q matrix-synapse.service; do while ! systemctl is-active -q matrix-synapse.service; do
@@ -351,11 +389,11 @@ in {
done done
''; '';
ExecStart = let ExecStart = let
flags = lib.cli.toGNUCommandLineShell {} { flags = lib.cli.toCommandLineShellGNU {} {
config-path = [ matrix-synapse-common-config (workerConfig worker) ] ++ cfg.extraConfigFiles; config-path = [ matrix-synapse-common-config (workerConfig worker) ] ++ cfg.extraConfigFiles;
keys-directory = cfg.dataDir; keys-directory = cfg.dataDir;
}; };
in "${cfg.package}/bin/synapse_worker ${flags}"; in "${wrapped}/bin/synapse_worker ${flags}";
}; };
}; };
})); }));

4
tests/default.nix Normal file
View File

@@ -0,0 +1,4 @@
{ nixpkgs, pkgs, matrix-lib, ... }:
{
nginx-pipeline-eval = pkgs.callPackage ./nginx-pipeline { inherit nixpkgs matrix-lib; };
}

View File

@@ -0,0 +1,53 @@
{ nixpkgs, lib, matrix-lib, writeText, ... }:
let
nixosConfig = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
../../module.nix
{
system.stateVersion = "25.11";
boot.isContainer = true;
services.matrix-synapse-next = {
enable = true;
enableNginx = true;
workers = {
enableMetrics = true;
federationSenders = 3;
federationReceivers = 3;
initialSyncers = 1;
normalSyncers = 1;
eventPersisters = 1;
useUserDirectoryWorker = true;
instances.auto-fed-receiver1.settings.worker_listeners = [
{
bind_addresses = [
"127.0.0.2"
];
port = 1337;
resources = [
{ compress = false;
names = [ "federation" ];
}
];
}
];
};
settings.server_name = "example.com";
};
}
];
};
inherit (nixosConfig.config.services.matrix-synapse-next.workers) instances;
in
writeText "matrix-synapse-next-nginx-pipeline-test.txt" ''
${(lib.generators.toPretty {}) instances}
====================================================
${(lib.generators.toPretty {}) (matrix-lib.mapWorkersToUpstreamsByType instances)}
''