0
2
mirror of https://github.com/dali99/nixos-matrix-modules.git synced 2026-06-22 16:07:57 +02:00

Compare commits

..

1 Commits

14 changed files with 128 additions and 878 deletions
-2
View File
@@ -1,2 +0,0 @@
result
result-*
-21
View File
@@ -1,21 +0,0 @@
# Migrations
This is a best effort document descibing neccecary changes you might have to do when updating
## 0.8.0
`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.
-2
View File
@@ -1,5 +1,3 @@
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:
```
Generated
+10 -10
View File
@@ -1,23 +1,23 @@
{
"nodes": {
"nixpkgs": {
"nixpkgs-lib": {
"locked": {
"lastModified": 1781216227,
"narHash": "sha256-9mUW6gNwoN2SWc/l0fW4svPNOulXLl8ijqKyeSOGgJE=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "a0374025a863d007d98e3297f6aa46cc3141c2f0",
"lastModified": 1673743903,
"narHash": "sha256-sloY6KYyVOozJ1CkbgJPpZ99TKIjIvM+04V48C04sMQ=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "7555e2dfcbac1533f047021f1744ac8871150f9f",
"type": "github"
},
"original": {
"id": "nixpkgs",
"ref": "nixos-26.05",
"type": "indirect"
"owner": "nix-community",
"repo": "nixpkgs.lib",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
"nixpkgs-lib": "nixpkgs-lib"
}
}
},
+4 -26
View File
@@ -2,35 +2,13 @@
description = "NixOS modules for matrix related services";
inputs = {
nixpkgs.url = "nixpkgs/nixos-26.05";
nixpkgs-lib.url = github:nix-community/nixpkgs.lib;
};
outputs = { self, nixpkgs }: {
outputs = { self, nixpkgs-lib }: {
nixosModules = {
default = import ./module.nix;
synapse = import ./synapse-module;
};
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
synapse
synapse-workers
;
});
lib = import ./lib.nix { lib = nixpkgs-lib.lib; };
};
}
+2 -62
View File
@@ -4,9 +4,9 @@ rec {
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
firstListenerOfType = type: ls: lib.lists.findFirst (isListenerType type)
(throw "No listener with resource: ${type} configured")
(lib.throw "No listener with resource: ${type} configured")
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: {
host = lib.head l.bind_addresses;
port = l.port;
@@ -17,64 +17,4 @@ rec {
l = firstListenerOfType r w.settings.worker_listeners;
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 (_: { }))
]
))
))
];
}
-14
View File
@@ -1,14 +0,0 @@
{ lib, ... }:
{
imports = [
./synapse-module
# 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.
'')
];
}
+33 -219
View File
@@ -1,8 +1,7 @@
{ pkgs, lib, options, config, ... }:
let
{ pkgs, lib, config, ... }:
let
matrix-lib = (import ../lib.nix { inherit lib; });
opt = options.services.matrix-synapse-next;
cfg = config.services.matrix-synapse-next;
wcfg = cfg.workers;
@@ -10,43 +9,10 @@ let
cfgText = "config.services.matrix-synapse-next";
wcfgText = "config.services.matrix-synapse-next.workers";
usesCustomSigningKeyPath = cfg.settings.signing_key_path != (opt.settings.type.getSubOptions { }).signing_key_path.default;
format = pkgs.formats.yaml { };
matrix-synapse-common-config = lib.pipe cfg.settings [
(settings: settings // {
listeners = map (lib.filterAttrsRecursive (_: v: v != null)) cfg.settings.listeners;
media_store_path = "/var/lib/matrix-synapse/media_store";
})
(settings: settings // (lib.optionalAttrs usesCustomSigningKeyPath {
signing_key_path = "/run/credentials/matrix-synapse.service/signing_key";
}))
(let
filterRecursiveNull =
o:
if lib.isAttrs o then
lib.mapAttrs (_: v: filterRecursiveNull v) (lib.filterAttrs (_: v: v != null) o)
else if lib.isList o then
map filterRecursiveNull (lib.filter (v: v != null) o)
else
o;
in filterRecursiveNull)
(format.generate "matrix-synapse-common-config.yaml")
];
# 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"
];
format = pkgs.formats.yaml {};
matrix-synapse-common-config = format.generate "matrix-synapse-common-config.yaml" cfg.settings;
pluginsEnv = cfg.package.python.buildEnv.override {
extraLibs = cfg.plugins;
};
inherit (lib)
@@ -67,7 +33,7 @@ in
imports = [
./nginx.nix
(import ./workers.nix {
inherit matrix-lib throw' format matrix-synapse-common-config wrapped;
inherit matrix-lib throw' format matrix-synapse-common-config pluginsEnv;
})
];
@@ -90,14 +56,6 @@ in
'';
};
withJemalloc = mkOption {
type = types.bool;
default = true;
description = ''
Whether to preload jemalloc to reduce memory fragmentation and overall usage.
'';
};
dataDir = mkOption {
type = types.path;
default = "/var/lib/matrix-synapse";
@@ -107,15 +65,6 @@ 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";
public_baseurl = mkOption {
@@ -135,8 +84,6 @@ in
description = "A yaml python logging config file";
};
enableSlidingSync = mkEnableOption "automatic Sliding Sync setup at `slidingsync.<domain>`";
settings = mkOption {
type = types.submodule {
freeformType = format.type;
@@ -172,42 +119,14 @@ in
type = types.listOf (types.submodule {
options = {
port = mkOption {
type = with types; nullOr types.port;
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.
:::
'';
type = types.port;
description = "The TCP port to bind to";
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 {
type = types.listOf types.str;
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.
:::
'';
description = "A list of local addresses to listen on";
};
type = mkOption {
@@ -266,14 +185,16 @@ in
# TODO: add defaultText
default = [
{
path = "${cfg.socketDir}/matrix-synapse.sock";
port = 8008;
bind_addresses = [ "127.0.0.1" ];
resources = [
{ names = [ "client" ]; compress = true; }
{ names = [ "federation" ]; compress = false; }
];
}
(mkIf (wcfg.instances != { }) {
path = "${cfg.socketDir}/matrix-synapse-replication.sock";
port = 9093;
bind_addresses = [ "127.0.0.1" ];
resources = [
{ names = [ "replication" ]; }
];
@@ -288,30 +209,6 @@ in
];
};
database.name = mkOption {
type = types.enum [ "psycopg2" ];
default = "psycopg2";
description = ''
The database engine name. Hardcoded to psycopg2, this module is not designed for use with sqlite.
'';
};
database.args.database = mkOption {
type = types.str;
default = "matrix-synapse";
description = ''
Name of the database.
'';
};
database.args.user = mkOption {
type = types.nullOr types.str;
default = "matrix-synapse";
description = ''
Username to use when connecting to postgresql.
'';
};
federation_ip_range_blacklist = mkOption {
type = types.listOf types.str;
description = ''
@@ -439,19 +336,11 @@ in
};
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 = {
group = "matrix-synapse";
home = "/var/lib/matrix-synapse";
home = cfg.dataDir;
createHome = true;
shell = "${pkgs.bash}/bin/bash";
uid = config.ids.uids.matrix-synapse;
};
@@ -462,8 +351,7 @@ in
systemd = {
targets.matrix-synapse = {
description = "Matrix synapse parent target";
after = [ "network-online.target" ];
requires = [ "network-online.target" ];
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
};
@@ -473,110 +361,36 @@ in
after= [ "system.slice" ];
};
tmpfiles.settings."10-matrix-synapse" = {
"${cfg.dataDir}".d = lib.mkIf (cfg.dataDir != "/var/lib/matrix-synapse") {
user = "matrix-synapse";
group = "matrix-synapse";
mode = "0700";
};
"${cfg.settings.media_store_path}".d = lib.mkIf (cfg.settings.media_store_path != "/var/lib/matrix-synapse/media_store") {
user = "matrix-synapse";
group = "matrix-synapse";
mode = "0700";
};
};
services.matrix-synapse = {
description = "Synapse Matrix homeserver";
partOf = [ "matrix-synapse.target" ];
wantedBy = [ "matrix-synapse.target" ];
after = lib.mkIf (config.systemd.tmpfiles.settings."10-matrix-synapse" != { }) [
"systemd-tmpfiles-setup.service"
"systemd-tmpfiles-resetup.service"
];
environment = lib.optionalAttrs cfg.withJemalloc {
LD_PRELOAD = "${pkgs.jemalloc}/lib/libjemalloc.so";
PYTHONMALLOC = "malloc";
};
preStart = let
flags = lib.cli.toGNUCommandLineShell {} {
config-path = [ matrix-synapse-common-config ] ++ cfg.extraConfigFiles;
keys-directory = cfg.dataDir;
generate-keys = true;
};
in "${cfg.package}/bin/synapse_homeserver ${flags}";
environment.PYTHONPATH =
lib.makeSearchPathOutput "lib" cfg.package.python.sitePackages [ pluginsEnv ];
serviceConfig = {
Type = "notify";
User = "matrix-synapse";
Group = "matrix-synapse";
Slice = "system-matrix-synapse.slice";
Restart = "always";
RestartSec = 3;
WorkingDirectory = "/var/lib/matrix-synapse";
StateDirectory = "matrix-synapse";
RuntimeDirectory = "matrix-synapse";
ExecStartPre = let
flags = lib.cli.toCommandLineShellGNU {} {
WorkingDirectory = cfg.dataDir;
ExecStart = let
flags = lib.cli.toGNUCommandLineShell {} {
config-path = [ matrix-synapse-common-config ] ++ cfg.extraConfigFiles;
keys-directory = "/var/lib/matrix-synapse";
generate-keys = true;
keys-directory = cfg.dataDir;
};
in "${cfg.package}/bin/synapse_homeserver ${flags}";
ExecStart = let
flags = lib.cli.toCommandLineShellGNU {} {
config-path = [ matrix-synapse-common-config ] ++ cfg.extraConfigFiles;
keys-directory = "/var/lib/matrix-synapse";
};
in "${wrapped}/bin/synapse_homeserver ${flags}";
ExecReload = "${lib.getExe' pkgs.coreutils "kill"} -HUP $MAINPID";
AmbientCapabilities = [ "" ];
CapabilityBoundingSet = [ "" ];
LockPersonality = true;
NoNewPrivileges = true;
PrivateDevices = true;
PrivateTmp = true;
PrivateUsers = true;
ProcSubset = "pid";
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
ProtectSystem = "strict";
BindPaths = (lib.optionals (cfg.dataDir != "/var/lib/matrix-synapse") [
"${cfg.dataDir}:/var/lib/matrix-synapse"
]) ++ (lib.optionals (cfg.settings.media_store_path != "${cfg.dataDir}/media_store") [
"${cfg.settings.media_store_path}:/var/lib/matrix-synapse/media_store"
]);
ReadWritePaths = lib.pipe cfg.settings.listeners [
(lib.filter (listener: listener.path != null))
(map (listener: dirOf listener.path))
(lib.filter (path: path != "/run/matrix-synapse"))
lib.uniqueStrings
];
LoadCredential = lib.mkIf usesCustomSigningKeyPath [
"signing_key:${cfg.settings.signing_key_path}"
];
RemoveIPC = true;
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_UNIX"
];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SocketBindAllow = lib.catAttrs "port" cfg.settings.listeners;
SocketBindDeny = "any";
SystemCallArchitectures = "native";
SystemCallFilter = [
"@system-service"
"~@resources"
"~@privileged"
];
UMask = "0027";
ExecReload = "${pkgs.utillinux}/bin/kill -HUP $MAINPID";
Restart = "on-failure";
};
};
};
+42 -34
View File
@@ -2,10 +2,13 @@
let
cfg = config.services.matrix-synapse-next;
matrix-lib = (import ../lib.nix { inherit lib; });
workerUpstreams = matrix-lib.mapWorkersToUpstreamsByType cfg.workers.instances;
listenerUpstreams = matrix-lib.mapListenersToUpstreamsByType cfg.settings.listeners;
getWorkersOfType = type: lib.filterAttrs (_: w: w.type == type) cfg.workers.instances;
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;
wAddressOfType = type: w: lib.lists.findFirst (_: true) (throw "No address in receiver") (firstListenerOfType type w).bind_addresses;
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
{
config = lib.mkIf cfg.enableNginx {
@@ -24,7 +27,6 @@ in
~^/_matrix/client/(api/v1|r0|v3)/rooms/[^/]+/initialSync$ synapse_initial_sync;
# Federation requests
~^/_matrix/federation/v1/version$ synapse_federation;
~^/_matrix/federation/v1/event/ synapse_federation;
~^/_matrix/federation/v1/state/ synapse_federation;
~^/_matrix/federation/v1/state_ids/ synapse_federation;
@@ -36,8 +38,6 @@ in
~^/_matrix/federation/v1/make_leave/ synapse_federation;
~^/_matrix/federation/(v1|v2)/send_join/ synapse_federation;
~^/_matrix/federation/(v1|v2)/send_leave/ synapse_federation;
~^/_matrix/federation/v1/make_knock/ synapse_federation;
~^/_matrix/federation/v1/send_knock/ synapse_federation;
~^/_matrix/federation/(v1|v2)/invite/ synapse_federation;
~^/_matrix/federation/v1/event_auth/ synapse_federation;
~^/_matrix/federation/v1/timestamp_to_event/ synapse_federation;
@@ -59,23 +59,17 @@ in
~^/_matrix/client/v1/rooms/.*/hierarchy$ synapse_client_interaction;
~^/_matrix/client/(v1|unstable)/rooms/.*/relations/ synapse_client_interaction;
~^/_matrix/client/v1/rooms/.*/threads$ synapse_client_interaction;
~^/_matrix/client/unstable/org.matrix.msc2716/rooms/.*/batch_send$ synapse_client_interaction;
~^/_matrix/client/unstable/im.nheko.summary/rooms/.*/summary$ synapse_client_interaction;
~^/_matrix/client/(r0|v3|unstable)/account/3pid$ synapse_client_interaction;
~^/_matrix/client/(r0|v3|unstable)/account/whoami$ synapse_client_interaction;
~^/_matrix/client/(r0|v3|unstable)/account/deactivate$ synapse_client_interaction;
~^/_matrix/client/(r0|v3)/delete_devices$ synapse_client_interaction;
~^/_matrix/client/(api/v1|r0|v3|unstable)/devices(/|$) synapse_client_interaction;
~^/_matrix/client/(r0|v3|unstable)/devices$ synapse_client_interaction;
~^/_matrix/client/versions$ synapse_client_interaction;
~^/_matrix/client/(api/v1|r0|v3|unstable)/voip/turnServer$ synapse_client_interaction;
~^/_matrix/client/(api/v1|r0|v3|unstable)/rooms/.*/event/ synapse_client_interaction;
~^/_matrix/client/(api/v1|r0|v3|unstable)/joined_rooms$ synapse_client_interaction;
~^/_matrix/client/v1/rooms/.*/timestamp_to_event$ synapse_client_interaction;
~^/_matrix/client/(api/v1|r0|v3|unstable/.*)/rooms/.*/aliases synapse_client_interaction;
~^/_matrix/client/(api/v1|r0|v3|unstable)/search$ synapse_client_interaction;
~^/_matrix/client/(r0|v3|unstable)/user/.*/filter(/|$) synapse_client_interaction;
~^/_matrix/client/(api/v1|r0|v3|unstable)/directory/room/.*$ synapse_client_interaction;
~^/_matrix/client/(r0|v3|unstable)/capabilities$ synapse_client_interaction;
~^/_matrix/client/(r0|v3|unstable)/notifications$ synapse_client_interaction;
# Encryption requests
~^/_matrix/client/(r0|v3|unstable)/keys/query$ synapse_client_encryption;
@@ -83,15 +77,11 @@ in
~^/_matrix/client/(r0|v3|unstable)/keys/claim$ synapse_client_encryption;
~^/_matrix/client/(r0|v3|unstable)/room_keys/ synapse_client_encryption;
~^/_matrix/client/(r0|v3|unstable)/keys/upload/ synapse_client_encryption;
~^/_matrix/client/(api/v1|r0|v3|unstable)/keys/device_signing/upload$ synapse_client_encryption;
~^/_matrix/client/(api/v1|r0|v3|unstable)/keys/signatures/upload$ synapse_client_encryption;
# Registration/login requests
~^/_matrix/client/(api/v1|r0|v3|unstable)/login$ synapse_client_login;
~^/_matrix/client/(r0|v3|unstable)/register$ synapse_client_login;
~^/_matrix/client/(r0|v3|unstable)/register/available$ synapse_client_login;
~^/_matrix/client/v1/register/m.login.registration_token/validity$ synapse_client_login;
~^/_matrix/client/(r0|v3|unstable)/password_policy$ synapse_client_login;
# Event sending requests
~^/_matrix/client/(api/v1|r0|v3|unstable)/rooms/.*/redact synapse_client_transaction;
@@ -99,7 +89,6 @@ in
~^/_matrix/client/(api/v1|r0|v3|unstable)/rooms/.*/state/ synapse_client_transaction;
~^/_matrix/client/(api/v1|r0|v3|unstable)/rooms/.*/(join|invite|leave|ban|unban|kick)$ synapse_client_transaction;
~^/_matrix/client/(api/v1|r0|v3|unstable)/join/ synapse_client_transaction;
~^/_matrix/client/(api/v1|r0|v3|unstable)/knock/ synapse_client_transaction;
~^/_matrix/client/(api/v1|r0|v3|unstable)/profile/ synapse_client_transaction;
# Account data requests
@@ -149,17 +138,24 @@ in
'';
services.nginx.upstreams.synapse_master.servers = let
mainListeners = builtins.intersectAttrs
(listenerUpstreams.client.http or { })
(listenerUpstreams.federation.http or { });
in
assert lib.assertMsg (mainListeners != { })
"No catch-all listener configured, or listener is not bound to an address";
mainListeners;
isMainListener = l: isListenerType "client" l && isListenerType "federation" l;
firstMainListener = lib.findFirst isMainListener
(throw "No catch-all listener configured") cfg.settings.listeners;
address = lib.findFirst (_: true) (throw "No address in main listener") firstMainListener.bind_addresses;
port = firstMainListener.port;
socketAddress = "${address}:${builtins.toString port}";
in {
"${socketAddress}" = { };
};
services.nginx.upstreams.synapse_worker_federation = {
servers = workerUpstreams.fed-receiver.federation.http or config.services.nginx.upstreams.synapse_master.servers;
servers = let
fedReceivers = getWorkersOfType "fed-receiver";
socketAddresses = generateSocketAddresses "federation" fedReceivers;
in if fedReceivers != { } then
lib.genAttrs socketAddresses (_: { })
else config.services.nginx.upstreams.synapse_master.servers;
extraConfig = ''
ip_hash;
'';
@@ -167,7 +163,12 @@ in
services.nginx.upstreams.synapse_worker_initial_sync = {
servers = workerUpstreams.initial-sync.client.http or config.services.nginx.upstreams.synapse_master.servers;
servers = let
initialSyncers = getWorkersOfType "initial-sync";
socketAddresses = generateSocketAddresses "client" initialSyncers;
in if initialSyncers != { } then
lib.genAttrs socketAddresses (_: { })
else config.services.nginx.upstreams.synapse_master.servers;
extraConfig = ''
hash $mxid_localpart consistent;
'';
@@ -175,7 +176,12 @@ in
services.nginx.upstreams.synapse_worker_normal_sync = {
servers = workerUpstreams.normal-sync.client.http or config.services.nginx.upstreams.synapse_master.servers;
servers = let
normalSyncers = getWorkersOfType "normal-sync";
socketAddresses = generateSocketAddresses "client" normalSyncers;
in if normalSyncers != { } then
lib.genAttrs socketAddresses (_: { })
else config.services.nginx.upstreams.synapse_master.servers;
extraConfig = ''
hash $mxid_localpart consistent;
'';
@@ -183,7 +189,12 @@ in
services.nginx.upstreams.synapse_worker_user-dir = {
servers = workerUpstreams.user-dir.client.http or config.services.nginx.upstreams.synapse_master.servers;
servers = let
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}" = {
@@ -219,9 +230,6 @@ in
locations."/_synapse/client" = {
proxyPass = "http://$synapse_backend";
};
locations."/.well-known/matrix" = {
proxyPass = "http://$synapse_backend";
};
};
};
}
+37 -163
View File
@@ -1,11 +1,11 @@
{ matrix-synapse-common-config,
matrix-lib,
wrapped,
pluginsEnv,
throw',
format
}:
{ pkgs, lib, options, config, ... }: let
opt = options.services.matrix-synapse-next;
{ pkgs, lib, config, ... }: let
cfg = config.services.matrix-synapse-next;
wcfg = config.services.matrix-synapse-next.workers;
@@ -13,8 +13,6 @@
cfgText = "config.services.matrix-synapse-next";
wcfgText = "config.services.matrix-synapse-next.workers";
usesCustomSigningKeyPath = cfg.settings.signing_key_path != (opt.settings.type.getSubOptions { }).signing_key_path.default;
inherit (lib) types mkOption mkEnableOption mkIf mkMerge literalExpression;
mkWorkerCountOption = workerType: mkOption {
@@ -58,7 +56,7 @@ in {
workerSettingsType = instanceCfg: types.submodule {
freeformType = format.type;
options = {
worker_app = mkOption {
type = types.enum [
@@ -76,16 +74,6 @@ in {
description = "Listener configuration for the worker, similar to the main synapse listener";
default = [ ];
};
worker_log_config = mkOption {
type = types.path;
description = ''
A yaml python logging config file as described by
https://docs.python.org/3.7/library/logging.config.html#configuration-dictionary-schema
'';
default = pkgs.writeText "log_config.yaml" cfg.mainLogConfig;
defaultText = "A config file generated from ${cfgText}.mainLogConfig";
};
};
};
@@ -98,17 +86,10 @@ in {
};
port = mkOption {
type = with types; nullOr port;
default = null;
type = types.port;
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 {
type = with types; listOf str;
description = "A list of local addresses to listen on";
@@ -180,7 +161,7 @@ in {
};
in {
mainReplicationHost = mkOption {
type = with types; nullOr str;
type = types.str;
default = let
host = (matrix-lib.connectionInfo mainReplicationListener).host;
in
@@ -193,32 +174,18 @@ in {
};
mainReplicationPort = mkOption {
type = with types; nullOr port;
type = types.port;
default = mainReplicationListener.port;
# TODO: add defaultText
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 {
type = types.str;
default = "127.0.0.1";
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 {
type = types.port;
description = "What port should the automatically configured workers start enumerating from";
@@ -266,36 +233,26 @@ in {
};
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 = {
federation_sender_instances =
lib.genList (i: "auto-fed-sender${toString (i + 1)}") wcfg.federationSenders;
instance_map = (lib.mkIf (cfg.workers.instances != { }) ({
main = if wcfg.mainReplicationPath != null then {
path = wcfg.mainReplicationPath;
} else {
host = wcfg.mainReplicationHost;
port = wcfg.mainReplicationPort;
main = let
host = lib.head mainReplicationListener.bind_addresses;
in {
host = if builtins.elem host [ "0.0.0.0" "::"] then "127.0.0.1" else host;
port = mainReplicationListener.port;
};
} // genAttrs' (lib.lists.range 1 wcfg.eventPersisters)
(i: "auto-event-persist${toString i}")
(i: let
wRL = matrix-lib.firstListenerOfType "replication" wcfg.instances."auto-event-persist${toString i}".settings.worker_listeners;
in if wRL.path != null then {
inherit (wRL) path;
} else matrix-lib.connectionInfo wRL)));
in matrix-lib.connectionInfo wRL)));
stream_writers.events =
mkIf (wcfg.eventPersisters > 0)
(lib.genList (i: "auto-event-persist${toString (i + 1)}") wcfg.eventPersisters);
(lib.genList (i: "auto-event-persist${toString (i + 1)}") wcfg.eventPersisters);
update_user_directory_from_worker =
mkIf wcfg.useUserDirectoryWorker "auto-user-dir";
@@ -303,15 +260,10 @@ in {
services.matrix-synapse-next.workers.instances = let
sum = lib.foldl lib.add 0;
workerListenersWithMetrics = portOffset: name:
[(if wcfg.workersUsePath
then {
path = "${cfg.socketDir}/matrix-synapse-worker-${name}.sock";
}
else {
port = wcfg.workerStartingPort + portOffset - 1;
}
)]
workerListenersWithMetrics = portOffset:
lib.singleton ({
port = wcfg.workerStartingPort + portOffset - 1;
})
++ lib.optional wcfg.enableMetrics {
port = wcfg.metricsStartingPort + portOffset;
resources = [ { names = [ "metrics" ]; } ];
@@ -322,7 +274,7 @@ in {
numberOfWorkers,
portOffset ? 0,
nameFn ? i: "auto-${type}${toString i}",
workerListenerFn ? i: name: workerListenersWithMetrics (portOffset + i) name
workerListenerFn ? i: workerListenersWithMetrics (portOffset + i)
}: genAttrs'
(lib.lists.range 1 numberOfWorkers)
nameFn
@@ -330,7 +282,7 @@ in {
isAuto = true;
inherit type;
index = i;
settings.worker_listeners = workerListenerFn i (nameFn i);
settings.worker_listeners = workerListenerFn i;
});
workerInstances = {
@@ -371,117 +323,39 @@ in {
systemd.services = let
workerList = lib.mapAttrsToList lib.nameValuePair wcfg.instances;
workerConfig = worker:
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;
}
//
# NOTE: the workers cannot pick up creds from `/run/credentials/matrix-synapse.service/*`
(lib.optionalAttrs usesCustomSigningKeyPath {
signing_key_path = "/run/credentials/matrix-synapse-worker-${worker.name}.service/signing_key";
})
);
workerConfig = worker: format.generate "matrix-synapse-worker-${worker.name}-config.yaml"
(worker.value.settings // { worker_name = worker.name; });
in builtins.listToAttrs (lib.flip map workerList (worker: {
name = "matrix-synapse-worker-${worker.name}";
value = {
description = "Synapse Matrix Worker";
partOf = [ "matrix-synapse.target" ];
wantedBy = [ "matrix-synapse.target" ];
after = [
"matrix-synapse.service"
] ++ (lib.optionals (config.systemd.tmpfiles.settings."10-matrix-synapse" != { }) [
"systemd-tmpfiles-setup.service"
"systemd-tmpfiles-resetup.service"
]);
after = [ "matrix-synapse.service" ];
requires = [ "matrix-synapse.service" ];
environment = lib.optionalAttrs cfg.withJemalloc {
LD_PRELOAD = "${pkgs.jemalloc}/lib/libjemalloc.so";
PYTHONMALLOC = "malloc";
environment = {
PYTHONPATH = lib.makeSearchPathOutput "lib" cfg.package.python.sitePackages [
pluginsEnv
];
};
serviceConfig = {
Type = "notify";
User = "matrix-synapse";
Group = "matrix-synapse";
Slice = "system-matrix-synapse.slice";
Restart = "always";
RestartSec = 3;
WorkingDirectory = "/var/lib/matrix-synapse";
RuntimeDirectory = "matrix-synapse";
StateDirectory = "matrix-synapse";
ExecStartPre = pkgs.writers.writeBash "wait-for-synapse" ''
# From https://md.darmstadt.ccc.de/synapse-at-work
while ! systemctl is-active -q matrix-synapse.service; do
sleep 1
done
'';
WorkingDirectory = cfg.dataDir;
# ExecStartPre = pkgs.writers.writeBash "wait-for-synapse" ''
# # From https://md.darmstadt.ccc.de/synapse-at-work
# while ! systemctl is-active -q matrix-synapse.service; do
# sleep 1
# done
# '';
ExecStart = let
flags = lib.cli.toCommandLineShellGNU {} {
flags = lib.cli.toGNUCommandLineShell {} {
config-path = [ matrix-synapse-common-config (workerConfig worker) ] ++ cfg.extraConfigFiles;
keys-directory = "/var/lib/matrix-synapse";
keys-directory = cfg.dataDir;
};
in "${wrapped}/bin/synapse_worker ${flags}";
AmbientCapabilities = [ "" ];
CapabilityBoundingSet = [ "" ];
LockPersonality = true;
NoNewPrivileges = true;
PrivateDevices = true;
PrivateTmp = true;
PrivateUsers = true;
ProcSubset = "pid";
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
ProtectSystem = "strict";
BindPaths = (lib.optionals (cfg.dataDir != "/var/lib/matrix-synapse") [
"${cfg.dataDir}:/var/lib/matrix-synapse"
]) ++ (lib.optionals (cfg.settings.media_store_path != "${cfg.dataDir}/media_store") [
"${cfg.settings.media_store_path}:/var/lib/matrix-synapse/media_store"
]);
ReadWritePaths = lib.pipe cfg.settings.listeners [
(lib.filter (listener: listener.path != null))
(map (listener: dirOf listener.path))
(lib.filter (path: path != "/run/matrix-synapse"))
lib.uniqueStrings
];
LoadCredential = lib.mkIf usesCustomSigningKeyPath [
"signing_key:${cfg.settings.signing_key_path}"
];
RemoveIPC = true;
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_UNIX"
];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SocketBindAllow = lib.pipe worker.value.settings.worker_listeners [
(map (lib.filterAttrsRecursive (_: v: v != null)))
(lib.catAttrs "port")
];
SocketBindDeny = "any";
SystemCallArchitectures = "native";
SystemCallFilter = [
"@system-service"
"~@resources"
"~@privileged"
];
UMask = "0027";
in "${cfg.package}/bin/synapse_worker ${flags}";
};
};
}));
-7
View File
@@ -1,7 +0,0 @@
{ nixpkgs, pkgs, matrix-lib, ... }:
{
nginx-pipeline-eval = pkgs.callPackage ./nginx-pipeline { inherit nixpkgs matrix-lib; };
synapse = pkgs.testers.runNixOSTest ./synapse;
synapse-workers = pkgs.testers.runNixOSTest ./synapse-workers;
}
-53
View File
@@ -1,53 +0,0 @@
{ 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)}
''
-52
View File
@@ -1,52 +0,0 @@
{ pkgs, ... }:
{
name = "matrix-synapse-workers";
nodes = {
server =
{
pkgs,
nodes,
...
}:
{
imports = [
../../synapse-module
];
services.postgresql = {
enable = true;
initialScript = pkgs.writeText "synapse-init.sql" ''
CREATE ROLE "matrix-synapse" WITH LOGIN PASSWORD 'synapse';
CREATE DATABASE "matrix-synapse" WITH OWNER "matrix-synapse"
TEMPLATE template0
LC_COLLATE = "C"
LC_CTYPE = "C";
'';
};
services.matrix-synapse-next = {
enable = true;
workers.federationSenders = 1;
workers.federationReceivers = 1;
workers.initialSyncers = 1;
workers.normalSyncers = 1;
workers.eventPersisters = 1;
workers.useUserDirectoryWorker = true;
settings = {
server_name = "example.com";
database = {
args.password = "synapse";
};
};
};
services.redis.servers."".enable = true;
};
};
testScript = ''
server.wait_for_unit("matrix-synapse.target");
'';
}
-213
View File
@@ -1,213 +0,0 @@
# Modified from https://github.com/NixOS/nixpkgs/blob/nixos-26.05/nixos/tests/matrix/synapse.nix
{ pkgs, lib, ... }:
let
mailerCerts = import /${pkgs.path}/nixos/tests/common/acme/server/snakeoil-certs.nix;
mailerDomain = mailerCerts.domain;
registrationSharedSecret = "unsecure123";
testUser = "alice";
testPassword = "alicealice";
testEmail = "alice@example.com";
in
{
name = "matrix-synapse";
nodes = {
# Since 0.33.0, matrix-synapse doesn't allow underscores in server names
server =
{
pkgs,
nodes,
config,
...
}:
let
mailserverIP = nodes.mailserver.networking.primaryIPAddress;
in
{
imports = [
../../synapse-module
];
services.matrix-synapse-next = {
enable = true;
settings = {
registration_shared_secret = registrationSharedSecret;
server_name = "example.com";
public_baseurl = "https://example.com";
database = {
args.password = "synapse";
};
redis = {
enabled = true;
host = "localhost";
port = config.services.redis.servers.matrix-synapse.port;
};
email = {
smtp_host = mailerDomain;
smtp_port = 25;
require_transport_security = true;
notif_from = "matrix <matrix@${mailerDomain}>";
app_name = "Matrix";
};
listeners = [
{
port = 8448;
bind_addresses = [
"127.0.0.1"
"::1"
];
type = "http";
x_forwarded = false;
resources = [
{
names = [
"client"
];
compress = true;
}
{
names = [
"federation"
];
compress = false;
}
];
}
];
};
};
services.postgresql = {
enable = true;
# The database name and user are configured by the following options:
# - services.matrix-synapse.database_name
# - services.matrix-synapse.database_user
#
# The values used here represent the default values of the module.
initialScript = pkgs.writeText "synapse-init.sql" ''
CREATE ROLE "matrix-synapse" WITH LOGIN PASSWORD 'synapse';
CREATE DATABASE "matrix-synapse" WITH OWNER "matrix-synapse"
TEMPLATE template0
LC_COLLATE = "C"
LC_CTYPE = "C";
'';
};
services.redis.servers.matrix-synapse = {
enable = true;
port = 6380;
};
networking.extraHosts = ''
${mailserverIP} ${mailerDomain}
'';
security.pki.certificateFiles = [
mailerCerts.ca.cert
];
environment.systemPackages =
let
sendTestMailStarttls = pkgs.writeScriptBin "send-testmail-starttls" ''
#!${pkgs.python3.interpreter}
import smtplib
import ssl
ctx = ssl.create_default_context()
with smtplib.SMTP('${mailerDomain}') as smtp:
smtp.ehlo()
smtp.starttls(context=ctx)
smtp.ehlo()
smtp.sendmail('matrix@${mailerDomain}', '${testEmail}', 'Subject: Test STARTTLS\n\nTest data.')
smtp.quit()
'';
obtainTokenAndRegisterEmail =
let
# adding the email through the API is quite complicated as it involves more than one step and some
# client-side calculation
insertEmailForAlice = pkgs.writeText "alice-email.sql" ''
INSERT INTO user_threepids (user_id, medium, address, validated_at, added_at)
VALUES ('${testUser}@server', 'email', '${testEmail}', '1629149927271', '1629149927270');
'';
in
pkgs.writeScriptBin "obtain-token-and-register-email" ''
#!${pkgs.runtimeShell}
set -o errexit
set -o pipefail
set -o nounset
su postgres -c "psql -d matrix-synapse -f ${insertEmailForAlice}"
curl --fail -XPOST -v 'http://localhost:8448/_matrix/client/r0/account/password/email/requestToken' --json '${builtins.toJSON {
email = testEmail;
client_secret = "foobar";
send_attempt = 1;
}}'
'';
in
[
sendTestMailStarttls
pkgs.matrix-synapse
obtainTokenAndRegisterEmail
];
};
# test mail delivery
mailserver = args: {
security.pki.certificateFiles = [
mailerCerts.ca.cert
];
networking.firewall.enable = false;
services.postfix = {
enable = true;
enableSubmission = true;
# blackhole transport
transport = "example.com discard:silently";
settings.main = {
myhostname = "${mailerDomain}";
# open relay for subnet
mynetworks_style = "subnet";
debug_peer_level = "10";
smtpd_relay_restrictions = [
"permit_mynetworks"
"reject_unauth_destination"
];
# disable obsolete protocols, something old versions of twisted are still using
smtpd_tls_protocols = "TLSv1.3, TLSv1.2, !TLSv1.1, !TLSv1, !SSLv2, !SSLv3";
smtpd_tls_mandatory_protocols = "TLSv1.3, TLSv1.2, !TLSv1.1, !TLSv1, !SSLv2, !SSLv3";
smtpd_tls_chain_files = [
"${mailerCerts.${mailerDomain}.key}"
"${mailerCerts.${mailerDomain}.cert}"
];
};
};
};
};
testScript = ''
start_all()
mailserver.wait_for_unit("postfix.service")
server.succeed("send-testmail-starttls")
server.wait_for_unit("matrix-synapse.service")
server.wait_until_succeeds(
"curl --fail -L http://localhost:8448/"
)
server.wait_until_succeeds(
"journalctl -u matrix-synapse.service | grep -q 'Connected to redis'"
)
server.require_unit_state("postgresql.target")
server.succeed("register_new_matrix_user -u ${testUser} -p ${testPassword} -a -k ${registrationSharedSecret} 'http://localhost:8448/'")
server.succeed("obtain-token-and-register-email")
'';
}