1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7let
8 cfg = config.services.matrix-continuwuity;
9 defaultUser = "continuwuity";
10 defaultGroup = "continuwuity";
11
12 format = pkgs.formats.toml { };
13 configFile = format.generate "continuwuity.toml" cfg.settings;
14in
15{
16 meta.maintainers = with lib.maintainers; [
17 nyabinary
18 snaki
19 ];
20 options.services.matrix-continuwuity = {
21 enable = lib.mkEnableOption "continuwuity";
22
23 user = lib.mkOption {
24 type = lib.types.nonEmptyStr;
25 description = ''
26 The user {command}`continuwuity` is run as.
27 '';
28 default = defaultUser;
29 };
30
31 group = lib.mkOption {
32 type = lib.types.nonEmptyStr;
33 description = ''
34 The group {command}`continuwuity` is run as.
35 '';
36 default = defaultGroup;
37 };
38
39 extraEnvironment = lib.mkOption {
40 type = lib.types.attrsOf lib.types.str;
41 description = "Extra Environment variables to pass to the continuwuity server.";
42 default = { };
43 example = {
44 RUST_BACKTRACE = "yes";
45 };
46 };
47
48 package = lib.mkPackageOption pkgs "matrix-continuwuity" { };
49
50 settings = lib.mkOption {
51 type = lib.types.submodule {
52 freeformType = format.type;
53 options = {
54 global.server_name = lib.mkOption {
55 type = lib.types.nonEmptyStr;
56 example = "example.com";
57 description = "The server_name is the name of this server. It is used as a suffix for user and room ids.";
58 };
59 global.address = lib.mkOption {
60 type = lib.types.nullOr (lib.types.listOf lib.types.nonEmptyStr);
61 default = null;
62 example = [
63 "127.0.0.1"
64 "::1"
65 ];
66 description = ''
67 Addresses (IPv4 or IPv6) to listen on for connections by the reverse proxy/tls terminator.
68 If set to `null`, continuwuity will listen on IPv4 and IPv6 localhost.
69 Must be `null` if `unix_socket_path` is set.
70 '';
71 };
72 global.port = lib.mkOption {
73 type = lib.types.listOf lib.types.port;
74 default = [ 6167 ];
75 description = ''
76 The port(s) continuwuity will be running on.
77 You need to set up a reverse proxy in your web server (e.g. apache or nginx),
78 so all requests to /_matrix on port 443 and 8448 will be forwarded to the continuwuity
79 instance running on this port.
80 '';
81 };
82 global.unix_socket_path = lib.mkOption {
83 type = lib.types.nullOr lib.types.path;
84 default = null;
85 description = ''
86 Listen on a UNIX socket at the specified path. If listening on a UNIX socket,
87 listening on an address will be disabled. The `address` option must be set to
88 `null` (the default value). The option {option}`services.continuwuity.group` must
89 be set to a group your reverse proxy is part of.
90
91 This will automatically add a system user "continuwuity" to your system if
92 {option}`services.continuwuity.user` is left at the default, and a "continuwuity"
93 group if {option}`services.continuwuity.group` is left at the default.
94 '';
95 };
96 global.unix_socket_perms = lib.mkOption {
97 type = lib.types.ints.positive;
98 default = 660;
99 description = "The default permissions (in octal) to create the UNIX socket with.";
100 };
101 global.max_request_size = lib.mkOption {
102 type = lib.types.ints.positive;
103 default = 20000000;
104 description = "Max request size in bytes. Don't forget to also change it in the proxy.";
105 };
106 global.allow_registration = lib.mkOption {
107 type = lib.types.bool;
108 default = false;
109 description = ''
110 Whether new users can register on this server.
111
112 Registration with token requires `registration_token` or `registration_token_file` to be set.
113
114 If set to true without a token configured, and
115 `yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse`
116 is set to true, users can freely register.
117 '';
118 };
119 global.allow_encryption = lib.mkOption {
120 type = lib.types.bool;
121 default = true;
122 description = "Whether new encrypted rooms can be created. Note: existing rooms will continue to work.";
123 };
124 global.allow_federation = lib.mkOption {
125 type = lib.types.bool;
126 default = true;
127 description = ''
128 Whether this server federates with other servers.
129 '';
130 };
131 global.trusted_servers = lib.mkOption {
132 type = lib.types.listOf lib.types.nonEmptyStr;
133 default = [ "matrix.org" ];
134 description = ''
135 Servers listed here will be used to gather public keys of other servers
136 (notary trusted key servers).
137
138 Currently, continuwuity doesn't support inbound batched key requests, so
139 this list should only contain other Synapse servers.
140
141 Example: `[ "matrix.org" "constellatory.net" "tchncs.de" ]`
142 '';
143 };
144 global.database_path = lib.mkOption {
145 readOnly = true;
146 type = lib.types.path;
147 default = "/var/lib/continuwuity/";
148 description = ''
149 Path to the continuwuity database, the directory where continuwuity will save its data.
150 Note that database_path cannot be edited because of the service's reliance on systemd StateDir.
151 '';
152 };
153 global.allow_announcements_check = lib.mkOption {
154 type = lib.types.bool;
155 default = true;
156 description = ''
157 If enabled, continuwuity will send a simple GET request periodically to
158 <https://continuwuity.org/.well-known/continuwuity/announcements> for any new announcements made.
159 '';
160 };
161 };
162 };
163 default = { };
164 # TOML does not allow null values, so we use null to omit those fields
165 apply = lib.filterAttrsRecursive (_: v: v != null);
166 description = ''
167 Generates the continuwuity.toml configuration file. Refer to
168 <https://continuwuity.org/configuration.html>
169 for details on supported values.
170 '';
171 };
172 };
173
174 config = lib.mkIf cfg.enable {
175 assertions = [
176 {
177 assertion = !(cfg.settings ? global.unix_socket_path) || !(cfg.settings ? global.address);
178 message = ''
179 In `services.continuwuity.settings.global`, `unix_socket_path` and `address` cannot be set at the
180 same time.
181 Leave one of the two options unset or explicitly set them to `null`.
182 '';
183 }
184 {
185 assertion = cfg.user != defaultUser -> config ? users.users.${cfg.user};
186 message = "If `services.continuwuity.user` is changed, the configured user must already exist.";
187 }
188 {
189 assertion = cfg.group != defaultGroup -> config ? users.groups.${cfg.group};
190 message = "If `services.continuwuity.group` is changed, the configured group must already exist.";
191 }
192 ];
193
194 users.users = lib.mkIf (cfg.user == defaultUser) {
195 ${defaultUser} = {
196 group = cfg.group;
197 home = cfg.settings.global.database_path;
198 isSystemUser = true;
199 };
200 };
201
202 users.groups = lib.mkIf (cfg.group == defaultGroup) {
203 ${defaultGroup} = { };
204 };
205
206 systemd.services.continuwuity = {
207 description = "Continuwuity Matrix Server";
208 documentation = [ "https://continuwuity.org/" ];
209 wantedBy = [ "multi-user.target" ];
210 wants = [ "network-online.target" ];
211 after = [ "network-online.target" ];
212 environment = lib.mkMerge [
213 { CONTINUWUITY_CONFIG = configFile; }
214 cfg.extraEnvironment
215 ];
216 startLimitBurst = 5;
217 startLimitIntervalSec = 60;
218 serviceConfig = {
219 DynamicUser = true;
220 User = cfg.user;
221 Group = cfg.group;
222
223 DevicePolicy = "closed";
224 LockPersonality = true;
225 MemoryDenyWriteExecute = true;
226 NoNewPrivileges = true;
227 ProtectClock = true;
228 ProtectControlGroups = true;
229 ProtectHome = true;
230 ProtectHostname = true;
231 ProtectKernelLogs = true;
232 ProtectKernelModules = true;
233 ProtectKernelTunables = true;
234 ProtectProc = "invisible";
235 ProtectSystem = "strict";
236 PrivateDevices = true;
237 PrivateMounts = true;
238 PrivateTmp = true;
239 PrivateUsers = true;
240 PrivateIPC = true;
241 RemoveIPC = true;
242 RestrictAddressFamilies = [
243 "AF_INET"
244 "AF_INET6"
245 "AF_UNIX"
246 ];
247 RestrictNamespaces = true;
248 RestrictRealtime = true;
249 RestrictSUIDSGID = true;
250 SystemCallArchitectures = "native";
251 SystemCallFilter = [
252 "@system-service @resources"
253 "~@clock @debug @module @mount @reboot @swap @cpu-emulation @obsolete @timer @chown @setuid @privileged @keyring @ipc"
254 ];
255 SystemCallErrorNumber = "EPERM";
256
257 StateDirectory = "continuwuity";
258 StateDirectoryMode = "0700";
259 RuntimeDirectory = "continuwuity";
260 RuntimeDirectoryMode = "0750";
261
262 ExecStart = lib.getExe cfg.package;
263 Restart = "on-failure";
264 RestartSec = 10;
265 };
266 };
267 };
268}