0
2
mirror of https://github.com/dali99/nixos-matrix-modules.git synced 2026-04-28 17:27:03 +02:00

Compare commits

...

40 Commits

Author SHA1 Message Date
danio 82959f612f Merge pull request #10 from h7x4/additional-nixpkgs-2511-stuff
Additional fixes for 25.11
2025-12-08 18:16:53 +01:00
oysteikt 51665e27e2 tests/nginx-pipeline: move to checks, fix nix flake show 2025-12-09 01:39:12 +09:00
oysteikt 700aa1b8a6 flake.nix: bump nixpkgs target from 23.11 -> 25.11 2025-12-09 01:38:41 +09:00
oysteikt a82c7e2d94 treewide: toGNUCommandLineShell -> toCommandLineShellGNU 2025-12-09 01:34:24 +09:00
oysteikt 8493e635fa synapse-module: source kill from coreutils 2025-12-09 01:33:07 +09:00
danio 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
danio 19c690bb4f Remove 'saml2' from extras as it is broken 2025-12-01 01:10:31 +01:00
danio 099db715d1 synapse: Remove removed extra feature 2025-07-22 22:35:55 +02:00
danio da9dc0479f sliding-sync: remove 2025-01-02 23:34:05 +01:00
danio ff787d410c Add documentation for new sliding-sync setup and upgrade info 2024-09-27 06:21:37 +02:00
danio f8843835e2 sliding-sync: deprecate 2024-09-27 06:09:23 +02:00
danio f4e20d0360 Update README.MD 2024-08-29 10:32:38 +02:00
danio d7dc42c9bb sliding-sync: make enableAcme lib.mkDefault to match synapse 2024-06-01 11:39:05 +02:00
danio 61b366f5f6 migrate to ensureDBOwnership 2024-06-01 11:11:14 +02:00
danio 6c9b67974b fix generating multiple upstreams of same type 2024-03-13 07:39:59 +01:00
danio 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
danio d48997cfb4 generate only one upstream per worker 2024-03-13 06:22:24 +01:00
oysteikt b8d7c76a7e treewide: add support for unix sockets 2024-01-27 07:52:26 +01:00
oysteikt 19d50fae63 nginx-pipeline: add basic test 2024-01-27 07:52:26 +01:00
oysteikt 18d3b34406 nginx: refactor upstream generation 2024-01-27 07:52:25 +01:00
oysteikt 85804fce8d lib: fix bug where lib.throw does not exist 2024-01-27 07:40:40 +01:00
danio 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
danio 3f92b5f197 use nixpkgs sliding sync package 2023-12-02 09:49:03 +01:00
danio a24a5e5da4 update to 23.11 2023-12-02 09:44:45 +01:00
danio e098146571 Update README.MD 2023-10-22 03:02:59 +02:00
danio 1e370b9622 matrix-sliding-sync: 0.99.10 -> 0.99.11 2023-10-16 03:49:32 +02:00
danio 161d1ed360 document some breaking changes 2023-09-24 04:39:20 +02:00
danio 50ae1b6e57 Implement easy sliding sync setup
Co-authored-by: h7x4 <h7x4@nani.wtf>
2023-09-24 04:39:20 +02:00
danio bedede1e6d Import sliding sync from nixpkgs unstable
Co-authored-by: Sandro Jäckel <sandro.jaeckel@gmail.com>
Co-authored-by: Emily <55066419+emilylange@users.noreply.github.com>
2023-09-24 03:32:54 +02:00
danio 66ff528912 Update README.MD 2023-09-11 00:04:19 +02:00
danio 8199f88a5a Update README.MD 2023-09-11 00:03:44 +02:00
lon bf997073d9 fix: don't force enableACME to allow useACMEHost 2023-07-27 22:10:46 +02:00
danio c158a35ea2 emergency handling of deprecations 2023-07-13 04:16:00 +02:00
danio 362496f4aa move matrix-lib to let block
Else it has to be called with { } which modules cant do
2023-02-17 23:59:59 +01:00
danio cf89fa8eb9 load matrix-lib directly inside module
To enable use with non-flakes
2023-02-17 23:44:47 +01:00
danio 59e39d551d Add a license
Co-authored-by: h7x4 <h7x4@nani.wtf>
2023-02-17 01:16:02 +01:00
danio 07e95170e8 introduce matrix-lib 2023-01-20 08:11:33 +01:00
danio 5ef8873997 simplify mainReplicationListener stuff 2023-01-20 08:11:33 +01:00
danio fbee6a0c0d Merge pull request #2 from h7x4/master 2023-01-19 21:48:06 +01:00
oysteikt 2fd07f83b5 Cleaned up matrix synapse module 2023-01-19 20:53:14 +01:00
13 changed files with 1207 additions and 807 deletions
+1
View File
@@ -0,0 +1 @@
result
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020, 2022-2023 Daniel Løvbrøtte Olsen and contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+21
View File
@@ -0,0 +1,21 @@
# 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.
+7 -1
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:
``` ```
@@ -9,6 +11,10 @@ With matrix.YOURDOMAIN pointing at the server:
workers.federationSenders = 1; workers.federationSenders = 1;
workers.federationReceivers = 1; workers.federationReceivers = 1;
workers.initialSyncers = 1;
workers.normalSyncers = 1;
workers.eventPersisters = 2;
workers.useUserDirectoryWorker = true;
enableNginx = true; enableNginx = true;
@@ -31,4 +37,4 @@ With matrix.YOURDOMAIN pointing at the server:
} }
``` ```
is ~enough to get a functional matrix-server running one federation sender and one federation receiver is ~enough to get a functional matrix-server running with some workers
Generated
+26
View File
@@ -0,0 +1,26 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1764983851,
"narHash": "sha256-y7RPKl/jJ/KAP/VKLMghMgXTlvNIJMHKskl8/Uuar7o=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "d9bc5c7dceb30d8d6fafa10aeb6aa8a48c218454",
"type": "github"
},
"original": {
"id": "nixpkgs",
"ref": "nixos-25.11",
"type": "indirect"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}
+26 -3
View File
@@ -1,9 +1,32 @@
{ {
description = "NixOS modules for matrix related services"; description = "NixOS modules for matrix related services";
outputs = { self }: { inputs = {
nixosModules = { nixpkgs.url = "nixpkgs/nixos-25.11";
synapse = import ./synapse-module;
}; };
outputs = { self, nixpkgs }: {
nixosModules = {
default = import ./module.nix;
};
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;
});
}; };
} }
+80
View File
@@ -0,0 +1,80 @@
{ lib }:
rec {
# checks if given listener configuration has type as a resource
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")
ls;
# Get an attrset of the host and port from a listener
connectionInfo = l: {
host = lib.head l.bind_addresses;
port = l.port;
};
# Get an attrset of the host and port from a worker given a type
workerConnectionResource = r: w: let
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
@@ -0,0 +1,14 @@
{ 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.
'')
];
}
+254 -482
View File
@@ -1,35 +1,66 @@
{ lib, pkgs, config, ... }: { pkgs, lib, config, ... }:
let let
matrix-lib = (import ../lib.nix { inherit lib; });
cfg = config.services.matrix-synapse-next; cfg = config.services.matrix-synapse-next;
wcfg = cfg.workers; wcfg = cfg.workers;
# Used to generate proper defaultTexts.
cfgText = "config.services.matrix-synapse-next";
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"
];
}; };
genAttrs' = items: f: g: builtins.listToAttrs (builtins.map (i: lib.attrsets.nameValuePair (f i) (g i)) items); inherit (lib)
literalExpression
mkEnableOption
mkIf
mkMerge
mkOption
mkPackageOption
types;
isListenerType = type: listener: lib.lists.any (r: lib.lists.any (n: n == type) r.names) listener.resources; throw' = str: throw ''
matrix-synapse-next error:
${str}
'';
in in
{ {
imports = [ ./nginx.nix ]; imports = [
./nginx.nix
(import ./workers.nix {
inherit matrix-lib throw' format matrix-synapse-common-config wrapped;
})
];
options.services.matrix-synapse-next = { options.services.matrix-synapse-next = {
enable = lib.mkEnableOption "matrix-synapse"; enable = mkEnableOption "matrix-synapse";
package = lib.mkOption { package = mkPackageOption pkgs "matrix-synapse" {};
type = lib.types.package;
default = pkgs.matrix-synapse;
};
plugins = lib.mkOption { plugins = mkOption {
type = lib.types.listOf lib.types.package; type = types.listOf types.package;
default = [ ]; default = [ ];
example = lib.literalExample '' example = literalExpression ''
with config.services.matrix-synapse-advanced.package.plugins; [ with ${cfgText}.package.plugins; [
matrix-synapse-ldap3 matrix-synapse-ldap3
matrix-synapse-pam matrix-synapse-pam
]; ];
@@ -39,8 +70,8 @@ in
''; '';
}; };
dataDir = lib.mkOption { dataDir = mkOption {
type = lib.types.str; type = types.path;
default = "/var/lib/matrix-synapse"; default = "/var/lib/matrix-synapse";
description = '' description = ''
The directory where matrix-synapse stores its stateful data such as The directory where matrix-synapse stores its stateful data such as
@@ -48,239 +79,42 @@ in
''; '';
}; };
enableNginx = lib.mkEnableOption "Enable the synapse module managing nginx"; socketDir = mkOption {
type = types.path;
public_baseurl = lib.mkOption { default = "/run/matrix-synapse";
type = lib.types.str;
default = "matrix.${cfg.settings.server_name}";
description = "The domain where clients and such will connect (May be different from server_name if using delegation)";
};
mainLogConfig = lib.mkOption {
type = lib.types.lines;
description = "A yaml python logging config file";
default = lib.readFile ./matrix-synapse-log_config.yaml;
};
workers = let
isReplication = l: lib.lists.any (r: lib.lists.any (n: n == "replication") r.names) l.resources;
dMRL = lib.lists.findFirst isReplication
(throw "No replication listener configured!")
cfg.settings.listeners;
dMRH = lib.findFirst (x: true) (throw "Replication listener had no addresses")
dMRL.bind_addresses;
dMRP = dMRL.port;
in {
mainReplicationHost = lib.mkOption {
type = lib.types.str;
default = if builtins.elem dMRH [ "0.0.0.0" "::" ] then "127.0.0.1" else dMRH;
description = "Host of the main synapse instance's replication listener";
};
mainReplicationPort = lib.mkOption {
type = lib.types.port;
default = dMRP;
description = "Port for the main synapse instance's replication listener";
};
defaultListenerAddress = lib.mkOption {
type = lib.types.str;
default = "127.0.0.1";
description = "The default listener address for the worker";
};
workerStartingPort = lib.mkOption {
type = lib.types.port;
description = "What port should the automatically configured workers start enumerating from";
default = 8083;
};
enableMetrics = lib.mkOption {
type = lib.types.bool;
default = cfg.settings.enable_metrics;
};
metricsStartingPort = lib.mkOption {
type = lib.types.port;
default = 18083;
};
federationSenders = lib.mkOption {
type = lib.types.ints.unsigned;
description = "How many automatically configured federation senders to set up";
default = 0;
};
federationReceivers = lib.mkOption {
type = lib.types.ints.unsigned;
description = "How many automatically configured federation recievers to set up";
default = 0;
};
initialSyncers = lib.mkOption {
type = lib.types.ints.unsigned;
description = "How many automatically configured intial syncers to set up";
default = 0;
};
normalSyncers = lib.mkOption {
type = lib.types.ints.unsigned;
description = "How many automatically configured sync workers to set up";
default = 0;
};
eventPersisters = lib.mkOption {
type = lib.types.ints.unsigned;
description = "How many automatically configured event-persisters to set up";
default = 0;
};
useUserDirectoryWorker = lib.mkEnableOption "user directory worker";
instances = lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule ({config, ...}: {
options.isAuto = lib.mkOption {
type = lib.types.bool;
internal = true;
default = false;
};
options.index = lib.mkOption {
internal = true;
type = lib.types.ints.positive;
};
# The custom string type here is mainly for the name to use for the metrics of custom worker types
options.type = lib.mkOption {
type = lib.types.either (lib.types.str) (lib.types.enum [ "fed-sender" "fed-receiver" ]);
};
options.settings = let
instanceCfg = config;
inherit (instanceCfg) type isAuto;
in lib.mkOption {
type = lib.types.submodule ({config, ...}: {
freeformType = format.type;
options.worker_app = let
mapTypeApp = t: {
"fed-sender" = "synapse.app.generic_worker";
"fed-receiver" = "synapse.app.generic_worker";
"initial-sync" = "synapse.app.generic_worker";
"normal-sync" = "synapse.app.generic_worker";
"event-persist" = "synapse.app.generic_worker";
"user-dir" = "synapse.app.generic_worker";
}.${t};
defaultApp = if (!isAuto)
then "synapse.app.generic_worker"
else mapTypeApp type;
in lib.mkOption {
type = lib.types.enum [
"synapse.app.generic_worker"
"synapse.app.appservice"
"synapse.app.media_repository"
"synapse.app.user_dir"
];
description = "The type of worker application";
default = defaultApp;
};
options.worker_replication_host = lib.mkOption {
type = lib.types.str;
default = cfg.workers.mainReplicationHost;
description = "The replication listeners ip on the main synapse process";
};
options.worker_replication_http_port = lib.mkOption {
type = lib.types.port;
default = cfg.workers.mainReplicationPort;
description = "The replication listeners port on the main synapse process";
};
options.worker_listeners = lib.mkOption {
type = lib.types.listOf (lib.types.submodule {
options.type = lib.mkOption {
type = lib.types.enum [ "http" "metrics" ];
description = "The type of the listener";
default = "http";
};
options.port = lib.mkOption {
type = lib.types.port;
description = "the TCP port to bind to";
};
options.bind_addresses = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = "A list of local addresses to listen on";
default = [ cfg.workers.defaultListenerAddress ];
};
options.tls = lib.mkOption {
type = lib.types.bool;
description = "set to true to enable TLS for this listener. Will use the TLS key/cert specified in tls_private_key_path / tls_certificate_path.";
default = false;
};
options.x_forwarded = lib.mkOption {
type = lib.types.bool;
description = '' description = ''
Only valid for an 'http' listener. Set to true to use the X-Forwarded-For header as the client IP. The directory where matrix-synapse by default stores the sockets of
Useful when Synapse is behind a reverse-proxy. all listeners that bind to UNIX sockets.
''; '';
default = true;
};
options.resources = let
typeToResources = t: {
"fed-receiver" = [ "federation" ];
"fed-sender" = [ ];
"initial-sync" = [ "client" ];
"normal-sync" = [ "client" ];
"event-persist" = [ "replication" ];
"user-dir" = [ "client" ];
}.${t};
in lib.mkOption {
type = lib.types.listOf (lib.types.submodule {
options.names = lib.mkOption {
type = lib.types.listOf (lib.types.enum [ "client" "consent" "federation" "keys" "media" "metrics" "openid" "replication" "static" "webclient" ]);
description = "A list of resources to host on this port";
default = lib.optionals isAuto (typeToResources type);
};
options.compress = lib.mkOption {
type = lib.types.bool;
description = "enable HTTP compression for this resource";
default = false;
};
});
default = [{ }];
};
});
description = "Listener configuration for the worker, similar to the main synapse listener";
default = [ ];
};
});
default = { };
};
}));
default = { };
description = "Worker configuration";
example = {
"federation_sender1" = {
settings = {
worker_name = "federation_sender1";
worker_app = "synapse.app.generic_worker";
worker_replication_host = "127.0.0.1";
worker_replication_http_port = 9093;
worker_listeners = [ ];
};
};
};
};
}; };
settings = lib.mkOption { enableNginx = mkEnableOption "The synapse module managing nginx";
type = lib.types.submodule {
public_baseurl = mkOption {
type = types.str;
default = "matrix.${cfg.settings.server_name}";
defaultText =
literalExpression ''matrix.''${${cfgText}.settings.server_name}'';
description = ''
The domain where clients and such will connect.
This may be different from server_name if using delegation.
'';
};
mainLogConfig = mkOption {
type = with types; coercedTo path lib.readFile lines;
default = ./matrix-synapse-log_config.yaml;
description = "A yaml python logging config file";
};
enableSlidingSync = mkEnableOption (lib.mdDoc "automatic Sliding Sync setup at `slidingsync.<domain>`");
settings = mkOption {
type = types.submodule {
freeformType = format.type; freeformType = format.type;
options = {
options.server_name = lib.mkOption { server_name = mkOption {
type = lib.types.str; type = types.str;
description = '' description = ''
The server_name name will appear at the end of usernames and room addresses The server_name name will appear at the end of usernames and room addresses
created on this server. For example if the server_name was example.com, created on this server. For example if the server_name was example.com,
@@ -300,72 +134,123 @@ in
example = "matrix.org"; example = "matrix.org";
}; };
options.use_presence = lib.mkOption { use_presence = mkOption {
type = lib.types.bool; type = types.bool;
description = "disable presence tracking, if you're having perfomance issues this can have a big impact"; description = "Disable presence tracking, if you're having perfomance issues this can have a big impact";
default = true; default = true;
}; };
options.listeners = lib.mkOption {
type = lib.types.listOf (lib.types.submodule { listeners = mkOption {
options.port = lib.mkOption { type = types.listOf (types.submodule {
type = lib.types.port; options = {
description = "the TCP port to bind to"; 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.
:::
'';
example = 8448; example = 8448;
}; };
options.bind_addresses = lib.mkOption {
type = lib.types.listOf lib.types.str; path = mkOption {
description = "A list of local addresses to listen on"; 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'';
}; };
options.type = lib.mkOption {
type = lib.types.enum [ "http" "manhole" "metrics" "replication" ]; 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.
:::
'';
};
type = mkOption {
type = types.enum [ "http" "manhole" "metrics" "replication" ];
description = "The type of the listener"; description = "The type of the listener";
default = "http"; default = "http";
}; };
options.tls = lib.mkOption {
type = lib.types.bool; tls = mkOption {
description = "set to true to enable TLS for this listener. Will use the TLS key/cert specified in tls_private_key_path / tls_certificate_path."; type = types.bool;
description = ''
Set to true to enable TLS for this listener.
Will use the TLS key/cert specified in tls_private_key_path / tls_certificate_path.
'';
default = false; default = false;
}; };
options.x_forwarded = lib.mkOption {
type = lib.types.bool; x_forwarded = mkOption {
type = types.bool;
description = '' description = ''
Only valid for an 'http' listener. Set to true to use the X-Forwarded-For header as the client IP. Set to true to use the X-Forwarded-For header as the client IP.
Only valid for an 'http' listener.
Useful when Synapse is behind a reverse-proxy. Useful when Synapse is behind a reverse-proxy.
''; '';
default = true; default = true;
}; };
options.resources = lib.mkOption {
type = lib.types.listOf (lib.types.submodule { resources = mkOption {
options.names = lib.mkOption { type = types.listOf (types.submodule {
type = lib.types.listOf (lib.types.enum [ "client" "consent" "federation" "keys" "media" "metrics" "openid" "replication" "static" "webclient" ]); options = {
names = mkOption {
type = with types; listOf (enum [
"client"
"consent"
"federation"
"keys"
"media"
"metrics"
"openid"
"replication"
"static"
"webclient"
]);
description = "A list of resources to host on this port"; description = "A list of resources to host on this port";
}; };
options.compress = lib.mkOption {
type = lib.types.bool; compress = mkEnableOption "HTTP compression for this resource";
description = "enable HTTP compression for this resource";
default = false;
}; };
}); });
}; };
};
}); });
description = "List of ports that Synapse should listen on, their purpose and their configuration"; description = "List of ports that Synapse should listen on, their purpose and their configuration";
# 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; }
]; ];
} }
(lib.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" ]; }
]; ];
}) })
(lib.mkIf cfg.settings.enable_metrics { (mkIf cfg.settings.enable_metrics {
port = 9000; port = 9000;
bind_addresses = [ "127.0.0.1" ]; bind_addresses = [ "127.0.0.1" ];
resources = [ resources = [
@@ -375,8 +260,8 @@ in
]; ];
}; };
options.federation_ip_range_blacklist = lib.mkOption { federation_ip_range_blacklist = mkOption {
type = lib.types.listOf lib.types.str; type = types.listOf types.str;
description = '' description = ''
Prevent federation requests from being sent to the following Prevent federation requests from being sent to the following
blacklist IP address CIDR ranges. If this option is not specified, or blacklist IP address CIDR ranges. If this option is not specified, or
@@ -394,62 +279,55 @@ in
"fc00::/7" "fc00::/7"
]; ];
}; };
options.log_config = lib.mkOption {
type = lib.types.path; log_config = mkOption {
type = types.path;
description = '' description = ''
A yaml python logging config file as described by A yaml python logging config file as described by
https://docs.python.org/3.7/library/logging.config.html#configuration-dictionary-schema https://docs.python.org/3.7/library/logging.config.html#configuration-dictionary-schema
''; '';
default = pkgs.writeText "log_config.yaml" cfg.mainLogConfig; default = pkgs.writeText "log_config.yaml" cfg.mainLogConfig;
defaultText = "A config file generated from ${cfgText}.mainLogConfig";
}; };
options.media_store_path = lib.mkOption { media_store_path = mkOption {
type = lib.types.path; type = types.path;
description = "Directory where uploaded images and attachments are stored"; description = "Directory where uploaded images and attachments are stored";
default = "${cfg.dataDir}/media_store"; default = "${cfg.dataDir}/media_store";
defaultText = literalExpression ''''${${cfgText}.dataDir}/media_store'';
}; };
options.max_upload_size = lib.mkOption {
type = lib.types.str; max_upload_size = mkOption {
type = types.str;
description = "The largest allowed upload size in bytes"; description = "The largest allowed upload size in bytes";
default = "50M"; default = "50M";
example = "800K";
}; };
options.enable_registration = lib.mkOption { enable_registration = mkEnableOption "registration for new users";
type = lib.types.bool; enable_metrics = mkEnableOption "collection and rendering of performance metrics";
description = "Enable registration for new users"; report_stats = mkEnableOption "reporting usage stats";
default = false;
};
options.enable_metrics = lib.mkOption { app_service_config_files = mkOption {
type = lib.types.bool; type = types.listOf types.path;
description = "Enable collection and rendering of performance metrics";
default = false;
};
options.report_stats = lib.mkOption {
type = lib.types.bool;
description = "TODO: Enable and Disable reporting usage stats";
default = false;
};
options.app_service_config_files = lib.mkOption {
type = lib.types.listOf lib.types.path;
description = "A list of application service config files to use"; description = "A list of application service config files to use";
default = []; default = [];
}; };
options.signing_key_path = lib.mkOption { signing_key_path = mkOption {
type = lib.types.path; type = types.path;
description = "Path to the signing key to sign messages with"; description = "Path to the signing key to sign messages with";
default = "${cfg.dataDir}/homeserver.signing.key"; default = "${cfg.dataDir}/homeserver.signing.key";
defaultText = literalExpression ''''${${cfgText}.dataDir}/homeserver.signing.key'';
}; };
options.trusted_key_servers = lib.mkOption { trusted_key_servers = mkOption {
type = lib.types.listOf (lib.types.submodule { type = types.listOf (types.submodule {
freeformType = format.type; freeformType = format.type;
options.server_name = lib.mkOption { options.server_name = mkOption {
type = lib.types.str; type = types.str;
description = "the name of the server. required"; description = "The name of the server. This is required.";
}; };
}); });
description = "The trusted servers to download signing keys from"; description = "The trusted servers to download signing keys from";
@@ -461,36 +339,42 @@ in
]; ];
}; };
options.federation_sender_instances = lib.mkOption { federation_sender_instances = mkOption {
type = lib.types.listOf lib.types.str; type = types.listOf types.str;
description = '' description = ''
This configuration must be shared between all federation sender workers, and if This configuration must be shared between all federation sender workers.
changed all federation sender workers must be stopped at the same time and then
started, to ensure that all instances are running with the same config (otherwise When changed, all federation sender workers must be stopped at the same time and
events may be dropped) restarted, to ensure that all instances are running with the same config.
Otherwise, events may be dropped.
''; '';
default = [ ]; default = [ ];
}; };
options.redis = lib.mkOption { redis = mkOption {
type = lib.types.submodule { type = types.submodule {
freeformType = format.type; freeformType = format.type;
options.enabled = lib.mkOption { options.enabled = mkOption {
type = lib.types.bool; type = types.bool;
description = "Enables using redis, required for worker support"; description = ''
default = (lib.lists.count (x: true) Whether to enable redis within synapse.
(lib.attrsets.attrValues cfg.workers.instances)) > 0;
This is required for worker support.
'';
default = wcfg.instances != { };
defaultText = literalExpression "${wcfgText}.instances != { }";
}; };
}; };
default = { }; default = { };
description = "configuration of redis for synapse and workers"; description = "Redis configuration for synapse and workers";
};
}; };
}; };
}; };
extraConfigFiles = lib.mkOption { extraConfigFiles = mkOption {
type = lib.types.listOf lib.types.path; type = types.listOf types.path;
default = []; default = [];
description = '' description = ''
Extra config files to include. Extra config files to include.
@@ -500,11 +384,18 @@ in
NixOPS is in use. NixOPS is in use.
''; '';
}; };
}; };
config = lib.mkIf cfg.enable (lib.mkMerge [ 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;
@@ -512,174 +403,55 @@ in
shell = "${pkgs.bash}/bin/bash"; shell = "${pkgs.bash}/bin/bash";
uid = config.ids.uids.matrix-synapse; uid = config.ids.uids.matrix-synapse;
}; };
users.groups.matrix-synapse = { users.groups.matrix-synapse = {
gid = config.ids.gids.matrix-synapse; gid = config.ids.gids.matrix-synapse;
}; };
systemd.targets.matrix-synapse = {
description = "Synapse parent target"; systemd = {
targets.matrix-synapse = {
description = "Matrix synapse parent target";
after = [ "network.target" ]; after = [ "network.target" ];
wantedBy = [ "multi-user.target" ]; wantedBy = [ "multi-user.target" ];
}; };
})
({ slices.system-matrix-synapse = {
systemd.services.matrix-synapse = { description = "Matrix synapse slice";
requires= [ "system.slice" ];
after= [ "system.slice" ];
};
services.matrix-synapse = {
description = "Synapse Matrix homeserver"; description = "Synapse Matrix homeserver";
partOf = [ "matrix-synapse.target" ]; partOf = [ "matrix-synapse.target" ];
wantedBy = [ "matrix-synapse.target" ]; wantedBy = [ "matrix-synapse.target" ];
preStart = ''
${cfg.package}/bin/synapse_homeserver \ preStart = let
${ lib.concatMapStringsSep "\n " (x: "--config-path ${x} \\") ([ matrix-synapse-common-config ] ++ cfg.extraConfigFiles) } flags = lib.cli.toCommandLineShellGNU {} {
--keys-directory ${cfg.dataDir} \ config-path = [ matrix-synapse-common-config ] ++ cfg.extraConfigFiles;
--generate-keys keys-directory = cfg.dataDir;
''; generate-keys = true;
environment.PYTHONPATH = lib.makeSearchPathOutput "lib" cfg.package.python.sitePackages [ pluginsEnv ]; };
in "${cfg.package}/bin/synapse_homeserver ${flags}";
serviceConfig = { serviceConfig = {
Type = "notify"; Type = "notify";
User = "matrix-synapse"; User = "matrix-synapse";
Group = "matrix-synapse"; Group = "matrix-synapse";
Slice = "system-matrix-synapse.slice";
WorkingDirectory = cfg.dataDir; WorkingDirectory = cfg.dataDir;
ExecStart = '' StateDirectory = "matrix-synapse";
${cfg.package}/bin/synapse_homeserver \ RuntimeDirectory = "matrix-synapse";
${ lib.concatMapStringsSep "\n " (x: "--config-path ${x} \\") ([ matrix-synapse-common-config ] ++ cfg.extraConfigFiles) } ExecStart = let
--keys-directory ${cfg.dataDir} flags = lib.cli.toCommandLineShellGNU {} {
''; config-path = [ matrix-synapse-common-config ] ++ cfg.extraConfigFiles;
ExecReload = "${pkgs.utillinux}/bin/kill -HUP $MAINPID"; keys-directory = cfg.dataDir;
};
in "${wrapped}/bin/synapse_homeserver ${flags}";
ExecReload = "${lib.getExe' pkgs.coreutils "kill"} -HUP $MAINPID";
Restart = "on-failure"; Restart = "on-failure";
}; };
}; };
})
(lib.mkMerge [
({
services.matrix-synapse-next.settings.federation_sender_instances = lib.genList (i: "auto-fed-sender${toString (i + 1)}") cfg.workers.federationSenders;
services.matrix-synapse-next.workers.instances = genAttrs' (lib.lists.range 1 cfg.workers.federationSenders)
(i: "auto-fed-sender${toString i}")
(i: {
isAuto = true; type = "fed-sender"; index = i;
settings.worker_listeners = lib.mkIf wcfg.enableMetrics [
{ port = cfg.workers.metricsStartingPort + i - 1;
resources = [ { names = [ "metrics" ]; } ];
}
];
});
})
({
services.matrix-synapse-next.workers.instances = genAttrs' (lib.lists.range 1 cfg.workers.federationReceivers)
(i: "auto-fed-receiver${toString i}")
(i: {
isAuto = true; type = "fed-receiver"; index = i;
settings.worker_listeners = [{ port = cfg.workers.workerStartingPort + i - 1; }]
++ lib.optional wcfg.enableMetrics { port = cfg.workers.metricsStartingPort + cfg.workers.federationSenders + i;
resources = [ { names = [ "metrics" ]; } ];
};
});
})
({
services.matrix-synapse-next.workers.instances = genAttrs' (lib.lists.range 1 cfg.workers.initialSyncers)
(i: "auto-initial-sync${toString i}")
(i: {
isAuto = true; type = "initial-sync"; index = i;
settings.worker_listeners = [{ port = cfg.workers.workerStartingPort + cfg.workers.federationReceivers + i - 1; }]
++ lib.optional wcfg.enableMetrics { port = cfg.workers.metricsStartingPort + cfg.workers.federationSenders + cfg.workers.federationReceivers + i;
resources = [ { names = [ "metrics" ]; } ];
};
});
})
({
services.matrix-synapse-next.workers.instances = genAttrs' (lib.lists.range 1 cfg.workers.normalSyncers)
(i: "auto-normal-sync${toString i}")
(i: {
isAuto = true; type = "normal-sync"; index = i;
settings.worker_listeners = [{ port = cfg.workers.workerStartingPort + cfg.workers.federationReceivers + cfg.workers.initialSyncers + i - 1; }]
++ lib.optional wcfg.enableMetrics { port = cfg.workers.metricsStartingPort + cfg.workers.federationSenders + cfg.workers.federationReceivers + cfg.workers.initialSyncers + i;
resources = [ { names = [ "metrics" ]; } ];
};
});
})
({
services.matrix-synapse-next.settings.instance_map = genAttrs' (lib.lists.range 1 cfg.workers.eventPersisters)
(i: "auto-event-persist${toString i}")
(i: let
isReplication = l: lib.lists.any (r: lib.lists.any (n: n == "replication") r.names) l.resources;
wRL = lib.lists.findFirst isReplication
(throw "No replication listener configured!")
cfg.workers.instances."auto-event-persist${toString i}".settings.worker_listeners;
wRH = lib.findFirst (x: true) (throw "Replication listener had no addresses")
wRL.bind_addresses;
wRP = wRL.port;
in {
host = wRH;
port = wRP;
}
);
services.matrix-synapse-next.settings.stream_writers.events = lib.mkIf (cfg.workers.eventPersisters > 0) (lib.genList (i: "auto-event-persist${toString (i + 1)}") cfg.workers.eventPersisters);
services.matrix-synapse-next.workers.instances = genAttrs' (lib.lists.range 1 cfg.workers.eventPersisters)
(i: "auto-event-persist${toString i}")
(i: {
isAuto = true; type = "event-persist"; index = i;
settings.worker_listeners = [{ port = cfg.workers.workerStartingPort + cfg.workers.federationReceivers + cfg.workers.initialSyncers + cfg.workers.normalSyncers + i - 1;}]
++ lib.optional wcfg.enableMetrics { port = cfg.workers.metricsStartingPort + cfg.workers.federationSenders + cfg.workers.federationReceivers + cfg.workers.initialSyncers + cfg.workers.normalSyncers + i;
resources = [ { names = [ "metrics" ]; } ];
};
});
})
(lib.mkIf cfg.workers.useUserDirectoryWorker {
services.matrix-synapse-next.workers.instances."auto-user-dir" = {
isAuto = true; type = "user-dir"; index = 1;
settings.worker_listeners = [{ port = cfg.workers.workerStartingPort + cfg.workers.federationReceivers + cfg.workers.initialSyncers + cfg.workers.normalSyncers + cfg.workers.eventPersisters + 1 - 1;}]
++ lib.optional wcfg.enableMetrics { port = cfg.workers.metricsStartingPort + cfg.workers.federationSenders + cfg.workers.federationReceivers + cfg.workers.initialSyncers + cfg.workers.normalSyncers + cfg.workers.eventPersisters + 1;
resources = [ { names = [ "metrics"]; } ];
};
};
services.matrix-synapse-next.settings.update_user_directory_from_worker = "auto-user-dir";
})
])
({
systemd.services = let
workerList = lib.mapAttrsToList (name: value: lib.nameValuePair name value ) cfg.workers.instances;
workerName = worker: worker.name;
workerSettings = worker: (worker.value.settings // {worker_name = (workerName worker);});
workerConfig = worker: format.generate "matrix-synapse-worker-${workerName worker}-config.yaml" (workerSettings worker);
in builtins.listToAttrs (map (worker:
{
name = "matrix-synapse-worker-${workerName worker}";
value = {
description = "Synapse Matrix Worker";
partOf = [ "matrix-synapse.target" ];
wantedBy = [ "matrix-synapse.target" ];
after = [ "matrix-synapse.service" ];
requires = [ "matrix-synapse.service" ];
environment.PYTHONPATH = lib.makeSearchPathOutput "lib" cfg.package.python.sitePackages [
pluginsEnv
];
serviceConfig = {
Type = "notify";
User = "matrix-synapse";
Group = "matrix-synapse";
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 = ''
${cfg.package}/bin/synapse_worker \
${ lib.concatMapStringsSep "\n " (x: "--config-path ${x} \\") ([ matrix-synapse-common-config (workerConfig worker) ] ++ cfg.extraConfigFiles) }
--keys-directory ${cfg.dataDir}
'';
}; };
}; };
} }
) workerList);
})
]);
}
+20 -42
View File
@@ -1,14 +1,11 @@
{ lib, pkgs, config, ...}: { pkgs, lib, config, ... }:
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 cartch-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,16 +169,11 @@ 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}" = {
enableACME = true; enableACME = lib.mkDefault true;
forceSSL = true; forceSSL = true;
locations."/_matrix" = { locations."/_matrix" = {
proxyPass = "http://$synapse_backend"; proxyPass = "http://$synapse_backend";
@@ -230,6 +205,9 @@ in
locations."/_synapse/client" = { locations."/_synapse/client" = {
proxyPass = "http://$synapse_backend"; proxyPass = "http://$synapse_backend";
}; };
locations."/.well-known/matrix" = {
proxyPass = "http://$synapse_backend";
};
}; };
}; };
} }
+401
View File
@@ -0,0 +1,401 @@
{ matrix-synapse-common-config,
matrix-lib,
wrapped,
throw',
format
}:
{ pkgs, lib, config, ... }: let
cfg = config.services.matrix-synapse-next;
wcfg = config.services.matrix-synapse-next.workers;
# Used to generate proper defaultTexts.
cfgText = "config.services.matrix-synapse-next";
wcfgText = "config.services.matrix-synapse-next.workers";
inherit (lib) types mkOption mkEnableOption mkIf mkMerge literalExpression;
mkWorkerCountOption = workerType: mkOption {
type = types.ints.unsigned;
description = "How many automatically configured ${workerType} workers to set up";
default = 0;
};
genAttrs' = items: f: g: builtins.listToAttrs (map (i: lib.nameValuePair (f i) (g i)) items);
mainReplicationListener = matrix-lib.firstListenerOfType "replication" cfg.settings.listeners;
in {
# See https://github.com/matrix-org/synapse/blob/develop/docs/workers.md for more info
options.services.matrix-synapse-next.workers = let
workerInstanceType = types.submodule ({ config, ... }: {
options = {
isAuto = mkOption {
type = types.bool;
internal = true;
default = false;
};
index = mkOption {
internal = true;
type = types.ints.positive;
};
# The custom string type here is mainly for the name to use
# for the metrics of custom worker types
type = mkOption {
type = types.str;
# TODO: add description and possibly default value?
};
settings = mkOption {
type = workerSettingsType config;
default = { };
};
};
});
workerSettingsType = instanceCfg: types.submodule {
freeformType = format.type;
options = {
worker_app = mkOption {
type = types.enum [
"synapse.app.generic_worker"
"synapse.app.appservice"
"synapse.app.media_repository"
"synapse.app.user_dir"
];
description = "The type of worker application";
default = "synapse.app.generic_worker";
};
worker_listeners = mkOption {
type = types.listOf (workerListenerType instanceCfg);
description = "Listener configuration for the worker, similar to the main synapse listener";
default = [ ];
};
};
};
workerListenerType = instanceCfg: types.submodule {
options = {
type = mkOption {
type = types.enum [ "http" "metrics" ];
description = "The type of the listener";
default = "http";
};
port = mkOption {
type = with types; nullOr port;
default = null;
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";
default = [ wcfg.defaultListenerAddress ];
defaultText = literalExpression "[ ${wcfgText}.defaultListenerAddress ]";
};
tls = mkOption {
type = types.bool;
description = ''
Whether to enable TLS for this listener.
Will use the TLS key/cert specified in tls_private_key_path / tls_certificate_path.
'';
default = false;
example = true;
};
x_forwarded = mkOption {
type = types.bool;
description = ''
Whether to use the X-Forwarded-For HTTP header as the client IP.
This option is only valid for an 'http' listener.
It is useful when Synapse is running behind a reverse-proxy.
'';
default = true;
example = false;
};
resources = let
typeToResources = t: {
"fed-receiver" = [ "federation" ];
"fed-sender" = [ ];
"initial-sync" = [ "client" ];
"normal-sync" = [ "client" ];
"event-persist" = [ "replication" ];
"user-dir" = [ "client" ];
}.${t};
in mkOption {
type = types.listOf (types.submodule {
options = {
names = mkOption {
type = with types; listOf (enum [
"client"
"consent"
"federation"
"keys"
"media"
"metrics"
"openid"
"replication"
"static"
"webclient"
]);
description = "A list of resources to host on this port";
default = lib.optionals instanceCfg.isAuto (typeToResources instanceCfg.type);
defaultText = ''
If the worker is generated from other config, the resource type will
be determined automatically.
'';
};
compress = mkEnableOption "HTTP compression for this resource";
};
});
default = [{ }];
};
};
};
in {
mainReplicationHost = mkOption {
type = with types; nullOr str;
default = let
host = (matrix-lib.connectionInfo mainReplicationListener).host;
in
# To avoid connecting to 0.0.0.0 and so on
if builtins.elem host [ "0.0.0.0" "::" ]
then "127.0.0.1"
else host;
# TODO: add defaultText
description = "Host of the main synapse instance's replication listener";
};
mainReplicationPort = mkOption {
type = with types; nullOr 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";
default = 8083;
};
enableMetrics = mkOption {
type = types.bool;
default = cfg.settings.enable_metrics;
defaultText = literalExpression "${cfgText}.settings.enable_metrics";
# TODO: add description
};
metricsStartingPort = mkOption {
type = types.port;
default = 18083;
# TODO: add description
};
federationSenders = mkWorkerCountOption "federation-sender";
federationReceivers = mkWorkerCountOption "federation-reciever";
initialSyncers = mkWorkerCountOption "initial-syncer";
normalSyncers = mkWorkerCountOption "sync";
eventPersisters = mkWorkerCountOption "event-persister";
useUserDirectoryWorker = mkEnableOption "user directory worker";
instances = mkOption {
type = types.attrsOf workerInstanceType;
default = { };
description = "Worker configuration";
example = {
"federation_sender1" = {
settings = {
worker_name = "federation_sender1";
worker_app = "synapse.app.generic_worker";
worker_replication_host = "127.0.0.1";
worker_replication_http_port = 9093;
worker_listeners = [ ];
};
};
};
};
};
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;
};
} // 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)));
stream_writers.events =
mkIf (wcfg.eventPersisters > 0)
(lib.genList (i: "auto-event-persist${toString (i + 1)}") wcfg.eventPersisters);
update_user_directory_from_worker =
mkIf wcfg.useUserDirectoryWorker "auto-user-dir";
};
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;
}
)]
++ lib.optional wcfg.enableMetrics {
port = wcfg.metricsStartingPort + portOffset;
resources = [ { names = [ "metrics" ]; } ];
};
makeWorkerInstances = {
type,
numberOfWorkers,
portOffset ? 0,
nameFn ? i: "auto-${type}${toString i}",
workerListenerFn ? i: name: workerListenersWithMetrics (portOffset + i) name
}: genAttrs'
(lib.lists.range 1 numberOfWorkers)
nameFn
(i: {
isAuto = true;
inherit type;
index = i;
settings.worker_listeners = workerListenerFn i (nameFn i);
});
workerInstances = {
"fed-sender" = wcfg.federationSenders;
"fed-receiver" = wcfg.federationReceivers;
"initial-sync" = wcfg.initialSyncers;
"normal-sync" = wcfg.normalSyncers;
"event-persist" = wcfg.eventPersisters;
} // (lib.optionalAttrs wcfg.useUserDirectoryWorker {
"user-dir" = {
numberOfWorkers = 1;
nameFn = _: "auto-user-dir";
};
});
coerceWorker = { name, value }: if builtins.isInt value then {
type = name;
numberOfWorkers = value;
} else { type = name; } // value;
# Like foldl, but keeps all intermediate values
#
# (b -> a -> b) -> b -> [a] -> [b]
scanl = f: x1: list: let
x2 = lib.head list;
x1' = f x1 x2;
in if list == [] then [] else [x1'] ++ (scanl f x1' (lib.tail list));
f = { portOffset, numberOfWorkers, ... }: x: x // { portOffset = portOffset + numberOfWorkers; };
init = { portOffset = 0; numberOfWorkers = 0; };
in lib.pipe workerInstances [
(lib.mapAttrsToList lib.nameValuePair)
(map coerceWorker)
(scanl f init)
(map makeWorkerInstances)
mkMerge
];
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;
});
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" ];
requires = [ "matrix-synapse.service" ];
serviceConfig = {
Type = "notify";
User = "matrix-synapse";
Group = "matrix-synapse";
Slice = "system-matrix-synapse.slice";
WorkingDirectory = cfg.dataDir;
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
'';
ExecStart = let
flags = lib.cli.toCommandLineShellGNU {} {
config-path = [ matrix-synapse-common-config (workerConfig worker) ] ++ cfg.extraConfigFiles;
keys-directory = cfg.dataDir;
};
in "${wrapped}/bin/synapse_worker ${flags}";
};
};
}));
};
}
+4
View File
@@ -0,0 +1,4 @@
{ nixpkgs, pkgs, matrix-lib, ... }:
{
nginx-pipeline-eval = pkgs.callPackage ./nginx-pipeline { inherit nixpkgs matrix-lib; };
}
+53
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)}
''