]> Git — Sourcephile - sourcephile-nix.git/blob - nixpkgs/patches/sourcehut.diff
murmur: fix reloading on ACME renewal
[sourcephile-nix.git] / nixpkgs / patches / sourcehut.diff
1 diff --git a/nixos/lib/make-options-doc/options-to-docbook.xsl b/nixos/lib/make-options-doc/options-to-docbook.xsl
2 index 18d19fddaca..304698a51ad 100644
3 --- a/nixos/lib/make-options-doc/options-to-docbook.xsl
4 +++ b/nixos/lib/make-options-doc/options-to-docbook.xsl
5 @@ -20,7 +20,7 @@
6 <title>Configuration Options</title>
7 <variablelist xml:id="configuration-variable-list">
8 <xsl:for-each select="attrs">
9 - <xsl:variable name="id" select="concat('opt-', str:replace(str:replace(str:replace(attr[@name = 'name']/string/@value, '*', '_'), '&lt;', '_'), '>', '_'))" />
10 + <xsl:variable name="id" select="concat('opt-', str:replace(str:replace(str:replace(str:replace(attr[@name = 'name']/string/@value, '*', '_'), '&lt;', '_'), '>', '_'), ':', '_'))" />
11 <varlistentry>
12 <term xlink:href="#{$id}">
13 <xsl:attribute name="xml:id"><xsl:value-of select="$id"/></xsl:attribute>
14 diff --git a/nixos/modules/services/databases/redis.nix b/nixos/modules/services/databases/redis.nix
15 index 8873f6d00e0..e14bb89c75c 100644
16 --- a/nixos/modules/services/databases/redis.nix
17 +++ b/nixos/modules/services/databases/redis.nix
18 @@ -5,17 +5,18 @@ with lib;
19 let
20 cfg = config.services.redis;
21
22 - ulimitNofile = cfg.maxclients + 32;
23 -
24 mkValueString = value:
25 if value == true then "yes"
26 else if value == false then "no"
27 else generators.mkValueStringDefault { } value;
28
29 - redisConfig = pkgs.writeText "redis.conf" (generators.toKeyValue {
30 + redisConfig = settings: pkgs.writeText "redis.conf" (generators.toKeyValue {
31 listsAsDuplicateKeys = true;
32 mkKeyValue = generators.mkKeyValueDefault { inherit mkValueString; } " ";
33 - } cfg.settings);
34 + } settings);
35 +
36 + redisName = name: "redis" + optionalString (name != "") ("-"+name);
37 + enabledServers = filterAttrs (name: conf: conf.enable) config.services.redis.servers;
38
39 in {
40 imports = [
41 @@ -25,6 +26,27 @@ in {
42 (mkRemovedOptionModule [ "services" "redis" "appendOnlyFilename" ] "This option was never used.")
43 (mkRemovedOptionModule [ "services" "redis" "pidFile" ] "This option was removed.")
44 (mkRemovedOptionModule [ "services" "redis" "extraConfig" ] "Use services.redis.settings instead.")
45 + (mkRenamedOptionModule [ "services" "redis" "enable"] [ "services" "redis" "servers" "" "enable" ])
46 + (mkRenamedOptionModule [ "services" "redis" "port"] [ "services" "redis" "servers" "" "port" ])
47 + (mkRenamedOptionModule [ "services" "redis" "openFirewall"] [ "services" "redis" "servers" "" "openFirewall" ])
48 + (mkRenamedOptionModule [ "services" "redis" "bind"] [ "services" "redis" "servers" "" "bind" ])
49 + (mkRenamedOptionModule [ "services" "redis" "unixSocket"] [ "services" "redis" "servers" "" "unixSocket" ])
50 + (mkRenamedOptionModule [ "services" "redis" "unixSocketPerm"] [ "services" "redis" "servers" "" "unixSocketPerm" ])
51 + (mkRenamedOptionModule [ "services" "redis" "logLevel"] [ "services" "redis" "servers" "" "logLevel" ])
52 + (mkRenamedOptionModule [ "services" "redis" "logfile"] [ "services" "redis" "servers" "" "logfile" ])
53 + (mkRenamedOptionModule [ "services" "redis" "syslog"] [ "services" "redis" "servers" "" "syslog" ])
54 + (mkRenamedOptionModule [ "services" "redis" "databases"] [ "services" "redis" "servers" "" "databases" ])
55 + (mkRenamedOptionModule [ "services" "redis" "maxclients"] [ "services" "redis" "servers" "" "maxclients" ])
56 + (mkRenamedOptionModule [ "services" "redis" "save"] [ "services" "redis" "servers" "" "save" ])
57 + (mkRenamedOptionModule [ "services" "redis" "slaveOf"] [ "services" "redis" "servers" "" "slaveOf" ])
58 + (mkRenamedOptionModule [ "services" "redis" "masterAuth"] [ "services" "redis" "servers" "" "masterAuth" ])
59 + (mkRenamedOptionModule [ "services" "redis" "requirePass"] [ "services" "redis" "servers" "" "requirePass" ])
60 + (mkRenamedOptionModule [ "services" "redis" "requirePassFile"] [ "services" "redis" "servers" "" "requirePassFile" ])
61 + (mkRenamedOptionModule [ "services" "redis" "appendOnly"] [ "services" "redis" "servers" "" "appendOnly" ])
62 + (mkRenamedOptionModule [ "services" "redis" "appendFsync"] [ "services" "redis" "servers" "" "appendFsync" ])
63 + (mkRenamedOptionModule [ "services" "redis" "slowLogLogSlowerThan"] [ "services" "redis" "servers" "" "slowLogLogSlowerThan" ])
64 + (mkRenamedOptionModule [ "services" "redis" "slowLogMaxLen"] [ "services" "redis" "servers" "" "slowLogMaxLen" ])
65 + (mkRenamedOptionModule [ "services" "redis" "settings"] [ "services" "redis" "servers" "" "settings" ])
66 ];
67
68 ###### interface
69 @@ -32,18 +54,6 @@ in {
70 options = {
71
72 services.redis = {
73 -
74 - enable = mkOption {
75 - type = types.bool;
76 - default = false;
77 - description = ''
78 - Whether to enable the Redis server. Note that the NixOS module for
79 - Redis disables kernel support for Transparent Huge Pages (THP),
80 - because this features causes major performance problems for Redis,
81 - e.g. (https://redis.io/topics/latency).
82 - '';
83 - };
84 -
85 package = mkOption {
86 type = types.package;
87 default = pkgs.redis;
88 @@ -51,177 +61,227 @@ in {
89 description = "Which Redis derivation to use.";
90 };
91
92 - port = mkOption {
93 - type = types.port;
94 - default = 6379;
95 - description = "The port for Redis to listen to.";
96 - };
97 + vmOverCommit = mkEnableOption ''
98 + setting of vm.overcommit_memory to 1
99 + (Suggested for Background Saving: http://redis.io/topics/faq)
100 + '';
101
102 - vmOverCommit = mkOption {
103 - type = types.bool;
104 - default = false;
105 - description = ''
106 - Set vm.overcommit_memory to 1 (Suggested for Background Saving: http://redis.io/topics/faq)
107 - '';
108 - };
109 -
110 - openFirewall = mkOption {
111 - type = types.bool;
112 - default = false;
113 - description = ''
114 - Whether to open ports in the firewall for the server.
115 - '';
116 - };
117 -
118 - bind = mkOption {
119 - type = with types; nullOr str;
120 - default = "127.0.0.1";
121 - description = ''
122 - The IP interface to bind to.
123 - <literal>null</literal> means "all interfaces".
124 - '';
125 - example = "192.0.2.1";
126 - };
127 -
128 - unixSocket = mkOption {
129 - type = with types; nullOr path;
130 - default = null;
131 - description = "The path to the socket to bind to.";
132 - example = "/run/redis/redis.sock";
133 - };
134 -
135 - unixSocketPerm = mkOption {
136 - type = types.int;
137 - default = 750;
138 - description = "Change permissions for the socket";
139 - example = 700;
140 - };
141 -
142 - logLevel = mkOption {
143 - type = types.str;
144 - default = "notice"; # debug, verbose, notice, warning
145 - example = "debug";
146 - description = "Specify the server verbosity level, options: debug, verbose, notice, warning.";
147 - };
148 -
149 - logfile = mkOption {
150 - type = types.str;
151 - default = "/dev/null";
152 - description = "Specify the log file name. Also 'stdout' can be used to force Redis to log on the standard output.";
153 - example = "/var/log/redis.log";
154 - };
155 -
156 - syslog = mkOption {
157 - type = types.bool;
158 - default = true;
159 - description = "Enable logging to the system logger.";
160 - };
161 -
162 - databases = mkOption {
163 - type = types.int;
164 - default = 16;
165 - description = "Set the number of databases.";
166 - };
167 -
168 - maxclients = mkOption {
169 - type = types.int;
170 - default = 10000;
171 - description = "Set the max number of connected clients at the same time.";
172 - };
173 -
174 - save = mkOption {
175 - type = with types; listOf (listOf int);
176 - default = [ [900 1] [300 10] [60 10000] ];
177 - description = "The schedule in which data is persisted to disk, represented as a list of lists where the first element represent the amount of seconds and the second the number of changes.";
178 - example = [ [900 1] [300 10] [60 10000] ];
179 - };
180 -
181 - slaveOf = mkOption {
182 - type = with types; nullOr (submodule ({ ... }: {
183 + servers = mkOption {
184 + type = with types; attrsOf (submodule ({config, name, ...}@args: {
185 options = {
186 - ip = mkOption {
187 - type = str;
188 - description = "IP of the Redis master";
189 - example = "192.168.1.100";
190 + enable = mkEnableOption ''
191 + Redis server.
192 +
193 + Note that the NixOS module for Redis disables kernel support
194 + for Transparent Huge Pages (THP),
195 + because this features causes major performance problems for Redis,
196 + e.g. (https://redis.io/topics/latency).
197 + '';
198 +
199 + user = mkOption {
200 + type = types.str;
201 + default = redisName name;
202 + defaultText = "\"redis\" or \"redis-\${name}\" if name != \"\"";
203 + description = "The username and groupname for redis-server.";
204 };
205
206 port = mkOption {
207 - type = port;
208 - description = "port of the Redis master";
209 + type = types.port;
210 default = 6379;
211 + description = "The port for Redis to listen to.";
212 + };
213 +
214 + openFirewall = mkOption {
215 + type = types.bool;
216 + default = false;
217 + description = ''
218 + Whether to open ports in the firewall for the server.
219 + '';
220 + };
221 +
222 + bind = mkOption {
223 + type = with types; nullOr str;
224 + default = if name == "" then "127.0.0.1" else null;
225 + defaultText = "127.0.0.1 or null if name != \"\"";
226 + description = ''
227 + The IP interface to bind to.
228 + <literal>null</literal> means "all interfaces".
229 + '';
230 + example = "192.0.2.1";
231 + };
232 +
233 + unixSocket = mkOption {
234 + type = with types; nullOr path;
235 + default = "/run/${redisName name}/redis.sock";
236 + defaultText = "\"/run/redis/redis.sock\" or \"/run/redis-\${name}/redis.sock\" if name != \"\"";
237 + description = "The path to the socket to bind to.";
238 + };
239 +
240 + unixSocketPerm = mkOption {
241 + type = types.int;
242 + default = 660;
243 + description = "Change permissions for the socket";
244 + example = 600;
245 + };
246 +
247 + logLevel = mkOption {
248 + type = types.str;
249 + default = "notice"; # debug, verbose, notice, warning
250 + example = "debug";
251 + description = "Specify the server verbosity level, options: debug, verbose, notice, warning.";
252 + };
253 +
254 + logfile = mkOption {
255 + type = types.str;
256 + default = "/dev/null";
257 + description = "Specify the log file name. Also 'stdout' can be used to force Redis to log on the standard output.";
258 + example = "/var/log/redis.log";
259 + };
260 +
261 + syslog = mkOption {
262 + type = types.bool;
263 + default = true;
264 + description = "Enable logging to the system logger.";
265 + };
266 +
267 + databases = mkOption {
268 + type = types.int;
269 + default = 16;
270 + description = "Set the number of databases.";
271 + };
272 +
273 + maxclients = mkOption {
274 + type = types.int;
275 + default = 10000;
276 + description = "Set the max number of connected clients at the same time.";
277 + };
278 +
279 + save = mkOption {
280 + type = with types; listOf (listOf int);
281 + default = [ [900 1] [300 10] [60 10000] ];
282 + description = "The schedule in which data is persisted to disk, represented as a list of lists where the first element represent the amount of seconds and the second the number of changes.";
283 + example = [ [900 1] [300 10] [60 10000] ];
284 + };
285 +
286 + slaveOf = mkOption {
287 + type = with types; nullOr (submodule ({ ... }: {
288 + options = {
289 + ip = mkOption {
290 + type = str;
291 + description = "IP of the Redis master";
292 + example = "192.168.1.100";
293 + };
294 +
295 + port = mkOption {
296 + type = port;
297 + description = "port of the Redis master";
298 + default = 6379;
299 + };
300 + };
301 + }));
302 +
303 + default = null;
304 + description = "IP and port to which this redis instance acts as a slave.";
305 + example = { ip = "192.168.1.100"; port = 6379; };
306 + };
307 +
308 + masterAuth = mkOption {
309 + type = with types; nullOr str;
310 + default = null;
311 + description = ''If the master is password protected (using the requirePass configuration)
312 + it is possible to tell the slave to authenticate before starting the replication synchronization
313 + process, otherwise the master will refuse the slave request.
314 + (STORED PLAIN TEXT, WORLD-READABLE IN NIX STORE)'';
315 + };
316 +
317 + requirePass = mkOption {
318 + type = with types; nullOr str;
319 + default = null;
320 + description = ''
321 + Password for database (STORED PLAIN TEXT, WORLD-READABLE IN NIX STORE).
322 + Use requirePassFile to store it outside of the nix store in a dedicated file.
323 + '';
324 + example = "letmein!";
325 + };
326 +
327 + requirePassFile = mkOption {
328 + type = with types; nullOr path;
329 + default = null;
330 + description = "File with password for the database.";
331 + example = "/run/keys/redis-password";
332 + };
333 +
334 + appendOnly = mkOption {
335 + type = types.bool;
336 + default = false;
337 + description = "By default data is only periodically persisted to disk, enable this option to use an append-only file for improved persistence.";
338 + };
339 +
340 + appendFsync = mkOption {
341 + type = types.str;
342 + default = "everysec"; # no, always, everysec
343 + description = "How often to fsync the append-only log, options: no, always, everysec.";
344 + };
345 +
346 + slowLogLogSlowerThan = mkOption {
347 + type = types.int;
348 + default = 10000;
349 + description = "Log queries whose execution take longer than X in milliseconds.";
350 + example = 1000;
351 + };
352 +
353 + slowLogMaxLen = mkOption {
354 + type = types.int;
355 + default = 128;
356 + description = "Maximum number of items to keep in slow log.";
357 + };
358 +
359 + settings = mkOption {
360 + # TODO: this should be converted to freeformType
361 + type = with types; attrsOf (oneOf [ bool int str (listOf str) ]);
362 + default = {};
363 + description = ''
364 + Redis configuration. Refer to
365 + <link xlink:href="https://redis.io/topics/config"/>
366 + for details on supported values.
367 + '';
368 + example = literalExample ''
369 + {
370 + loadmodule = [ "/path/to/my_module.so" "/path/to/other_module.so" ];
371 + }
372 + '';
373 };
374 };
375 + config.settings = mkMerge [
376 + {
377 + port = config.port;
378 + daemonize = false;
379 + supervised = "systemd";
380 + loglevel = config.logLevel;
381 + logfile = config.logfile;
382 + syslog-enabled = config.syslog;
383 + databases = config.databases;
384 + maxclients = config.maxclients;
385 + save = map (d: "${toString (builtins.elemAt d 0)} ${toString (builtins.elemAt d 1)}") config.save;
386 + dbfilename = "dump.rdb";
387 + dir = "/var/lib/${redisName name}";
388 + appendOnly = config.appendOnly;
389 + appendfsync = config.appendFsync;
390 + slowlog-log-slower-than = config.slowLogLogSlowerThan;
391 + slowlog-max-len = config.slowLogMaxLen;
392 + }
393 + (mkIf (config.bind != null) { bind = config.bind; })
394 + (mkIf (config.unixSocket != null) {
395 + unixsocket = config.unixSocket;
396 + unixsocketperm = toString config.unixSocketPerm;
397 + })
398 + (mkIf (config.slaveOf != null) { slaveof = "${config.slaveOf.ip} ${toString config.slaveOf.port}"; })
399 + (mkIf (config.masterAuth != null) { masterauth = config.masterAuth; })
400 + (mkIf (config.requirePass != null) { requirepass = config.requirePass; })
401 + ];
402 }));
403 -
404 - default = null;
405 - description = "IP and port to which this redis instance acts as a slave.";
406 - example = { ip = "192.168.1.100"; port = 6379; };
407 - };
408 -
409 - masterAuth = mkOption {
410 - type = with types; nullOr str;
411 - default = null;
412 - description = ''If the master is password protected (using the requirePass configuration)
413 - it is possible to tell the slave to authenticate before starting the replication synchronization
414 - process, otherwise the master will refuse the slave request.
415 - (STORED PLAIN TEXT, WORLD-READABLE IN NIX STORE)'';
416 - };
417 -
418 - requirePass = mkOption {
419 - type = with types; nullOr str;
420 - default = null;
421 - description = ''
422 - Password for database (STORED PLAIN TEXT, WORLD-READABLE IN NIX STORE).
423 - Use requirePassFile to store it outside of the nix store in a dedicated file.
424 - '';
425 - example = "letmein!";
426 - };
427 -
428 - requirePassFile = mkOption {
429 - type = with types; nullOr path;
430 - default = null;
431 - description = "File with password for the database.";
432 - example = "/run/keys/redis-password";
433 - };
434 -
435 - appendOnly = mkOption {
436 - type = types.bool;
437 - default = false;
438 - description = "By default data is only periodically persisted to disk, enable this option to use an append-only file for improved persistence.";
439 - };
440 -
441 - appendFsync = mkOption {
442 - type = types.str;
443 - default = "everysec"; # no, always, everysec
444 - description = "How often to fsync the append-only log, options: no, always, everysec.";
445 - };
446 -
447 - slowLogLogSlowerThan = mkOption {
448 - type = types.int;
449 - default = 10000;
450 - description = "Log queries whose execution take longer than X in milliseconds.";
451 - example = 1000;
452 - };
453 -
454 - slowLogMaxLen = mkOption {
455 - type = types.int;
456 - default = 128;
457 - description = "Maximum number of items to keep in slow log.";
458 - };
459 -
460 - settings = mkOption {
461 - type = with types; attrsOf (oneOf [ bool int str (listOf str) ]);
462 + description = "Configuration of multiple <literal>redis-server</literal> instances.";
463 default = {};
464 - description = ''
465 - Redis configuration. Refer to
466 - <link xlink:href="https://redis.io/topics/config"/>
467 - for details on supported values.
468 - '';
469 - example = literalExample ''
470 - {
471 - loadmodule = [ "/path/to/my_module.so" "/path/to/other_module.so" ];
472 - }
473 - '';
474 };
475 };
476
477 @@ -230,77 +290,60 @@ in {
478
479 ###### implementation
480
481 - config = mkIf config.services.redis.enable {
482 - assertions = [{
483 - assertion = cfg.requirePass != null -> cfg.requirePassFile == null;
484 - message = "You can only set one services.redis.requirePass or services.redis.requirePassFile";
485 - }];
486 - boot.kernel.sysctl = (mkMerge [
487 + config = mkIf (enabledServers != {}) {
488 +
489 + assertions = attrValues (mapAttrs (name: conf: {
490 + assertion = conf.requirePass != null -> conf.requirePassFile == null;
491 + message = ''
492 + You can only set one services.redis.servers.${name}.requirePass
493 + or services.redis.servers.${name}.requirePassFile
494 + '';
495 + }) enabledServers);
496 +
497 + boot.kernel.sysctl = mkMerge [
498 { "vm.nr_hugepages" = "0"; }
499 ( mkIf cfg.vmOverCommit { "vm.overcommit_memory" = "1"; } )
500 - ]);
501 + ];
502
503 - networking.firewall = mkIf cfg.openFirewall {
504 - allowedTCPPorts = [ cfg.port ];
505 - };
506 -
507 - users.users.redis = {
508 - description = "Redis database user";
509 - isSystemUser = true;
510 - };
511 - users.groups.redis = {};
512 + networking.firewall.allowedTCPPorts = concatMap (conf:
513 + optional conf.openFirewall conf.port
514 + ) (attrValues enabledServers);
515
516 environment.systemPackages = [ cfg.package ];
517
518 - services.redis.settings = mkMerge [
519 - {
520 - port = cfg.port;
521 - daemonize = false;
522 - supervised = "systemd";
523 - loglevel = cfg.logLevel;
524 - logfile = cfg.logfile;
525 - syslog-enabled = cfg.syslog;
526 - databases = cfg.databases;
527 - maxclients = cfg.maxclients;
528 - save = map (d: "${toString (builtins.elemAt d 0)} ${toString (builtins.elemAt d 1)}") cfg.save;
529 - dbfilename = "dump.rdb";
530 - dir = "/var/lib/redis";
531 - appendOnly = cfg.appendOnly;
532 - appendfsync = cfg.appendFsync;
533 - slowlog-log-slower-than = cfg.slowLogLogSlowerThan;
534 - slowlog-max-len = cfg.slowLogMaxLen;
535 - }
536 - (mkIf (cfg.bind != null) { bind = cfg.bind; })
537 - (mkIf (cfg.unixSocket != null) { unixsocket = cfg.unixSocket; unixsocketperm = "${toString cfg.unixSocketPerm}"; })
538 - (mkIf (cfg.slaveOf != null) { slaveof = "${cfg.slaveOf.ip} ${toString cfg.slaveOf.port}"; })
539 - (mkIf (cfg.masterAuth != null) { masterauth = cfg.masterAuth; })
540 - (mkIf (cfg.requirePass != null) { requirepass = cfg.requirePass; })
541 - ];
542 + users.users = mapAttrs' (name: conf: nameValuePair (redisName name) {
543 + description = "System user for the redis-server instance ${name}";
544 + isSystemUser = true;
545 + }) enabledServers;
546 + users.groups = mapAttrs' (name: conf: nameValuePair (redisName name) {
547 + }) enabledServers;
548
549 - systemd.services.redis = {
550 - description = "Redis Server";
551 + systemd.services = mapAttrs' (name: conf: nameValuePair (redisName name) {
552 + description = "Redis Server - ${redisName name}";
553
554 wantedBy = [ "multi-user.target" ];
555 after = [ "network.target" ];
556
557 - preStart = ''
558 - install -m 600 ${redisConfig} /run/redis/redis.conf
559 - '' + optionalString (cfg.requirePassFile != null) ''
560 - password=$(cat ${escapeShellArg cfg.requirePassFile})
561 - echo "requirePass $password" >> /run/redis/redis.conf
562 - '';
563 -
564 serviceConfig = {
565 - ExecStart = "${cfg.package}/bin/redis-server /run/redis/redis.conf";
566 + ExecStart = "${cfg.package}/bin/redis-server /run/${redisName name}/redis.conf";
567 + ExecStartPre = [("+"+pkgs.writeShellScript "${redisName name}-credentials" (''
568 + install -o '${conf.user}' -m 600 ${redisConfig conf.settings} /run/${redisName name}/redis.conf
569 + '' + optionalString (conf.requirePassFile != null) ''
570 + {
571 + printf requirePass' '
572 + cat ${escapeShellArg conf.requirePassFile}
573 + } >>/run/${redisName name}/redis.conf
574 + '')
575 + )];
576 Type = "notify";
577 # User and group
578 - User = "redis";
579 - Group = "redis";
580 + User = conf.user;
581 + Group = conf.user;
582 # Runtime directory and mode
583 - RuntimeDirectory = "redis";
584 + RuntimeDirectory = redisName name;
585 RuntimeDirectoryMode = "0750";
586 # State directory and mode
587 - StateDirectory = "redis";
588 + StateDirectory = redisName name;
589 StateDirectoryMode = "0700";
590 # Access write directories
591 UMask = "0077";
592 @@ -309,7 +352,7 @@ in {
593 # Security
594 NoNewPrivileges = true;
595 # Process Properties
596 - LimitNOFILE = "${toString ulimitNofile}";
597 + LimitNOFILE = mkDefault "${toString (conf.maxclients + 32)}";
598 # Sandboxing
599 ProtectSystem = "strict";
600 ProtectHome = true;
601 @@ -322,7 +365,9 @@ in {
602 ProtectKernelModules = true;
603 ProtectKernelTunables = true;
604 ProtectControlGroups = true;
605 - RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
606 + RestrictAddressFamilies =
607 + optionals (conf.bind != null) ["AF_INET" "AF_INET6"] ++
608 + optional (conf.unixSocket != null) "AF_UNIX";
609 RestrictNamespaces = true;
610 LockPersonality = true;
611 MemoryDenyWriteExecute = true;
612 @@ -333,6 +378,7 @@ in {
613 SystemCallArchitectures = "native";
614 SystemCallFilter = "~@cpu-emulation @debug @keyring @memlock @mount @obsolete @privileged @resources @setuid";
615 };
616 - };
617 + }) enabledServers;
618 +
619 };
620 }
621 diff --git a/nixos/modules/services/misc/sourcehut/builds.nix b/nixos/modules/services/misc/sourcehut/builds.nix
622 deleted file mode 100644
623 index e446f08284f..00000000000
624 --- a/nixos/modules/services/misc/sourcehut/builds.nix
625 +++ /dev/null
626 @@ -1,234 +0,0 @@
627 -{ config, lib, pkgs, ... }:
628 -
629 -with lib;
630 -let
631 - cfg = config.services.sourcehut;
632 - scfg = cfg.builds;
633 - rcfg = config.services.redis;
634 - iniKey = "builds.sr.ht";
635 -
636 - drv = pkgs.sourcehut.buildsrht;
637 -in
638 -{
639 - options.services.sourcehut.builds = {
640 - user = mkOption {
641 - type = types.str;
642 - default = "buildsrht";
643 - description = ''
644 - User for builds.sr.ht.
645 - '';
646 - };
647 -
648 - port = mkOption {
649 - type = types.port;
650 - default = 5002;
651 - description = ''
652 - Port on which the "builds" module should listen.
653 - '';
654 - };
655 -
656 - database = mkOption {
657 - type = types.str;
658 - default = "builds.sr.ht";
659 - description = ''
660 - PostgreSQL database name for builds.sr.ht.
661 - '';
662 - };
663 -
664 - statePath = mkOption {
665 - type = types.path;
666 - default = "${cfg.statePath}/buildsrht";
667 - description = ''
668 - State path for builds.sr.ht.
669 - '';
670 - };
671 -
672 - enableWorker = mkOption {
673 - type = types.bool;
674 - default = false;
675 - description = ''
676 - Run workers for builds.sr.ht.
677 - '';
678 - };
679 -
680 - images = mkOption {
681 - type = types.attrsOf (types.attrsOf (types.attrsOf types.package));
682 - default = { };
683 - example = lib.literalExample ''(let
684 - # Pinning unstable to allow usage with flakes and limit rebuilds.
685 - pkgs_unstable = builtins.fetchGit {
686 - url = "https://github.com/NixOS/nixpkgs";
687 - rev = "ff96a0fa5635770390b184ae74debea75c3fd534";
688 - ref = "nixos-unstable";
689 - };
690 - image_from_nixpkgs = pkgs_unstable: (import ("${pkgs.sourcehut.buildsrht}/lib/images/nixos/image.nix") {
691 - pkgs = (import pkgs_unstable {});
692 - });
693 - in
694 - {
695 - nixos.unstable.x86_64 = image_from_nixpkgs pkgs_unstable;
696 - }
697 - )'';
698 - description = ''
699 - Images for builds.sr.ht. Each package should be distro.release.arch and point to a /nix/store/package/root.img.qcow2.
700 - '';
701 - };
702 -
703 - };
704 -
705 - config = with scfg; let
706 - image_dirs = lib.lists.flatten (
707 - lib.attrsets.mapAttrsToList
708 - (distro: revs:
709 - lib.attrsets.mapAttrsToList
710 - (rev: archs:
711 - lib.attrsets.mapAttrsToList
712 - (arch: image:
713 - pkgs.runCommand "buildsrht-images" { } ''
714 - mkdir -p $out/${distro}/${rev}/${arch}
715 - ln -s ${image}/*.qcow2 $out/${distro}/${rev}/${arch}/root.img.qcow2
716 - '')
717 - archs)
718 - revs)
719 - scfg.images);
720 - image_dir_pre = pkgs.symlinkJoin {
721 - name = "builds.sr.ht-worker-images-pre";
722 - paths = image_dirs ++ [
723 - "${pkgs.sourcehut.buildsrht}/lib/images"
724 - ];
725 - };
726 - image_dir = pkgs.runCommand "builds.sr.ht-worker-images" { } ''
727 - mkdir -p $out/images
728 - cp -Lr ${image_dir_pre}/* $out/images
729 - '';
730 - in
731 - lib.mkIf (cfg.enable && elem "builds" cfg.services) {
732 - users = {
733 - users = {
734 - "${user}" = {
735 - isSystemUser = true;
736 - group = user;
737 - extraGroups = lib.optionals cfg.builds.enableWorker [ "docker" ];
738 - description = "builds.sr.ht user";
739 - };
740 - };
741 -
742 - groups = {
743 - "${user}" = { };
744 - };
745 - };
746 -
747 - services.postgresql = {
748 - authentication = ''
749 - local ${database} ${user} trust
750 - '';
751 - ensureDatabases = [ database ];
752 - ensureUsers = [
753 - {
754 - name = user;
755 - ensurePermissions = { "DATABASE \"${database}\"" = "ALL PRIVILEGES"; };
756 - }
757 - ];
758 - };
759 -
760 - systemd = {
761 - tmpfiles.rules = [
762 - "d ${statePath} 0755 ${user} ${user} -"
763 - ] ++ (lib.optionals cfg.builds.enableWorker
764 - [ "d ${statePath}/logs 0775 ${user} ${user} - -" ]
765 - );
766 -
767 - services = {
768 - buildsrht = import ./service.nix { inherit config pkgs lib; } scfg drv iniKey
769 - {
770 - after = [ "postgresql.service" "network.target" ];
771 - requires = [ "postgresql.service" ];
772 - wantedBy = [ "multi-user.target" ];
773 -
774 - description = "builds.sr.ht website service";
775 -
776 - serviceConfig.ExecStart = "${cfg.python}/bin/gunicorn ${drv.pname}.app:app -b ${cfg.address}:${toString port}";
777 -
778 - # Hack to bypass this hack: https://git.sr.ht/~sircmpwn/core.sr.ht/tree/master/item/srht-update-profiles#L6
779 - } // { preStart = " "; };
780 -
781 - buildsrht-worker = {
782 - enable = scfg.enableWorker;
783 - after = [ "postgresql.service" "network.target" ];
784 - requires = [ "postgresql.service" ];
785 - wantedBy = [ "multi-user.target" ];
786 - partOf = [ "buildsrht.service" ];
787 - description = "builds.sr.ht worker service";
788 - path = [ pkgs.openssh pkgs.docker ];
789 - preStart = let qemuPackage = pkgs.qemu_kvm;
790 - in ''
791 - if [[ "$(docker images -q qemu:latest 2> /dev/null)" == "" || "$(cat ${statePath}/docker-image-qemu 2> /dev/null || true)" != "${qemuPackage.version}" ]]; then
792 - # Create and import qemu:latest image for docker
793 - ${
794 - pkgs.dockerTools.streamLayeredImage {
795 - name = "qemu";
796 - tag = "latest";
797 - contents = [ qemuPackage ];
798 - }
799 - } | docker load
800 - # Mark down current package version
801 - printf "%s" "${qemuPackage.version}" > ${statePath}/docker-image-qemu
802 - fi
803 - '';
804 - serviceConfig = {
805 - Type = "simple";
806 - User = user;
807 - Group = "nginx";
808 - Restart = "always";
809 - };
810 - serviceConfig.ExecStart = "${pkgs.sourcehut.buildsrht}/bin/builds.sr.ht-worker";
811 - };
812 - };
813 - };
814 -
815 - services.sourcehut.settings = {
816 - # URL builds.sr.ht is being served at (protocol://domain)
817 - "builds.sr.ht".origin = mkDefault "http://builds.${cfg.originBase}";
818 - # Address and port to bind the debug server to
819 - "builds.sr.ht".debug-host = mkDefault "0.0.0.0";
820 - "builds.sr.ht".debug-port = mkDefault port;
821 - # Configures the SQLAlchemy connection string for the database.
822 - "builds.sr.ht".connection-string = mkDefault "postgresql:///${database}?user=${user}&host=/var/run/postgresql";
823 - # Set to "yes" to automatically run migrations on package upgrade.
824 - "builds.sr.ht".migrate-on-upgrade = mkDefault "yes";
825 - # builds.sr.ht's OAuth client ID and secret for meta.sr.ht
826 - # Register your client at meta.example.org/oauth
827 - "builds.sr.ht".oauth-client-id = mkDefault null;
828 - "builds.sr.ht".oauth-client-secret = mkDefault null;
829 - # The redis connection used for the celery worker
830 - "builds.sr.ht".redis = mkDefault "redis://${rcfg.bind}:${toString rcfg.port}/3";
831 - # The shell used for ssh
832 - "builds.sr.ht".shell = mkDefault "runner-shell";
833 - # Register the builds.sr.ht dispatcher
834 - "git.sr.ht::dispatch".${builtins.unsafeDiscardStringContext "${pkgs.sourcehut.buildsrht}/bin/buildsrht-keys"} = mkDefault "${user}:${user}";
835 -
836 - # Location for build logs, images, and control command
837 - } // lib.attrsets.optionalAttrs scfg.enableWorker {
838 - # Default worker stores logs that are accessible via this address:port
839 - "builds.sr.ht::worker".name = mkDefault "127.0.0.1:5020";
840 - "builds.sr.ht::worker".buildlogs = mkDefault "${scfg.statePath}/logs";
841 - "builds.sr.ht::worker".images = mkDefault "${image_dir}/images";
842 - "builds.sr.ht::worker".controlcmd = mkDefault "${image_dir}/images/control";
843 - "builds.sr.ht::worker".timeout = mkDefault "3m";
844 - };
845 -
846 - services.nginx.virtualHosts."logs.${cfg.originBase}" =
847 - if scfg.enableWorker then {
848 - listen = with builtins; let address = split ":" cfg.settings."builds.sr.ht::worker".name;
849 - in [{ addr = elemAt address 0; port = lib.toInt (elemAt address 2); }];
850 - locations."/logs".root = "${scfg.statePath}";
851 - } else { };
852 -
853 - services.nginx.virtualHosts."builds.${cfg.originBase}" = {
854 - forceSSL = true;
855 - locations."/".proxyPass = "http://${cfg.address}:${toString port}";
856 - locations."/query".proxyPass = "http://${cfg.address}:${toString (port + 100)}";
857 - locations."/static".root = "${pkgs.sourcehut.buildsrht}/${pkgs.sourcehut.python.sitePackages}/buildsrht";
858 - };
859 - };
860 -}
861 diff --git a/nixos/modules/services/misc/sourcehut/default.nix b/nixos/modules/services/misc/sourcehut/default.nix
862 index 9c812d6b043..c6c70e6ae2b 100644
863 --- a/nixos/modules/services/misc/sourcehut/default.nix
864 +++ b/nixos/modules/services/misc/sourcehut/default.nix
865 @@ -1,14 +1,90 @@
866 { config, pkgs, lib, ... }:
867 -
868 with lib;
869 let
870 + inherit (config.services) nginx postfix postgresql redis;
871 + inherit (config.users) users groups;
872 cfg = config.services.sourcehut;
873 - cfgIni = cfg.settings;
874 - settingsFormat = pkgs.formats.ini { };
875 + domain = cfg.settings."sr.ht".global-domain;
876 + settingsFormat = pkgs.formats.ini {
877 + listToValue = concatMapStringsSep "," (generators.mkValueStringDefault {});
878 + mkKeyValue = k: v:
879 + if v == null then ""
880 + else generators.mkKeyValueDefault {
881 + mkValueString = v:
882 + if v == true then "yes"
883 + else if v == false then "no"
884 + else generators.mkValueStringDefault {} v;
885 + } "=" k v;
886 + };
887 + configIniOfService = srv: settingsFormat.generate "sourcehut-${srv}-config.ini"
888 + # Each service needs access to only a subset of sections (and secrets).
889 + (filterAttrs (k: v: v != null)
890 + (mapAttrs (section: v:
891 + let srvMatch = builtins.match "^([a-z]*)\\.sr\\.ht(::.*)?$" section; in
892 + if srvMatch == null # Include sections shared by all services
893 + || head srvMatch == srv # Include sections for the service being configured
894 + then v
895 + # Enable Web links and integrations between services.
896 + else if tail srvMatch == [ null ] && elem (head srvMatch) cfg.services
897 + then {
898 + inherit (v) origin;
899 + # mansrht crashes without it
900 + oauth-client-id = v.oauth-client-id or null;
901 + }
902 + # Drop sub-sections of other services
903 + else null)
904 + (recursiveUpdate cfg.settings {
905 + # Those paths are mounted using BindPaths= or BindReadOnlyPaths=
906 + # for services needing access to them.
907 + "builds.sr.ht::worker".buildlogs = "/var/log/sourcehut/buildsrht/logs";
908 + "git.sr.ht".post-update-script = "/var/lib/sourcehut/gitsrht/bin/post-update-script";
909 + "git.sr.ht".repos = "/var/lib/sourcehut/gitsrht/repos";
910 + "hg.sr.ht".changegroup-script = "/var/lib/sourcehut/hgsrht/bin/changegroup-script";
911 + "hg.sr.ht".repos = "/var/lib/sourcehut/hgsrht/repos";
912 + # Making this a per service option despite being in a global section,
913 + # so that it uses the redis-server used by the service.
914 + "sr.ht".redis-host = cfg.${srv}.redis.host;
915 + })));
916 + commonServiceSettings = srv: {
917 + origin = mkOption {
918 + description = "URL ${srv}.sr.ht is being served at (protocol://domain)";
919 + type = types.str;
920 + default = "https://${srv}.${domain}";
921 + defaultText = "https://${srv}.example.com";
922 + };
923 + debug-host = mkOption {
924 + description = "Address to bind the debug server to.";
925 + type = with types; nullOr str;
926 + default = null;
927 + };
928 + debug-port = mkOption {
929 + description = "Port to bind the debug server to.";
930 + type = with types; nullOr str;
931 + default = null;
932 + };
933 + connection-string = mkOption {
934 + description = "SQLAlchemy connection string for the database.";
935 + type = types.str;
936 + default = "postgresql:///localhost?user=${srv}srht&host=/run/postgresql";
937 + };
938 + migrate-on-upgrade = mkEnableOption "automatic migrations on package upgrade" // { default = true; };
939 + oauth-client-id = mkOption {
940 + description = "${srv}.sr.ht's OAuth client id for meta.sr.ht.";
941 + type = types.str;
942 + };
943 + oauth-client-secret = mkOption {
944 + description = "${srv}.sr.ht's OAuth client secret for meta.sr.ht.";
945 + type = types.path;
946 + apply = s: "<" + toString s;
947 + };
948 + };
949
950 # Specialized python containing all the modules
951 python = pkgs.sourcehut.python.withPackages (ps: with ps; [
952 gunicorn
953 + eventlet
954 + # For monitoring Celery: sudo -u listssrht celery --app listssrht.process -b redis+socket:///run/redis-sourcehut/redis.sock?virtual_host=5 flower
955 + flower
956 # Sourcehut services
957 srht
958 buildsrht
959 @@ -19,69 +95,37 @@ let
960 listssrht
961 mansrht
962 metasrht
963 + # Not a python package
964 + #pagessrht
965 pastesrht
966 todosrht
967 ]);
968 + mkOptionNullOrStr = description: mkOption {
969 + inherit description;
970 + type = with types; nullOr str;
971 + default = null;
972 + };
973 in
974 {
975 - imports =
976 - [
977 - ./git.nix
978 - ./hg.nix
979 - ./hub.nix
980 - ./todo.nix
981 - ./man.nix
982 - ./meta.nix
983 - ./paste.nix
984 - ./builds.nix
985 - ./lists.nix
986 - ./dispatch.nix
987 - (mkRemovedOptionModule [ "services" "sourcehut" "nginx" "enable" ] ''
988 - The sourcehut module supports `nginx` as a local reverse-proxy by default and doesn't
989 - support other reverse-proxies officially.
990 -
991 - However it's possible to use an alternative reverse-proxy by
992 -
993 - * disabling nginx
994 - * adjusting the relevant settings for server addresses and ports directly
995 -
996 - Further details about this can be found in the `Sourcehut`-section of the NixOS-manual.
997 - '')
998 - ];
999 -
1000 options.services.sourcehut = {
1001 - enable = mkOption {
1002 - type = types.bool;
1003 - default = false;
1004 - description = ''
1005 - Enable sourcehut - git hosting, continuous integration, mailing list, ticket tracking,
1006 - task dispatching, wiki and account management services
1007 - '';
1008 - };
1009 + enable = mkEnableOption ''
1010 + sourcehut - git hosting, continuous integration, mailing list, ticket tracking,
1011 + task dispatching, wiki and account management services
1012 + '';
1013
1014 services = mkOption {
1015 - type = types.nonEmptyListOf (types.enum [ "builds" "dispatch" "git" "hub" "hg" "lists" "man" "meta" "paste" "todo" ]);
1016 - default = [ "man" "meta" "paste" ];
1017 - example = [ "builds" "dispatch" "git" "hub" "hg" "lists" "man" "meta" "paste" "todo" ];
1018 + type = with types; listOf (enum
1019 + [ "builds" "dispatch" "git" "hg" "hub" "lists" "man" "meta" "pages" "paste" "todo" ]);
1020 + defaultText = "locally enabled services";
1021 description = ''
1022 - Services to enable on the sourcehut network.
1023 + Services that may be displayed as links in the title bar of the Web interface.
1024 '';
1025 };
1026
1027 - originBase = mkOption {
1028 + listenAddress = mkOption {
1029 type = types.str;
1030 - default = with config.networking; hostName + lib.optionalString (domain != null) ".${domain}";
1031 - description = ''
1032 - Host name used by reverse-proxy and for default settings. Will host services at git."''${originBase}". For example: git.sr.ht
1033 - '';
1034 - };
1035 -
1036 - address = mkOption {
1037 - type = types.str;
1038 - default = "127.0.0.1";
1039 - description = ''
1040 - Address to bind to.
1041 - '';
1042 + default = "localhost";
1043 + description = "Address to bind to.";
1044 };
1045
1046 python = mkOption {
1047 @@ -94,105 +138,1222 @@ in
1048 '';
1049 };
1050
1051 - statePath = mkOption {
1052 - type = types.path;
1053 - default = "/var/lib/sourcehut";
1054 - description = ''
1055 - Root state path for the sourcehut network. If left as the default value
1056 - this directory will automatically be created before the sourcehut server
1057 - starts, otherwise the sysadmin is responsible for ensuring the
1058 - directory exists with appropriate ownership and permissions.
1059 - '';
1060 + minio = {
1061 + enable = mkEnableOption ''local minio integration'';
1062 + };
1063 +
1064 + nginx = {
1065 + enable = mkEnableOption ''local nginx integration'';
1066 + virtualHost = mkOption {
1067 + type = types.attrs;
1068 + default = {};
1069 + description = "Virtual-host configuration merged with all Sourcehut's virtual-hosts.";
1070 + };
1071 + };
1072 +
1073 + postfix = {
1074 + enable = mkEnableOption ''local postfix integration'';
1075 + };
1076 +
1077 + postgresql = {
1078 + enable = mkEnableOption ''local postgresql integration'';
1079 + };
1080 +
1081 + redis = {
1082 + enable = mkEnableOption ''local redis integration in a dedicated redis-server'';
1083 };
1084
1085 settings = mkOption {
1086 type = lib.types.submodule {
1087 freeformType = settingsFormat.type;
1088 + options."sr.ht" = {
1089 + global-domain = mkOption {
1090 + description = "Global domain name.";
1091 + type = types.str;
1092 + example = "example.com";
1093 + };
1094 + environment = mkOption {
1095 + description = "Values other than \"production\" adds a banner to each page.";
1096 + type = types.enum [ "development" "production" ];
1097 + default = "development";
1098 + };
1099 + network-key = mkOption {
1100 + description = ''
1101 + An absolute file path (which should be outside the Nix-store)
1102 + to a secret key to encrypt internal messages with. Use <code>srht-keygen network</code> to
1103 + generate this key. It must be consistent between all services and nodes.
1104 + '';
1105 + type = types.path;
1106 + apply = s: "<" + toString s;
1107 + };
1108 + owner-email = mkOption {
1109 + description = "Owner's email.";
1110 + type = types.str;
1111 + default = "contact@example.com";
1112 + };
1113 + owner-name = mkOption {
1114 + description = "Owner's name.";
1115 + type = types.str;
1116 + default = "John Doe";
1117 + };
1118 + site-blurb = mkOption {
1119 + description = "Blurb for your site.";
1120 + type = types.str;
1121 + default = "the hacker's forge";
1122 + };
1123 + site-info = mkOption {
1124 + description = "The top-level info page for your site.";
1125 + type = types.str;
1126 + default = "https://sourcehut.org";
1127 + };
1128 + service-key = mkOption {
1129 + description = ''
1130 + An absolute file path (which should be outside the Nix-store)
1131 + to a key used for encrypting session cookies. Use <code>srht-keygen service</code> to
1132 + generate the service key. This must be shared between each node of the same
1133 + service (e.g. git1.sr.ht and git2.sr.ht), but different services may use
1134 + different keys. If you configure all of your services with the same
1135 + config.ini, you may use the same service-key for all of them.
1136 + '';
1137 + type = types.path;
1138 + apply = s: "<" + toString s;
1139 + };
1140 + site-name = mkOption {
1141 + description = "The name of your network of sr.ht-based sites.";
1142 + type = types.str;
1143 + default = "sourcehut";
1144 + };
1145 + source-url = mkOption {
1146 + description = "The source code for your fork of sr.ht.";
1147 + type = types.str;
1148 + default = "https://git.sr.ht/~sircmpwn/srht";
1149 + };
1150 + };
1151 + options.mail = {
1152 + smtp-host = mkOptionNullOrStr "Outgoing SMTP host.";
1153 + smtp-port = mkOption {
1154 + description = "Outgoing SMTP port.";
1155 + type = with types; nullOr port;
1156 + default = null;
1157 + };
1158 + smtp-user = mkOptionNullOrStr "Outgoing SMTP user.";
1159 + smtp-password = mkOptionNullOrStr "Outgoing SMTP password.";
1160 + smtp-from = mkOptionNullOrStr "Outgoing SMTP FROM.";
1161 + error-to = mkOptionNullOrStr "Address receiving application exceptions";
1162 + error-from = mkOptionNullOrStr "Address sending application exceptions";
1163 + pgp-privkey = mkOptionNullOrStr ''
1164 + An absolute file path (which should be outside the Nix-store)
1165 + to an OpenPGP private key.
1166 +
1167 + Your PGP key information (DO NOT mix up pub and priv here)
1168 + You must remove the password from your secret key, if present.
1169 + You can do this with <code>gpg --edit-key [key-id]</code>,
1170 + then use the <code>passwd</code> command and do not enter a new password.
1171 + '';
1172 + pgp-pubkey = mkOptionNullOrStr "OpenPGP public key.";
1173 + pgp-key-id = mkOptionNullOrStr "OpenPGP key identifier.";
1174 + };
1175 + options.objects = {
1176 + s3-upstream = mkOption {
1177 + description = "Configure the S3-compatible object storage service.";
1178 + type = with types; nullOr str;
1179 + default = null;
1180 + };
1181 + s3-access-key = mkOption {
1182 + description = "Access key to the S3-compatible object storage service";
1183 + type = with types; nullOr str;
1184 + default = null;
1185 + };
1186 + s3-secret-key = mkOption {
1187 + description = ''
1188 + An absolute file path (which should be outside the Nix-store)
1189 + to the secret key of the S3-compatible object storage service.
1190 + '';
1191 + type = with types; nullOr path;
1192 + default = null;
1193 + apply = mapNullable (s: "<" + toString s);
1194 + };
1195 + };
1196 + options.webhooks = {
1197 + private-key = mkOption {
1198 + description = ''
1199 + An absolute file path (which should be outside the Nix-store)
1200 + to a base64-encoded Ed25519 key for signing webhook payloads.
1201 + This should be consistent for all *.sr.ht sites,
1202 + as this key will be used to verify signatures
1203 + from other sites in your network.
1204 + Use the <code>srht-keygen webhook</code> command to generate a key.
1205 + '';
1206 + type = types.path;
1207 + apply = s: "<" + toString s;
1208 + };
1209 + };
1210 +
1211 + options."dispatch.sr.ht" = commonServiceSettings "dispatch" // {
1212 + };
1213 + options."dispatch.sr.ht::github" = {
1214 + oauth-client-id = mkOptionNullOrStr "OAuth client id.";
1215 + oauth-client-secret = mkOptionNullOrStr "OAuth client secret.";
1216 + };
1217 + options."dispatch.sr.ht::gitlab" = {
1218 + enabled = mkEnableOption "GitLab integration";
1219 + canonical-upstream = mkOption {
1220 + type = types.str;
1221 + description = "Canonical upstream.";
1222 + default = "gitlab.com";
1223 + };
1224 + repo-cache = mkOption {
1225 + type = types.str;
1226 + description = "Repository cache directory.";
1227 + default = "./repo-cache";
1228 + };
1229 + "gitlab.com" = mkOption {
1230 + type = with types; nullOr str;
1231 + description = "GitLab id and secret.";
1232 + default = null;
1233 + example = "GitLab:application id:secret";
1234 + };
1235 + };
1236 +
1237 + options."builds.sr.ht" = commonServiceSettings "builds" // {
1238 + allow-free = mkEnableOption "nonpaying users to submit builds";
1239 + redis = mkOption {
1240 + description = "The Redis connection used for the Celery worker.";
1241 + type = types.str;
1242 + default = "unix+socket:/run/redis-sourcehut-buildsrht/redis.sock?virtual_host=2";
1243 + };
1244 + shell = mkOption {
1245 + description = ''
1246 + Scripts used to launch on SSH connection.
1247 + <literal>/usr/bin/master-shell</literal> on master,
1248 + <literal>/usr/bin/runner-shell</literal> on runner.
1249 + If master and worker are on the same system
1250 + set to <literal>/usr/bin/runner-shell</literal>.
1251 + '';
1252 + type = types.enum ["/usr/bin/master-shell" "/usr/bin/runner-shell"];
1253 + default = "/usr/bin/master-shell";
1254 + };
1255 + };
1256 + options."builds.sr.ht::worker" = {
1257 + bind-address = mkOption {
1258 + description = ''
1259 + HTTP bind address for serving local build information/monitoring.
1260 + '';
1261 + type = types.str;
1262 + default = "localhost:8080";
1263 + };
1264 + buildlogs = mkOption {
1265 + description = "Path to write build logs.";
1266 + type = types.str;
1267 + default = "/var/log/sourcehut/buildsrht";
1268 + };
1269 + name = mkOption {
1270 + description = ''
1271 + Listening address and listening port
1272 + of the build runner (with HTTP port if not 80).
1273 + '';
1274 + type = types.str;
1275 + default = "localhost:5020";
1276 + };
1277 + timeout = mkOption {
1278 + description = ''
1279 + Max build duration.
1280 + See <link xlink:href="https://golang.org/pkg/time/#ParseDuration"/>.
1281 + '';
1282 + type = types.str;
1283 + default = "3m";
1284 + };
1285 + };
1286 +
1287 + options."git.sr.ht" = commonServiceSettings "git" // {
1288 + outgoing-domain = mkOption {
1289 + description = "Outgoing domain.";
1290 + type = types.str;
1291 + default = "https://git.localhost.localdomain";
1292 + };
1293 + post-update-script = mkOption {
1294 + description = ''
1295 + A post-update script which is installed in every git repo.
1296 + This setting is propagated to newer and existing repositories.
1297 + '';
1298 + type = types.path;
1299 + default = "${pkgs.sourcehut.gitsrht}/bin/gitsrht-update-hook";
1300 + defaultText = "\${pkgs.sourcehut.gitsrht}/bin/gitsrht-update-hook";
1301 + # Git hooks are run relative to their repository's directory,
1302 + # but gitsrht-update-hook looks up ../config.ini
1303 + apply = p: pkgs.writeShellScript "update-hook-wrapper" ''
1304 + test -e "''${PWD%/*}"/config.ini ||
1305 + ln -s ${users."sshsrht".home}/../config.ini "''${PWD%/*}"/config.ini
1306 + exec -a "$0" '${p}' "$@"
1307 + '';
1308 + };
1309 + repos = mkOption {
1310 + description = ''
1311 + Path to git repositories on disk.
1312 + If changing the default, you must ensure that
1313 + the gitsrht's user as read and write access to it.
1314 + '';
1315 + type = types.str;
1316 + default = "/var/lib/sourcehut/gitsrht/repos";
1317 + };
1318 + webhooks = mkOption {
1319 + description = "The Redis connection used for the webhooks worker.";
1320 + type = types.str;
1321 + default = "unix+socket:/run/redis-sourcehut-gitsrht/redis.sock?virtual_host=1";
1322 + };
1323 + };
1324 + options."git.sr.ht::api" = {
1325 + internal-ipnet = mkOption {
1326 + description = ''
1327 + Set of IP subnets which are permitted to utilize internal API
1328 + authentication. This should be limited to the subnets
1329 + from which your *.sr.ht services are running.
1330 + See <xref linkend="opt-services.sourcehut.listenAddress"/>.
1331 + '';
1332 + type = with types; listOf str;
1333 + default = [ "127.0.0.0/8" "::1/128" ];
1334 + };
1335 + };
1336 +
1337 + options."hg.sr.ht" = commonServiceSettings "hg" // {
1338 + changegroup-script = mkOption {
1339 + description = ''
1340 + A changegroup script which is installed in every mercurial repo.
1341 + This setting is propagated to newer and existing repositories.
1342 + '';
1343 + type = types.str;
1344 + default = "${cfg.python}/bin/hgsrht-hook-changegroup";
1345 + defaultText = "\${cfg.python}/bin/hgsrht-hook-changegroup";
1346 + # Mercurial's changegroup hooks are run relative to their repository's directory,
1347 + # but hgsrht-hook-changegroup looks up ./config.ini
1348 + apply = p: pkgs.writeShellScript "hook-changegroup-wrapper" ''
1349 + test -e "''$PWD"/config.ini ||
1350 + ln -s ${users."sshsrht".home}/../config.ini "''$PWD"/config.ini
1351 + exec -a "$0" '${p}' "$@"
1352 + '';
1353 + };
1354 + repos = mkOption {
1355 + description = ''
1356 + Path to mercurial repositories on disk.
1357 + If changing the default, you must ensure that
1358 + the hgsrht's user as read and write access to it.
1359 + '';
1360 + type = types.str;
1361 + default = "/var/lib/sourcehut/hgsrht/repos";
1362 + };
1363 + srhtext = mkOptionNullOrStr ''
1364 + Path to the srht mercurial extension
1365 + (defaults to where the hgsrht code is)
1366 + '';
1367 + clone_bundle_threshold = mkOption {
1368 + description = ".hg/store size (in MB) past which the nightly job generates clone bundles.";
1369 + type = types.ints.unsigned;
1370 + default = 50;
1371 + };
1372 + hg_ssh = mkOption {
1373 + description = "Path to hg-ssh (if not in $PATH).";
1374 + type = types.str;
1375 + default = "${pkgs.mercurial}/bin/hg-ssh";
1376 + defaultText = "\${pkgs.mercurial}/bin/hg-ssh";
1377 + };
1378 + webhooks = mkOption {
1379 + description = "The Redis connection used for the webhooks worker.";
1380 + type = types.str;
1381 + default = "unix+socket:/run/redis-sourcehut-hgsrht/redis.sock?virtual_host=1";
1382 + };
1383 + };
1384 +
1385 + options."hub.sr.ht" = commonServiceSettings "hub" // {
1386 + };
1387 +
1388 + options."lists.sr.ht" = commonServiceSettings "lists" // {
1389 + allow-new-lists = mkEnableOption "Allow creation of new lists.";
1390 + notify-from = mkOption {
1391 + description = "Outgoing email for notifications generated by users.";
1392 + type = types.str;
1393 + default = "lists-notify@localhost.localdomain";
1394 + };
1395 + posting-domain = mkOption {
1396 + description = "Posting domain.";
1397 + type = types.str;
1398 + default = "lists.localhost.localdomain";
1399 + };
1400 + redis = mkOption {
1401 + description = "The Redis connection used for the Celery worker.";
1402 + type = types.str;
1403 + default = "unix+socket:/run/redis-sourcehut-listssrht/redis.sock?virtual_host=2";
1404 + };
1405 + webhooks = mkOption {
1406 + description = "The Redis connection used for the webhooks worker.";
1407 + type = types.str;
1408 + default = "unix+socket:/run/redis-sourcehut-listssrht/redis.sock?virtual_host=1";
1409 + };
1410 + };
1411 + options."lists.sr.ht::worker" = {
1412 + reject-mimetypes = mkOption {
1413 + description = ''
1414 + Comma-delimited list of Content-Types to reject. Messages with Content-Types
1415 + included in this list are rejected. Multipart messages are always supported,
1416 + and each part is checked against this list.
1417 +
1418 + Uses fnmatch for wildcard expansion.
1419 + '';
1420 + type = with types; listOf str;
1421 + default = ["text/html"];
1422 + };
1423 + reject-url = mkOption {
1424 + description = "Reject URL.";
1425 + type = types.str;
1426 + default = "https://man.sr.ht/lists.sr.ht/etiquette.md";
1427 + };
1428 + sock = mkOption {
1429 + description = ''
1430 + Path for the lmtp daemon's unix socket. Direct incoming mail to this socket.
1431 + Alternatively, specify IP:PORT and an SMTP server will be run instead.
1432 + '';
1433 + type = types.str;
1434 + default = "/tmp/lists.sr.ht-lmtp.sock";
1435 + };
1436 + sock-group = mkOption {
1437 + description = ''
1438 + The lmtp daemon will make the unix socket group-read/write
1439 + for users in this group.
1440 + '';
1441 + type = types.str;
1442 + default = "postfix";
1443 + };
1444 + };
1445 +
1446 + options."man.sr.ht" = commonServiceSettings "man" // {
1447 + };
1448 +
1449 + options."meta.sr.ht" =
1450 + removeAttrs (commonServiceSettings "meta")
1451 + ["oauth-client-id" "oauth-client-secret"] // {
1452 + api-origin = mkOption {
1453 + description = "Origin URL for API, 100 more than web.";
1454 + type = types.str;
1455 + default = "http://${cfg.listenAddress}:${toString (cfg.meta.port + 100)}";
1456 + defaultText = ''http://<xref linkend="opt-services.sourcehut.listenAddress"/>:''${toString (<xref linkend="opt-services.sourcehut.meta.port"/> + 100)}'';
1457 + };
1458 + webhooks = mkOption {
1459 + description = "The Redis connection used for the webhooks worker.";
1460 + type = types.str;
1461 + default = "unix+socket:/run/redis-sourcehut-metasrht/redis.sock?virtual_host=1";
1462 + };
1463 + welcome-emails = mkEnableOption "sending stock sourcehut welcome emails after signup";
1464 + };
1465 + options."meta.sr.ht::api" = {
1466 + internal-ipnet = mkOption {
1467 + description = ''
1468 + Set of IP subnets which are permitted to utilize internal API
1469 + authentication. This should be limited to the subnets
1470 + from which your *.sr.ht services are running.
1471 + See <xref linkend="opt-services.sourcehut.listenAddress"/>.
1472 + '';
1473 + type = with types; listOf str;
1474 + default = [ "127.0.0.0/8" "::1/128" ];
1475 + };
1476 + };
1477 + options."meta.sr.ht::aliases" = mkOption {
1478 + description = "Aliases for the client IDs of commonly used OAuth clients.";
1479 + type = with types; attrsOf int;
1480 + default = {};
1481 + example = { "git.sr.ht" = 12345; };
1482 + };
1483 + options."meta.sr.ht::billing" = {
1484 + enabled = mkEnableOption "the billing system";
1485 + stripe-public-key = mkOptionNullOrStr "Public key for Stripe. Get your keys at https://dashboard.stripe.com/account/apikeys";
1486 + stripe-secret-key = mkOptionNullOrStr ''
1487 + An absolute file path (which should be outside the Nix-store)
1488 + to a secret key for Stripe. Get your keys at https://dashboard.stripe.com/account/apikeys
1489 + '' // {
1490 + apply = mapNullable (s: "<" + toString s);
1491 + };
1492 + };
1493 + options."meta.sr.ht::settings" = {
1494 + registration = mkEnableOption "public registration";
1495 + onboarding-redirect = mkOption {
1496 + description = "Where to redirect new users upon registration.";
1497 + type = types.str;
1498 + default = "https://meta.localhost.localdomain";
1499 + };
1500 + user-invites = mkOption {
1501 + description = ''
1502 + How many invites each user is issued upon registration
1503 + (only applicable if open registration is disabled).
1504 + '';
1505 + type = types.ints.unsigned;
1506 + default = 5;
1507 + };
1508 + };
1509 +
1510 + options."pages.sr.ht" = commonServiceSettings "pages" // {
1511 + gemini-certs = mkOption {
1512 + description = ''
1513 + An absolute file path (which should be outside the Nix-store)
1514 + to Gemini certificates.
1515 + '';
1516 + type = with types; nullOr path;
1517 + default = null;
1518 + };
1519 + max-site-size = mkOption {
1520 + description = "Maximum size of any given site (post-gunzip), in MiB.";
1521 + type = types.int;
1522 + default = 1024;
1523 + };
1524 + user-domain = mkOption {
1525 + description = ''
1526 + Configures the user domain, if enabled.
1527 + All users are given &lt;username&gt;.this.domain.
1528 + '';
1529 + type = with types; nullOr str;
1530 + default = null;
1531 + };
1532 + };
1533 + options."pages.sr.ht::api" = {
1534 + internal-ipnet = mkOption {
1535 + description = ''
1536 + Set of IP subnets which are permitted to utilize internal API
1537 + authentication. This should be limited to the subnets
1538 + from which your *.sr.ht services are running.
1539 + See <xref linkend="opt-services.sourcehut.listenAddress"/>.
1540 + '';
1541 + type = with types; listOf str;
1542 + default = [ "127.0.0.0/8" "::1/128" ];
1543 + };
1544 + };
1545 +
1546 + options."paste.sr.ht" = commonServiceSettings "paste" // {
1547 + };
1548 +
1549 + options."todo.sr.ht" = commonServiceSettings "todo" // {
1550 + notify-from = mkOption {
1551 + description = "Outgoing email for notifications generated by users.";
1552 + type = types.str;
1553 + default = "todo-notify@localhost.localdomain";
1554 + };
1555 + webhooks = mkOption {
1556 + description = "The Redis connection used for the webhooks worker.";
1557 + type = types.str;
1558 + default = "unix+socket:/run/redis-sourcehut-todosrht/redis.sock?virtual_host=1";
1559 + };
1560 + };
1561 + options."todo.sr.ht::mail" = {
1562 + posting-domain = mkOption {
1563 + description = "Posting domain.";
1564 + type = types.str;
1565 + default = "todo.localhost.localdomain";
1566 + };
1567 + sock = mkOption {
1568 + description = ''
1569 + Path for the lmtp daemon's unix socket. Direct incoming mail to this socket.
1570 + Alternatively, specify IP:PORT and an SMTP server will be run instead.
1571 + '';
1572 + type = types.str;
1573 + default = "/tmp/todo.sr.ht-lmtp.sock";
1574 + };
1575 + sock-group = mkOption {
1576 + description = ''
1577 + The lmtp daemon will make the unix socket group-read/write
1578 + for users in this group.
1579 + '';
1580 + type = types.str;
1581 + default = "postfix";
1582 + };
1583 + };
1584 };
1585 default = { };
1586 description = ''
1587 The configuration for the sourcehut network.
1588 '';
1589 };
1590 - };
1591
1592 - config = mkIf cfg.enable {
1593 - assertions =
1594 - [
1595 - {
1596 - assertion = with cfgIni.webhooks; private-key != null && stringLength private-key == 44;
1597 - message = "The webhook's private key must be defined and of a 44 byte length.";
1598 - }
1599 + builds = {
1600 + enableWorker = mkEnableOption "worker for builds.sr.ht";
1601
1602 - {
1603 - assertion = hasAttrByPath [ "meta.sr.ht" "origin" ] cfgIni && cfgIni."meta.sr.ht".origin != null;
1604 - message = "meta.sr.ht's origin must be defined.";
1605 - }
1606 - ];
1607 + images = mkOption {
1608 + type = with types; attrsOf (attrsOf (attrsOf package));
1609 + default = { };
1610 + example = lib.literalExample ''(let
1611 + # Pinning unstable to allow usage with flakes and limit rebuilds.
1612 + pkgs_unstable = builtins.fetchGit {
1613 + url = "https://github.com/NixOS/nixpkgs";
1614 + rev = "ff96a0fa5635770390b184ae74debea75c3fd534";
1615 + ref = "nixos-unstable";
1616 + };
1617 + image_from_nixpkgs = (import ("${pkgs.sourcehut.buildsrht}/lib/images/nixos/image.nix") {
1618 + pkgs = (import pkgs_unstable {});
1619 + });
1620 + in
1621 + {
1622 + nixos.unstable.x86_64 = image_from_nixpkgs;
1623 + }
1624 + )'';
1625 + description = ''
1626 + Images for builds.sr.ht. Each package should be distro.release.arch and point to a /nix/store/package/root.img.qcow2.
1627 + '';
1628 + };
1629 + };
1630
1631 - virtualisation.docker.enable = true;
1632 - environment.etc."sr.ht/config.ini".source =
1633 - settingsFormat.generate "sourcehut-config.ini" (mapAttrsRecursive
1634 - (
1635 - path: v: if v == null then "" else v
1636 - )
1637 - cfg.settings);
1638 + git = {
1639 + package = mkOption {
1640 + type = types.package;
1641 + default = pkgs.git;
1642 + example = literalExample "pkgs.gitFull";
1643 + description = ''
1644 + Git package for git.sr.ht. This can help silence collisions.
1645 + '';
1646 + };
1647 + fcgiwrap.preforkProcess = mkOption {
1648 + description = "Number of fcgiwrap processes to prefork.";
1649 + type = types.int;
1650 + default = 4;
1651 + };
1652 + };
1653
1654 - environment.systemPackages = [ pkgs.sourcehut.coresrht ];
1655 + hg = {
1656 + package = mkOption {
1657 + type = types.package;
1658 + default = pkgs.mercurial;
1659 + description = ''
1660 + Mercurial package for hg.sr.ht. This can help silence collisions.
1661 + '';
1662 + };
1663 + cloneBundles = mkOption {
1664 + type = types.bool;
1665 + default = false;
1666 + description = ''
1667 + Generate clonebundles (which require more disk space but dramatically speed up cloning large repositories).
1668 + '';
1669 + };
1670 + };
1671
1672 - # PostgreSQL server
1673 - services.postgresql.enable = mkOverride 999 true;
1674 - # Mail server
1675 - services.postfix.enable = mkOverride 999 true;
1676 - # Cron daemon
1677 - services.cron.enable = mkOverride 999 true;
1678 - # Redis server
1679 - services.redis.enable = mkOverride 999 true;
1680 - services.redis.bind = mkOverride 999 "127.0.0.1";
1681 -
1682 - services.sourcehut.settings = {
1683 - # The name of your network of sr.ht-based sites
1684 - "sr.ht".site-name = mkDefault "sourcehut";
1685 - # The top-level info page for your site
1686 - "sr.ht".site-info = mkDefault "https://sourcehut.org";
1687 - # {{ site-name }}, {{ site-blurb }}
1688 - "sr.ht".site-blurb = mkDefault "the hacker's forge";
1689 - # If this != production, we add a banner to each page
1690 - "sr.ht".environment = mkDefault "development";
1691 - # Contact information for the site owners
1692 - "sr.ht".owner-name = mkDefault "Drew DeVault";
1693 - "sr.ht".owner-email = mkDefault "sir@cmpwn.com";
1694 - # The source code for your fork of sr.ht
1695 - "sr.ht".source-url = mkDefault "https://git.sr.ht/~sircmpwn/srht";
1696 - # A secret key to encrypt session cookies with
1697 - "sr.ht".secret-key = mkDefault null;
1698 - "sr.ht".global-domain = mkDefault null;
1699 -
1700 - # Outgoing SMTP settings
1701 - mail.smtp-host = mkDefault null;
1702 - mail.smtp-port = mkDefault null;
1703 - mail.smtp-user = mkDefault null;
1704 - mail.smtp-password = mkDefault null;
1705 - mail.smtp-from = mkDefault null;
1706 - # Application exceptions are emailed to this address
1707 - mail.error-to = mkDefault null;
1708 - mail.error-from = mkDefault null;
1709 - # Your PGP key information (DO NOT mix up pub and priv here)
1710 - # You must remove the password from your secret key, if present.
1711 - # You can do this with gpg --edit-key [key-id], then use the passwd
1712 - # command and do not enter a new password.
1713 - mail.pgp-privkey = mkDefault null;
1714 - mail.pgp-pubkey = mkDefault null;
1715 - mail.pgp-key-id = mkDefault null;
1716 -
1717 - # base64-encoded Ed25519 key for signing webhook payloads. This should be
1718 - # consistent for all *.sr.ht sites, as we'll use this key to verify signatures
1719 - # from other sites in your network.
1720 - #
1721 - # Use the srht-webhook-keygen command to generate a key.
1722 - webhooks.private-key = mkDefault null;
1723 + lists = {
1724 + process = {
1725 + extraArgs = mkOption {
1726 + type = with types; listOf str;
1727 + default = [ "--loglevel DEBUG" "--pool eventlet" "--without-heartbeat" ];
1728 + description = "Extra arguments passed to the Celery responsible for processing mails.";
1729 + };
1730 + celeryConfig = mkOption {
1731 + type = types.lines;
1732 + default = "";
1733 + description = "Content of the <literal>celeryconfig.py</literal> used by the Celery of <literal>listssrht-process</literal>.";
1734 + };
1735 + };
1736 };
1737 };
1738 +
1739 + config = mkIf cfg.enable (mkMerge [
1740 + {
1741 + environment.systemPackages = [ pkgs.sourcehut.coresrht ];
1742 +
1743 + services.sourcehut.settings = {
1744 + "git.sr.ht".outgoing-domain = mkDefault "https://git.${domain}";
1745 + "lists.sr.ht".notify-from = mkDefault "lists-notify@${domain}";
1746 + "lists.sr.ht".posting-domain = mkDefault "lists.${domain}";
1747 + "meta.sr.ht::settings".onboarding-redirect = mkDefault "https://meta.${domain}";
1748 + "todo.sr.ht".notify-from = mkDefault "todo-notify@${domain}";
1749 + "todo.sr.ht::mail".posting-domain = mkDefault "todo.${domain}";
1750 + };
1751 + }
1752 + (mkIf cfg.postgresql.enable {
1753 + assertions = [
1754 + { assertion = postgresql.enable;
1755 + message = "postgresql must be enabled and configured";
1756 + }
1757 + ];
1758 + })
1759 + (mkIf cfg.postfix.enable {
1760 + assertions = [
1761 + { assertion = postfix.enable;
1762 + message = "postfix must be enabled and configured";
1763 + }
1764 + ];
1765 + # Needed for sharing the LMTP sockets with JoinsNamespaceOf=
1766 + systemd.services.postfix.serviceConfig.PrivateTmp = true;
1767 + })
1768 + (mkIf cfg.redis.enable {
1769 + services.redis.vmOverCommit = mkDefault true;
1770 + })
1771 + (mkIf cfg.nginx.enable {
1772 + assertions = [
1773 + { assertion = nginx.enable;
1774 + message = "nginx must be enabled and configured";
1775 + }
1776 + ];
1777 + # For proxyPass= in virtual-hosts for Sourcehut services.
1778 + services.nginx.recommendedProxySettings = mkDefault true;
1779 + })
1780 + (mkIf (cfg.builds.enable || cfg.git.enable || cfg.hg.enable) {
1781 + services.openssh = {
1782 + # Note that sshd will continue to honor AuthorizedKeysFile
1783 + # sshsrht-dispatch needs to read ${users."sshsrht".home}/../config.ini,
1784 + # Note that you may want automatically rotate
1785 + # or link to /dev/null the following log files:
1786 + # - /var/log/gitsrht-dispatch
1787 + # - /var/log/{build,git,hg}srht-keys
1788 + # - /var/log/{git,hg}srht-shell
1789 + # - /var/log/gitsrht-update-hook
1790 + authorizedKeysCommand = ''/etc/ssh/srht-dispatch "%u" "%h" "%t" "%k"'';
1791 + # srht-dispatch will setuid/setgid according to [git.sr.ht::dispatch]
1792 + authorizedKeysCommandUser = "root";
1793 + extraConfig = ''
1794 + PermitUserEnvironment SRHT_*
1795 + '';
1796 + };
1797 + environment.etc."ssh/srht-dispatch" = {
1798 + # sshd_config(5): The program must be owned by root, not writable by group or others
1799 + mode = "0755";
1800 + source = pkgs.writeShellScript "srht-dispatch" ''
1801 + set -e
1802 + cd ${users."sshsrht".home}
1803 + ${cfg.python}/bin/gitsrht-dispatch "$@"
1804 + '';
1805 + };
1806 + systemd.services.sshd = let
1807 + # TODO: use a filtered config.ini containing only [git.sr.ht::dispatch]
1808 + configIni = settingsFormat.generate "sourcehut-dispatch-config.ini"
1809 + # Each service needs access to only a subset of sections (and secrets).
1810 + (filterAttrs (k: v: k == "git.sr.ht::dispatch")
1811 + cfg.settings);
1812 + in {
1813 + #path = optional cfg.git.enable [ cfg.git.package ];
1814 + restartTriggers = [ configIni ];
1815 + serviceConfig = {
1816 + RuntimeDirectory = [ "sourcehut/sshsrht/subdir" ];
1817 + BindReadOnlyPaths =
1818 + # Note that those /usr/bin/* paths are hardcoded in multiple places in *.sr.ht,
1819 + # for instance to get the user from the [git.sr.ht::dispatch] settings.
1820 + # *srht-keys needs to:
1821 + # - access a redis-server in [sr.ht] redis-host,
1822 + # - access the PostgreSQL server in [*.sr.ht] connection-string,
1823 + # - query metasrht-api (through the HTTP API).
1824 + optionals cfg.builds.enable [
1825 + "${pkgs.writeShellScript "buildsrht-keys-wrapper" ''
1826 + set -ex
1827 + cd /run/sourcehut/buildsrht/subdir
1828 + exec ${pkgs.sourcehut.buildsrht}/bin/buildsrht-keys "$@"
1829 + ''}:/usr/bin/buildsrht-keys"
1830 + "${pkgs.sourcehut.buildsrht}/bin/master-shell:/usr/bin/master-shell"
1831 + "${pkgs.sourcehut.buildsrht}/bin/runner-shell:/usr/bin/runner-shell"
1832 + ] ++
1833 + optionals cfg.git.enable [
1834 + "${pkgs.writeShellScript "gitsrht-keys-wrapper" ''
1835 + set -ex
1836 + cd /run/sourcehut/gitsrht/subdir
1837 + exec ${pkgs.sourcehut.gitsrht}/bin/gitsrht-keys "$@"
1838 + ''}:/usr/bin/gitsrht-keys"
1839 + "${pkgs.sourcehut.gitsrht}/bin/gitsrht-shell:/usr/bin/gitsrht-shell"
1840 + ] ++
1841 + optionals cfg.hg.enable [
1842 + "${pkgs.writeShellScript "hgsrht-keys-wrapper" ''
1843 + set -ex
1844 + cd /run/sourcehut/hgsrht/subdir
1845 + exec ${pkgs.sourcehut.hgsrht}/bin/hgsrht-keys "$@"
1846 + ''}:/usr/bin/hgsrht-keys"
1847 + "${pkgs.sourcehut.hgsrht}/bin/hgsrht-shell:/usr/bin/hgsrht-shell"
1848 + ];
1849 + ExecStartPre = mkBefore [("+"+pkgs.writeShellScript "sshsrht-credentials" ''
1850 + # Replace values begining with a '<' by the content of the file whose name is after.
1851 + ${pkgs.gawk}/bin/gawk '{ if (match($0,/^([^=]+=)<(.+)/,m)) { getline f < m[2]; print m[1] f } else print $0 }' ${configIni} |
1852 + install -o ${users."sshsrht".name} -g ${groups."sshsrht".name} -m 440 \
1853 + /dev/stdin ${users."sshsrht".home}/../config.ini
1854 + '')];
1855 + };
1856 + };
1857 + users = {
1858 + users."sshsrht" = {
1859 + isSystemUser = true;
1860 + # srht-dispatch, *srht-keys, and *srht-shell
1861 + # look up in ../config.ini from this directory;
1862 + # that config.ini being set in *srht.service's ExecStartPre=
1863 + home = "/run/sourcehut/sshsrht/subdir";
1864 + group = groups.nogroup.name;
1865 + description = "sourcehut user for sshd's AuthorizedKeysCommandUser";
1866 + };
1867 + groups."sshsrht" = {};
1868 + };
1869 + })
1870 + ]);
1871 +
1872 + imports = [
1873 +
1874 + (import ./service.nix "builds" {
1875 + inherit configIniOfService;
1876 + srvsrht = "buildsrht";
1877 + port = 5002;
1878 + # TODO: a celery worker on the master and worker are apparently needed
1879 + extraServices.buildsrht-worker = let
1880 + qemuPackage = pkgs.qemu_kvm;
1881 + serviceName = "buildsrht-worker";
1882 + statePath = "/var/lib/sourcehut/${serviceName}";
1883 + in mkIf cfg.builds.enableWorker {
1884 + path = [ pkgs.openssh pkgs.docker ];
1885 + preStart = ''
1886 + set -x
1887 + if test -z "$(docker images -q qemu:latest 2>/dev/null)" \
1888 + || test "$(cat ${statePath}/docker-image-qemu)" != "${qemuPackage.version}"
1889 + then
1890 + # Create and import qemu:latest image for docker
1891 + ${pkgs.dockerTools.streamLayeredImage {
1892 + name = "qemu";
1893 + tag = "latest";
1894 + contents = [ qemuPackage ];
1895 + }} | docker load
1896 + # Mark down current package version
1897 + echo '${qemuPackage.version}' >${statePath}/docker-image-qemu
1898 + fi
1899 + '';
1900 + serviceConfig = {
1901 + ExecStart = "${pkgs.sourcehut.buildsrht}/bin/builds.sr.ht-worker";
1902 + RuntimeDirectory = [ "sourcehut/${serviceName}/subdir" ];
1903 + # builds.sr.ht-worker looks up ../config.ini
1904 + LogsDirectory = [ "sourcehut/${serviceName}" ];
1905 + StateDirectory = [ "sourcehut/${serviceName}" ];
1906 + WorkingDirectory = "-"+"/run/sourcehut/${serviceName}/subdir";
1907 + };
1908 + };
1909 + extraConfig = let
1910 + image_dirs = flatten (
1911 + mapAttrsToList (distro: revs:
1912 + mapAttrsToList (rev: archs:
1913 + mapAttrsToList (arch: image:
1914 + pkgs.runCommand "buildsrht-images" { } ''
1915 + mkdir -p $out/${distro}/${rev}/${arch}
1916 + ln -s ${image}/*.qcow2 $out/${distro}/${rev}/${arch}/root.img.qcow2
1917 + ''
1918 + ) archs
1919 + ) revs
1920 + ) cfg.builds.images
1921 + );
1922 + image_dir_pre = pkgs.symlinkJoin {
1923 + name = "builds.sr.ht-worker-images-pre";
1924 + paths = image_dirs;
1925 + # FIXME: not working, apparently because ubuntu/latest is a broken link
1926 + # ++ [ "${pkgs.sourcehut.buildsrht}/lib/images" ];
1927 + };
1928 + image_dir = pkgs.runCommand "builds.sr.ht-worker-images" { } ''
1929 + mkdir -p $out/images
1930 + cp -Lr ${image_dir_pre}/* $out/images
1931 + '';
1932 + in mkMerge [
1933 + {
1934 + users.users.${cfg.builds.user} = {
1935 + shell = pkgs.bash;
1936 + # Allow reading of ${users."sshsrht".home}/../config.ini
1937 + extraGroups = [ groups."sshsrht".name ];
1938 + };
1939 +
1940 + virtualisation.docker.enable = true;
1941 +
1942 + services.sourcehut.settings = mkMerge [
1943 + { # Note that git.sr.ht::dispatch is not a typo,
1944 + # gitsrht-dispatch always use this section
1945 + "git.sr.ht::dispatch"."/usr/bin/buildsrht-keys" =
1946 + mkDefault "${cfg.builds.user}:${cfg.builds.group}";
1947 + }
1948 + (mkIf cfg.builds.enableWorker {
1949 + "builds.sr.ht::worker".shell = "/usr/bin/runner-shell";
1950 + "builds.sr.ht::worker".images = mkDefault "${image_dir}/images";
1951 + "builds.sr.ht::worker".controlcmd = mkDefault "${image_dir}/images/control";
1952 + })
1953 + ];
1954 + }
1955 + (mkIf cfg.builds.enableWorker {
1956 + users.groups = {
1957 + docker.members = [ cfg.builds.user ];
1958 + };
1959 + })
1960 + (mkIf (cfg.builds.enableWorker && cfg.nginx.enable) {
1961 + # Allow nginx access to buildlogs
1962 + users.users.${nginx.user}.extraGroups = [ cfg.builds.group ];
1963 + systemd.services.nginx = {
1964 + serviceConfig.BindReadOnlyPaths = [ "${cfg.settings."builds.sr.ht::worker".buildlogs}:/var/log/nginx/buildsrht/logs" ];
1965 + };
1966 + services.nginx.virtualHosts."logs.${domain}" = mkMerge [ {
1967 + /* FIXME: is a listen needed?
1968 + listen = with builtins;
1969 + # FIXME: not compatible with IPv6
1970 + let address = split ":" cfg.settings."builds.sr.ht::worker".name; in
1971 + [{ addr = elemAt address 0; port = lib.toInt (elemAt address 2); }];
1972 + */
1973 + locations."/logs/".alias = "/var/log/nginx/buildsrht/logs/";
1974 + } cfg.nginx.virtualHost ];
1975 + })
1976 + ];
1977 + })
1978 +
1979 + (import ./service.nix "dispatch" {
1980 + inherit configIniOfService;
1981 + port = 5005;
1982 + })
1983 +
1984 + (import ./service.nix "git" (let
1985 + baseService = {
1986 + path = [ cfg.git.package ];
1987 + serviceConfig.BindPaths = [ "${cfg.settings."git.sr.ht".repos}:/var/lib/sourcehut/gitsrht/repos" ];
1988 + serviceConfig.BindReadOnlyPaths = [ "${cfg.settings."git.sr.ht".post-update-script}:/var/lib/sourcehut/gitsrht/bin/post-update-script" ];
1989 + };
1990 + in {
1991 + inherit configIniOfService;
1992 + mainService = mkMerge [ baseService {
1993 + serviceConfig.StateDirectory = [ "sourcehut/gitsrht" "sourcehut/gitsrht/repos" ];
1994 + } ];
1995 + port = 5001;
1996 + webhooks = true;
1997 + extraTimers.gitsrht-periodic = {
1998 + service = baseService;
1999 + timerConfig.OnCalendar = ["20min"];
2000 + };
2001 + extraConfig = mkMerge [
2002 + {
2003 + users.users.${cfg.git.user} = {
2004 + # https://stackoverflow.com/questions/22314298/git-push-results-in-fatal-protocol-error-bad-line-length-character-this
2005 + # Probably could use gitsrht-shell if output is restricted to just parameters...
2006 + shell = pkgs.bash;
2007 + # Allow reading of ${users."sshsrht".home}/../config.ini
2008 + extraGroups = [ groups."sshsrht".name ];
2009 + home = users.sshsrht.home;
2010 + };
2011 + services.sourcehut.settings = {
2012 + "git.sr.ht::dispatch"."/usr/bin/gitsrht-keys" =
2013 + mkDefault "${cfg.git.user}:${cfg.git.group}";
2014 + };
2015 + systemd.services.sshd = baseService;
2016 + }
2017 + (mkIf cfg.nginx.enable {
2018 + services.nginx.virtualHosts."git.${domain}" = {
2019 + locations."/authorize" = {
2020 + proxyPass = "http://${cfg.listenAddress}:${toString cfg.git.port}";
2021 + extraConfig = ''
2022 + proxy_pass_request_body off;
2023 + proxy_set_header Content-Length "";
2024 + proxy_set_header X-Original-URI $request_uri;
2025 + '';
2026 + };
2027 + locations."~ ^/([^/]+)/([^/]+)/(HEAD|info/refs|objects/info/.*|git-upload-pack).*$" = {
2028 + root = "/var/lib/sourcehut/gitsrht/repos";
2029 + fastcgiParams = {
2030 + GIT_HTTP_EXPORT_ALL = "";
2031 + GIT_PROJECT_ROOT = "$document_root";
2032 + PATH_INFO = "$uri";
2033 + SCRIPT_FILENAME = "${cfg.git.package}/bin/git-http-backend";
2034 + };
2035 + extraConfig = ''
2036 + auth_request /authorize;
2037 + fastcgi_read_timeout 500s;
2038 + fastcgi_pass unix:/run/gitsrht-fcgiwrap.sock;
2039 + gzip off;
2040 + '';
2041 + };
2042 + };
2043 + systemd.sockets.gitsrht-fcgiwrap = {
2044 + before = [ "nginx.service" ];
2045 + wantedBy = [ "sockets.target" "gitsrht.service" ];
2046 + # This path remains accessible to nginx.service, which has no RootDirectory=
2047 + socketConfig.ListenStream = "/run/gitsrht-fcgiwrap.sock";
2048 + socketConfig.SocketUser = nginx.user;
2049 + socketConfig.SocketMode = "600";
2050 + };
2051 + })
2052 + ];
2053 + extraServices.gitsrht-fcgiwrap = mkIf cfg.nginx.enable {
2054 + serviceConfig = {
2055 + # Socket is passed by gitsrht-fcgiwrap.socket
2056 + ExecStart = "${pkgs.fcgiwrap}/sbin/fcgiwrap -c ${toString cfg.git.fcgiwrap.preforkProcess}";
2057 + # No need for config.ini
2058 + ExecStartPre = mkForce [];
2059 + User = null;
2060 + DynamicUser = true;
2061 + BindReadOnlyPaths = [ "${cfg.settings."git.sr.ht".repos}:/var/lib/sourcehut/gitsrht/repos" ];
2062 + IPAddressDeny = "any";
2063 + InaccessiblePaths = [ "-+/run/postgresql" "-+/run/redis-sourcehut" ];
2064 + PrivateNetwork = true;
2065 + RestrictAddressFamilies = mkForce [ "none" ];
2066 + SystemCallFilter = mkForce [
2067 + "@system-service"
2068 + "~@aio" "~@keyring" "~@memlock" "~@privileged" "~@resources" "~@setuid"
2069 + # @timer is needed for alarm()
2070 + ];
2071 + };
2072 + };
2073 + }))
2074 +
2075 + (import ./service.nix "hg" (let
2076 + baseService = {
2077 + path = [ cfg.hg.package ];
2078 + serviceConfig.BindPaths = [ "${cfg.settings."hg.sr.ht".repos}:/var/lib/sourcehut/hgsrht/repos" ];
2079 + serviceConfig.BindReadOnlyPaths = [ "${cfg.settings."ht.sr.ht".changegroup-script}:/var/lib/sourcehut/hgsrht/bin/changegroup-script" ];
2080 + };
2081 + in {
2082 + inherit configIniOfService;
2083 + mainService = mkMerge [ baseService {
2084 + serviceConfig.StateDirectory = [ "sourcehut/hgsrht" "sourcehut/hgsrht/repos" ];
2085 + } ];
2086 + port = 5010;
2087 + webhooks = true;
2088 + extraTimers.hgsrht-periodic = {
2089 + service = baseService;
2090 + timerConfig.OnCalendar = ["20min"];
2091 + };
2092 + extraTimers.hgsrht-clonebundles = mkIf cfg.hg.cloneBundles {
2093 + service = baseService;
2094 + timerConfig.OnCalendar = ["daily"];
2095 + timerConfig.AccuracySec = "1h";
2096 + };
2097 + extraConfig = mkMerge [
2098 + {
2099 + users.users.${cfg.hg.user} = {
2100 + shell = pkgs.bash;
2101 + # Allow reading of ${users."sshsrht".home}/../config.ini
2102 + extraGroups = [ groups."sshsrht".name ];
2103 + };
2104 + services.sourcehut.settings = {
2105 + # Note that git.sr.ht::dispatch is not a typo,
2106 + # gitsrht-dispatch always uses this section.
2107 + "git.sr.ht::dispatch"."/usr/bin/hgsrht-keys" =
2108 + mkDefault "${cfg.hg.user}:${cfg.hg.group}";
2109 + };
2110 + systemd.services.sshd = baseService;
2111 + }
2112 + (mkIf cfg.nginx.enable {
2113 + # Allow nginx access to repositories
2114 + users.users.${nginx.user}.extraGroups = [ cfg.hg.group ];
2115 + services.nginx.virtualHosts."hg.${domain}" = {
2116 + locations."/authorize" = {
2117 + proxyPass = "http://${cfg.listenAddress}:${toString cfg.hg.port}";
2118 + extraConfig = ''
2119 + proxy_pass_request_body off;
2120 + proxy_set_header Content-Length "";
2121 + proxy_set_header X-Original-URI $request_uri;
2122 + '';
2123 + };
2124 + # Let clients reach pull bundles. We don't really need to lock this down even for
2125 + # private repos because the bundles are named after the revision hashes...
2126 + # so someone would need to know or guess a SHA value to download anything.
2127 + # TODO: proxyPass to an hg serve service?
2128 + locations."~ ^/[~^][a-z0-9_]+/[a-zA-Z0-9_.-]+/\\.hg/bundles/.*$" = {
2129 + root = "/var/lib/nginx/hgsrht/repos";
2130 + extraConfig = ''
2131 + auth_request /authorize;
2132 + gzip off;
2133 + '';
2134 + };
2135 + };
2136 + systemd.services.nginx = {
2137 + serviceConfig.BindReadOnlyPaths = [ "${cfg.settings."hg.sr.ht".repos}:/var/lib/nginx/hgsrht/repos" ];
2138 + };
2139 + })
2140 + ];
2141 + }))
2142 +
2143 + (import ./service.nix "hub" {
2144 + inherit configIniOfService;
2145 + port = 5014;
2146 + extraConfig = {
2147 + services.nginx = mkIf cfg.nginx.enable {
2148 + virtualHosts."hub.${domain}" = mkMerge [ {
2149 + serverAliases = [ domain ];
2150 + } cfg.nginx.virtualHost ];
2151 + };
2152 + };
2153 + })
2154 +
2155 + (import ./service.nix "lists" (let
2156 + srvsrht = "listssrht";
2157 + in {
2158 + inherit configIniOfService;
2159 + port = 5006;
2160 + webhooks = true;
2161 + # Receive the mail from Postfix and enqueue them into Redis and PostgreSQL
2162 + extraServices.listssrht-lmtp = {
2163 + wants = [ "postfix.service" ];
2164 + unitConfig.JoinsNamespaceOf = optional cfg.postfix.enable "postfix.service";
2165 + serviceConfig.ExecStart = "${cfg.python}/bin/listssrht-lmtp";
2166 + # Avoid crashing: os.chown(sock, os.getuid(), sock_gid)
2167 + serviceConfig.PrivateUsers = mkForce false;
2168 + };
2169 + # Dequeue the mails from Redis and dispatch them
2170 + extraServices.listssrht-process = {
2171 + serviceConfig = {
2172 + preStart = ''
2173 + cp ${pkgs.writeText "${srvsrht}-webhooks-celeryconfig.py" cfg.lists.process.celeryConfig} \
2174 + /run/sourcehut/${srvsrht}-webhooks/celeryconfig.py
2175 + '';
2176 + ExecStart = "${cfg.python}/bin/celery --app listssrht.process worker --hostname listssrht-process@%%h " + concatStringsSep " " cfg.lists.process.extraArgs;
2177 + # Avoid crashing: os.getloadavg()
2178 + ProcSubset = mkForce "all";
2179 + };
2180 + };
2181 + extraConfig = mkIf cfg.postfix.enable {
2182 + users.groups.${postfix.group}.members = [ cfg.lists.user ];
2183 + services.sourcehut.settings."lists.sr.ht::mail".sock-group = postfix.group;
2184 + services.postfix = {
2185 + destination = [ "lists.${domain}" ];
2186 + # FIXME: an accurate recipient list should be queried
2187 + # from the lists.sr.ht PostgreSQL database to avoid backscattering.
2188 + # But usernames are unfortunately not in that database but in meta.sr.ht.
2189 + # Note that two syntaxes are allowed:
2190 + # - ~username/list-name@lists.${domain}
2191 + # - u.username.list-name@lists.${domain}
2192 + localRecipients = [ "@lists.${domain}" ];
2193 + transport = ''
2194 + lists.${domain} lmtp:unix:${cfg.settings."lists.sr.ht::worker".sock}
2195 + '';
2196 + };
2197 + };
2198 + }))
2199 +
2200 + (import ./service.nix "man" {
2201 + inherit configIniOfService;
2202 + port = 5004;
2203 + })
2204 +
2205 + (import ./service.nix "meta" {
2206 + inherit configIniOfService;
2207 + port = 5000;
2208 + webhooks = true;
2209 + extraServices.metasrht-api = {
2210 + serviceConfig.Restart = "always";
2211 + serviceConfig.RestartSec = "2s";
2212 + preStart = "set -x\n" + concatStringsSep "\n\n" (attrValues (mapAttrs (k: s:
2213 + let srvMatch = builtins.match "^([a-z]*)\\.sr\\.ht$" k;
2214 + srv = head srvMatch;
2215 + in
2216 + # Configure client(s) as "preauthorized"
2217 + optionalString (srvMatch != null && cfg.${srv}.enable && ((s.oauth-client-id or null) != null)) ''
2218 + # Configure ${srv}'s OAuth client as "preauthorized"
2219 + ${postgresql.package}/bin/psql '${cfg.settings."meta.sr.ht".connection-string}' \
2220 + -c "UPDATE oauthclient SET preauthorized = true WHERE client_id = '${s.oauth-client-id}'"
2221 + ''
2222 + ) cfg.settings));
2223 + serviceConfig.ExecStart = "${pkgs.sourcehut.metasrht}/bin/metasrht-api -b ${cfg.listenAddress}:${toString (cfg.meta.port + 100)}";
2224 + };
2225 + extraTimers.metasrht-daily.timerConfig = {
2226 + OnCalendar = ["daily"];
2227 + AccuracySec = "1h";
2228 + };
2229 + extraConfig = mkMerge [
2230 + {
2231 + assertions = [
2232 + { assertion = let s = cfg.settings."meta.sr.ht::billing"; in
2233 + s.enabled == "yes" -> (s.stripe-public-key != null && s.stripe-secret-key != null);
2234 + message = "If meta.sr.ht::billing is enabled, the keys must be defined.";
2235 + }
2236 + ];
2237 + environment.systemPackages = optional cfg.meta.enable
2238 + (pkgs.writeShellScriptBin "metasrht-manageuser" ''
2239 + set -eux
2240 + if test "$(${pkgs.coreutils}/bin/id -n -u)" != '${cfg.meta.user}'
2241 + then exec sudo -u '${cfg.meta.user}' "$0" "$@"
2242 + else
2243 + # In order to load config.ini
2244 + if cd /run/sourcehut/metasrht
2245 + then exec ${cfg.python}/bin/metasrht-manageuser "$@"
2246 + else cat <<EOF
2247 + Please run: sudo systemctl start metasrht
2248 + EOF
2249 + exit 1
2250 + fi
2251 + fi
2252 + '');
2253 + }
2254 + (mkIf cfg.nginx.enable {
2255 + services.nginx.virtualHosts."meta.${domain}" = {
2256 + locations."/query" = {
2257 + proxyPass = cfg.settings."meta.sr.ht".api-origin;
2258 + extraConfig = ''
2259 + if ($request_method = 'OPTIONS') {
2260 + add_header 'Access-Control-Allow-Origin' '*';
2261 + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
2262 + add_header 'Access-Control-Allow-Headers' 'User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
2263 + add_header 'Access-Control-Max-Age' 1728000;
2264 + add_header 'Content-Type' 'text/plain; charset=utf-8';
2265 + add_header 'Content-Length' 0;
2266 + return 204;
2267 + }
2268 +
2269 + add_header 'Access-Control-Allow-Origin' '*';
2270 + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
2271 + add_header 'Access-Control-Allow-Headers' 'User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
2272 + add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
2273 + '';
2274 + };
2275 + };
2276 + })
2277 + ];
2278 + })
2279 +
2280 + (import ./service.nix "pages" {
2281 + inherit configIniOfService;
2282 + port = 5112;
2283 + mainService = let
2284 + srvsrht = "pagessrht";
2285 + version = pkgs.sourcehut.${srvsrht}.version;
2286 + stateDir = "/var/lib/sourcehut/${srvsrht}";
2287 + iniKey = "pages.sr.ht";
2288 + in {
2289 + preStart = mkBefore ''
2290 + set -x
2291 + # Use the /run/sourcehut/${srvsrht}/config.ini
2292 + # installed by a previous ExecStartPre= in baseService
2293 + cd /run/sourcehut/${srvsrht}
2294 +
2295 + if test ! -e ${stateDir}/db; then
2296 + ${postgresql.package}/bin/psql '${cfg.settings.${iniKey}.connection-string}' -f ${pkgs.sourcehut.pagessrht}/share/sql/schema.sql
2297 + echo ${version} >${stateDir}/db
2298 + fi
2299 +
2300 + ${optionalString cfg.settings.${iniKey}.migrate-on-upgrade ''
2301 + # Just try all the migrations because they're not linked to the version
2302 + for sql in ${pkgs.sourcehut.pagessrht}/share/sql/migrations/*.sql; do
2303 + ${postgresql.package}/bin/psql '${cfg.settings.${iniKey}.connection-string}' -f "$sql" || true
2304 + done
2305 + ''}
2306 +
2307 + # Disable webhook
2308 + touch ${stateDir}/webhook
2309 + '';
2310 + serviceConfig = {
2311 + ExecStart = mkForce "${pkgs.sourcehut.pagessrht}/bin/pages.sr.ht -b ${cfg.listenAddress}:${toString cfg.pages.port}";
2312 + };
2313 + };
2314 + })
2315 +
2316 + (import ./service.nix "paste" {
2317 + inherit configIniOfService;
2318 + port = 5011;
2319 + })
2320 +
2321 + (import ./service.nix "todo" {
2322 + inherit configIniOfService;
2323 + port = 5003;
2324 + webhooks = true;
2325 + extraServices.todosrht-lmtp = {
2326 + wants = [ "postfix.service" ];
2327 + unitConfig.JoinsNamespaceOf = optional cfg.postfix.enable "postfix.service";
2328 + serviceConfig.ExecStart = "${cfg.python}/bin/todosrht-lmtp";
2329 + # Avoid crashing: os.chown(sock, os.getuid(), sock_gid)
2330 + serviceConfig.PrivateUsers = mkForce false;
2331 + };
2332 + extraConfig = mkIf cfg.postfix.enable {
2333 + users.groups.${postfix.group}.members = [ cfg.todo.user ];
2334 + services.sourcehut.settings."todo.sr.ht::mail".sock-group = postfix.group;
2335 + services.postfix.transport = ''
2336 + todo.${domain} lmtp:unix:${cfg.settings."todo.sr.ht::mail".sock}
2337 + '';
2338 + };
2339 + })
2340 +
2341 + (mkRenamedOptionModule [ "services" "sourcehut" "originBase" ]
2342 + [ "services" "sourcehut" "settings" "sr.ht" "global-domain" ])
2343 + (mkRenamedOptionModule [ "services" "sourcehut" "address" ]
2344 + [ "services" "sourcehut" "listenAddress" ])
2345 +
2346 + ];
2347 +
2348 meta.doc = ./sourcehut.xml;
2349 - meta.maintainers = with maintainers; [ tomberek ];
2350 + meta.maintainers = with maintainers; [ julm tomberek ];
2351 }
2352 diff --git a/nixos/modules/services/misc/sourcehut/dispatch.nix b/nixos/modules/services/misc/sourcehut/dispatch.nix
2353 deleted file mode 100644
2354 index a9db17bebe8..00000000000
2355 --- a/nixos/modules/services/misc/sourcehut/dispatch.nix
2356 +++ /dev/null
2357 @@ -1,125 +0,0 @@
2358 -{ config, lib, pkgs, ... }:
2359 -
2360 -with lib;
2361 -let
2362 - cfg = config.services.sourcehut;
2363 - cfgIni = cfg.settings;
2364 - scfg = cfg.dispatch;
2365 - iniKey = "dispatch.sr.ht";
2366 -
2367 - drv = pkgs.sourcehut.dispatchsrht;
2368 -in
2369 -{
2370 - options.services.sourcehut.dispatch = {
2371 - user = mkOption {
2372 - type = types.str;
2373 - default = "dispatchsrht";
2374 - description = ''
2375 - User for dispatch.sr.ht.
2376 - '';
2377 - };
2378 -
2379 - port = mkOption {
2380 - type = types.port;
2381 - default = 5005;
2382 - description = ''
2383 - Port on which the "dispatch" module should listen.
2384 - '';
2385 - };
2386 -
2387 - database = mkOption {
2388 - type = types.str;
2389 - default = "dispatch.sr.ht";
2390 - description = ''
2391 - PostgreSQL database name for dispatch.sr.ht.
2392 - '';
2393 - };
2394 -
2395 - statePath = mkOption {
2396 - type = types.path;
2397 - default = "${cfg.statePath}/dispatchsrht";
2398 - description = ''
2399 - State path for dispatch.sr.ht.
2400 - '';
2401 - };
2402 - };
2403 -
2404 - config = with scfg; lib.mkIf (cfg.enable && elem "dispatch" cfg.services) {
2405 -
2406 - users = {
2407 - users = {
2408 - "${user}" = {
2409 - isSystemUser = true;
2410 - group = user;
2411 - description = "dispatch.sr.ht user";
2412 - };
2413 - };
2414 -
2415 - groups = {
2416 - "${user}" = { };
2417 - };
2418 - };
2419 -
2420 - services.postgresql = {
2421 - authentication = ''
2422 - local ${database} ${user} trust
2423 - '';
2424 - ensureDatabases = [ database ];
2425 - ensureUsers = [
2426 - {
2427 - name = user;
2428 - ensurePermissions = { "DATABASE \"${database}\"" = "ALL PRIVILEGES"; };
2429 - }
2430 - ];
2431 - };
2432 -
2433 - systemd = {
2434 - tmpfiles.rules = [
2435 - "d ${statePath} 0750 ${user} ${user} -"
2436 - ];
2437 -
2438 - services.dispatchsrht = import ./service.nix { inherit config pkgs lib; } scfg drv iniKey {
2439 - after = [ "postgresql.service" "network.target" ];
2440 - requires = [ "postgresql.service" ];
2441 - wantedBy = [ "multi-user.target" ];
2442 -
2443 - description = "dispatch.sr.ht website service";
2444 -
2445 - serviceConfig.ExecStart = "${cfg.python}/bin/gunicorn ${drv.pname}.app:app -b ${cfg.address}:${toString port}";
2446 - };
2447 - };
2448 -
2449 - services.sourcehut.settings = {
2450 - # URL dispatch.sr.ht is being served at (protocol://domain)
2451 - "dispatch.sr.ht".origin = mkDefault "http://dispatch.${cfg.originBase}";
2452 - # Address and port to bind the debug server to
2453 - "dispatch.sr.ht".debug-host = mkDefault "0.0.0.0";
2454 - "dispatch.sr.ht".debug-port = mkDefault port;
2455 - # Configures the SQLAlchemy connection string for the database.
2456 - "dispatch.sr.ht".connection-string = mkDefault "postgresql:///${database}?user=${user}&host=/var/run/postgresql";
2457 - # Set to "yes" to automatically run migrations on package upgrade.
2458 - "dispatch.sr.ht".migrate-on-upgrade = mkDefault "yes";
2459 - # dispatch.sr.ht's OAuth client ID and secret for meta.sr.ht
2460 - # Register your client at meta.example.org/oauth
2461 - "dispatch.sr.ht".oauth-client-id = mkDefault null;
2462 - "dispatch.sr.ht".oauth-client-secret = mkDefault null;
2463 -
2464 - # Github Integration
2465 - "dispatch.sr.ht::github".oauth-client-id = mkDefault null;
2466 - "dispatch.sr.ht::github".oauth-client-secret = mkDefault null;
2467 -
2468 - # Gitlab Integration
2469 - "dispatch.sr.ht::gitlab".enabled = mkDefault null;
2470 - "dispatch.sr.ht::gitlab".canonical-upstream = mkDefault "gitlab.com";
2471 - "dispatch.sr.ht::gitlab".repo-cache = mkDefault "./repo-cache";
2472 - # "dispatch.sr.ht::gitlab"."gitlab.com" = mkDefault "GitLab:application id:secret";
2473 - };
2474 -
2475 - services.nginx.virtualHosts."dispatch.${cfg.originBase}" = {
2476 - forceSSL = true;
2477 - locations."/".proxyPass = "http://${cfg.address}:${toString port}";
2478 - locations."/query".proxyPass = "http://${cfg.address}:${toString (port + 100)}";
2479 - locations."/static".root = "${pkgs.sourcehut.dispatchsrht}/${pkgs.sourcehut.python.sitePackages}/dispatchsrht";
2480 - };
2481 - };
2482 -}
2483 diff --git a/nixos/modules/services/misc/sourcehut/git.nix b/nixos/modules/services/misc/sourcehut/git.nix
2484 deleted file mode 100644
2485 index 99b9aec0612..00000000000
2486 --- a/nixos/modules/services/misc/sourcehut/git.nix
2487 +++ /dev/null
2488 @@ -1,214 +0,0 @@
2489 -{ config, lib, pkgs, ... }:
2490 -
2491 -with lib;
2492 -let
2493 - cfg = config.services.sourcehut;
2494 - scfg = cfg.git;
2495 - iniKey = "git.sr.ht";
2496 -
2497 - rcfg = config.services.redis;
2498 - drv = pkgs.sourcehut.gitsrht;
2499 -in
2500 -{
2501 - options.services.sourcehut.git = {
2502 - user = mkOption {
2503 - type = types.str;
2504 - visible = false;
2505 - internal = true;
2506 - readOnly = true;
2507 - default = "git";
2508 - description = ''
2509 - User for git.sr.ht.
2510 - '';
2511 - };
2512 -
2513 - port = mkOption {
2514 - type = types.port;
2515 - default = 5001;
2516 - description = ''
2517 - Port on which the "git" module should listen.
2518 - '';
2519 - };
2520 -
2521 - database = mkOption {
2522 - type = types.str;
2523 - default = "git.sr.ht";
2524 - description = ''
2525 - PostgreSQL database name for git.sr.ht.
2526 - '';
2527 - };
2528 -
2529 - statePath = mkOption {
2530 - type = types.path;
2531 - default = "${cfg.statePath}/gitsrht";
2532 - description = ''
2533 - State path for git.sr.ht.
2534 - '';
2535 - };
2536 -
2537 - package = mkOption {
2538 - type = types.package;
2539 - default = pkgs.git;
2540 - example = literalExample "pkgs.gitFull";
2541 - description = ''
2542 - Git package for git.sr.ht. This can help silence collisions.
2543 - '';
2544 - };
2545 - };
2546 -
2547 - config = with scfg; lib.mkIf (cfg.enable && elem "git" cfg.services) {
2548 - # sshd refuses to run with `Unsafe AuthorizedKeysCommand ... bad ownership or modes for directory /nix/store`
2549 - environment.etc."ssh/gitsrht-dispatch" = {
2550 - mode = "0755";
2551 - text = ''
2552 - #! ${pkgs.stdenv.shell}
2553 - ${cfg.python}/bin/gitsrht-dispatch "$@"
2554 - '';
2555 - };
2556 -
2557 - # Needs this in the $PATH when sshing into the server
2558 - environment.systemPackages = [ cfg.git.package ];
2559 -
2560 - users = {
2561 - users = {
2562 - "${user}" = {
2563 - isSystemUser = true;
2564 - group = user;
2565 - # https://stackoverflow.com/questions/22314298/git-push-results-in-fatal-protocol-error-bad-line-length-character-this
2566 - # Probably could use gitsrht-shell if output is restricted to just parameters...
2567 - shell = pkgs.bash;
2568 - description = "git.sr.ht user";
2569 - };
2570 - };
2571 -
2572 - groups = {
2573 - "${user}" = { };
2574 - };
2575 - };
2576 -
2577 - services = {
2578 - cron.systemCronJobs = [ "*/20 * * * * ${cfg.python}/bin/gitsrht-periodic" ];
2579 - fcgiwrap.enable = true;
2580 -
2581 - openssh.authorizedKeysCommand = ''/etc/ssh/gitsrht-dispatch "%u" "%h" "%t" "%k"'';
2582 - openssh.authorizedKeysCommandUser = "root";
2583 - openssh.extraConfig = ''
2584 - PermitUserEnvironment SRHT_*
2585 - '';
2586 -
2587 - postgresql = {
2588 - authentication = ''
2589 - local ${database} ${user} trust
2590 - '';
2591 - ensureDatabases = [ database ];
2592 - ensureUsers = [
2593 - {
2594 - name = user;
2595 - ensurePermissions = { "DATABASE \"${database}\"" = "ALL PRIVILEGES"; };
2596 - }
2597 - ];
2598 - };
2599 - };
2600 -
2601 - systemd = {
2602 - tmpfiles.rules = [
2603 - # /var/log is owned by root
2604 - "f /var/log/git-srht-shell 0644 ${user} ${user} -"
2605 -
2606 - "d ${statePath} 0750 ${user} ${user} -"
2607 - "d ${cfg.settings."${iniKey}".repos} 2755 ${user} ${user} -"
2608 - ];
2609 -
2610 - services = {
2611 - gitsrht = import ./service.nix { inherit config pkgs lib; } scfg drv iniKey {
2612 - after = [ "redis.service" "postgresql.service" "network.target" ];
2613 - requires = [ "redis.service" "postgresql.service" ];
2614 - wantedBy = [ "multi-user.target" ];
2615 -
2616 - # Needs internally to create repos at the very least
2617 - path = [ pkgs.git ];
2618 - description = "git.sr.ht website service";
2619 -
2620 - serviceConfig.ExecStart = "${cfg.python}/bin/gunicorn ${drv.pname}.app:app -b ${cfg.address}:${toString port}";
2621 - };
2622 -
2623 - gitsrht-webhooks = {
2624 - after = [ "postgresql.service" "network.target" ];
2625 - requires = [ "postgresql.service" ];
2626 - wantedBy = [ "multi-user.target" ];
2627 -
2628 - description = "git.sr.ht webhooks service";
2629 - serviceConfig = {
2630 - Type = "simple";
2631 - User = user;
2632 - Restart = "always";
2633 - };
2634 -
2635 - serviceConfig.ExecStart = "${cfg.python}/bin/celery -A ${drv.pname}.webhooks worker --loglevel=info";
2636 - };
2637 - };
2638 - };
2639 -
2640 - services.sourcehut.settings = {
2641 - # URL git.sr.ht is being served at (protocol://domain)
2642 - "git.sr.ht".origin = mkDefault "http://git.${cfg.originBase}";
2643 - # Address and port to bind the debug server to
2644 - "git.sr.ht".debug-host = mkDefault "0.0.0.0";
2645 - "git.sr.ht".debug-port = mkDefault port;
2646 - # Configures the SQLAlchemy connection string for the database.
2647 - "git.sr.ht".connection-string = mkDefault "postgresql:///${database}?user=${user}&host=/var/run/postgresql";
2648 - # Set to "yes" to automatically run migrations on package upgrade.
2649 - "git.sr.ht".migrate-on-upgrade = mkDefault "yes";
2650 - # The redis connection used for the webhooks worker
2651 - "git.sr.ht".webhooks = mkDefault "redis://${rcfg.bind}:${toString rcfg.port}/1";
2652 -
2653 - # A post-update script which is installed in every git repo.
2654 - "git.sr.ht".post-update-script = mkDefault "${pkgs.sourcehut.gitsrht}/bin/gitsrht-update-hook";
2655 -
2656 - # git.sr.ht's OAuth client ID and secret for meta.sr.ht
2657 - # Register your client at meta.example.org/oauth
2658 - "git.sr.ht".oauth-client-id = mkDefault null;
2659 - "git.sr.ht".oauth-client-secret = mkDefault null;
2660 - # Path to git repositories on disk
2661 - "git.sr.ht".repos = mkDefault "/var/lib/git";
2662 -
2663 - "git.sr.ht".outgoing-domain = mkDefault "http://git.${cfg.originBase}";
2664 -
2665 - # The authorized keys hook uses this to dispatch to various handlers
2666 - # The format is a program to exec into as the key, and the user to match as the
2667 - # value. When someone tries to log in as this user, this program is executed
2668 - # and is expected to omit an AuthorizedKeys file.
2669 - #
2670 - # Discard of the string context is in order to allow derivation-derived strings.
2671 - # This is safe if the relevant package is installed which will be the case if the setting is utilized.
2672 - "git.sr.ht::dispatch".${builtins.unsafeDiscardStringContext "${pkgs.sourcehut.gitsrht}/bin/gitsrht-keys"} = mkDefault "${user}:${user}";
2673 - };
2674 -
2675 - services.nginx.virtualHosts."git.${cfg.originBase}" = {
2676 - forceSSL = true;
2677 - locations."/".proxyPass = "http://${cfg.address}:${toString port}";
2678 - locations."/query".proxyPass = "http://${cfg.address}:${toString (port + 100)}";
2679 - locations."/static".root = "${pkgs.sourcehut.gitsrht}/${pkgs.sourcehut.python.sitePackages}/gitsrht";
2680 - extraConfig = ''
2681 - location = /authorize {
2682 - proxy_pass http://${cfg.address}:${toString port};
2683 - proxy_pass_request_body off;
2684 - proxy_set_header Content-Length "";
2685 - proxy_set_header X-Original-URI $request_uri;
2686 - }
2687 - location ~ ^/([^/]+)/([^/]+)/(HEAD|info/refs|objects/info/.*|git-upload-pack).*$ {
2688 - auth_request /authorize;
2689 - root /var/lib/git;
2690 - fastcgi_pass unix:/run/fcgiwrap.sock;
2691 - fastcgi_param SCRIPT_FILENAME ${pkgs.git}/bin/git-http-backend;
2692 - fastcgi_param PATH_INFO $uri;
2693 - fastcgi_param GIT_PROJECT_ROOT $document_root;
2694 - fastcgi_read_timeout 500s;
2695 - include ${pkgs.nginx}/conf/fastcgi_params;
2696 - gzip off;
2697 - }
2698 - '';
2699 -
2700 - };
2701 - };
2702 -}
2703 diff --git a/nixos/modules/services/misc/sourcehut/hg.nix b/nixos/modules/services/misc/sourcehut/hg.nix
2704 deleted file mode 100644
2705 index 5cd36bb0455..00000000000
2706 --- a/nixos/modules/services/misc/sourcehut/hg.nix
2707 +++ /dev/null
2708 @@ -1,173 +0,0 @@
2709 -{ config, lib, pkgs, ... }:
2710 -
2711 -with lib;
2712 -let
2713 - cfg = config.services.sourcehut;
2714 - scfg = cfg.hg;
2715 - iniKey = "hg.sr.ht";
2716 -
2717 - rcfg = config.services.redis;
2718 - drv = pkgs.sourcehut.hgsrht;
2719 -in
2720 -{
2721 - options.services.sourcehut.hg = {
2722 - user = mkOption {
2723 - type = types.str;
2724 - internal = true;
2725 - readOnly = true;
2726 - default = "hg";
2727 - description = ''
2728 - User for hg.sr.ht.
2729 - '';
2730 - };
2731 -
2732 - port = mkOption {
2733 - type = types.port;
2734 - default = 5010;
2735 - description = ''
2736 - Port on which the "hg" module should listen.
2737 - '';
2738 - };
2739 -
2740 - database = mkOption {
2741 - type = types.str;
2742 - default = "hg.sr.ht";
2743 - description = ''
2744 - PostgreSQL database name for hg.sr.ht.
2745 - '';
2746 - };
2747 -
2748 - statePath = mkOption {
2749 - type = types.path;
2750 - default = "${cfg.statePath}/hgsrht";
2751 - description = ''
2752 - State path for hg.sr.ht.
2753 - '';
2754 - };
2755 -
2756 - cloneBundles = mkOption {
2757 - type = types.bool;
2758 - default = false;
2759 - description = ''
2760 - Generate clonebundles (which require more disk space but dramatically speed up cloning large repositories).
2761 - '';
2762 - };
2763 - };
2764 -
2765 - config = with scfg; lib.mkIf (cfg.enable && elem "hg" cfg.services) {
2766 - # In case it ever comes into being
2767 - environment.etc."ssh/hgsrht-dispatch" = {
2768 - mode = "0755";
2769 - text = ''
2770 - #! ${pkgs.stdenv.shell}
2771 - ${cfg.python}/bin/gitsrht-dispatch $@
2772 - '';
2773 - };
2774 -
2775 - environment.systemPackages = [ pkgs.mercurial ];
2776 -
2777 - users = {
2778 - users = {
2779 - "${user}" = {
2780 - isSystemUser = true;
2781 - group = user;
2782 - # Assuming hg.sr.ht needs this too
2783 - shell = pkgs.bash;
2784 - description = "hg.sr.ht user";
2785 - };
2786 - };
2787 -
2788 - groups = {
2789 - "${user}" = { };
2790 - };
2791 - };
2792 -
2793 - services = {
2794 - cron.systemCronJobs = [ "*/20 * * * * ${cfg.python}/bin/hgsrht-periodic" ]
2795 - ++ optional cloneBundles "0 * * * * ${cfg.python}/bin/hgsrht-clonebundles";
2796 -
2797 - openssh.authorizedKeysCommand = ''/etc/ssh/hgsrht-dispatch "%u" "%h" "%t" "%k"'';
2798 - openssh.authorizedKeysCommandUser = "root";
2799 - openssh.extraConfig = ''
2800 - PermitUserEnvironment SRHT_*
2801 - '';
2802 -
2803 - postgresql = {
2804 - authentication = ''
2805 - local ${database} ${user} trust
2806 - '';
2807 - ensureDatabases = [ database ];
2808 - ensureUsers = [
2809 - {
2810 - name = user;
2811 - ensurePermissions = { "DATABASE \"${database}\"" = "ALL PRIVILEGES"; };
2812 - }
2813 - ];
2814 - };
2815 - };
2816 -
2817 - systemd = {
2818 - tmpfiles.rules = [
2819 - # /var/log is owned by root
2820 - "f /var/log/hg-srht-shell 0644 ${user} ${user} -"
2821 -
2822 - "d ${statePath} 0750 ${user} ${user} -"
2823 - "d ${cfg.settings."${iniKey}".repos} 2755 ${user} ${user} -"
2824 - ];
2825 -
2826 - services.hgsrht = import ./service.nix { inherit config pkgs lib; } scfg drv iniKey {
2827 - after = [ "redis.service" "postgresql.service" "network.target" ];
2828 - requires = [ "redis.service" "postgresql.service" ];
2829 - wantedBy = [ "multi-user.target" ];
2830 -
2831 - path = [ pkgs.mercurial ];
2832 - description = "hg.sr.ht website service";
2833 -
2834 - serviceConfig.ExecStart = "${cfg.python}/bin/gunicorn ${drv.pname}.app:app -b ${cfg.address}:${toString port}";
2835 - };
2836 - };
2837 -
2838 - services.sourcehut.settings = {
2839 - # URL hg.sr.ht is being served at (protocol://domain)
2840 - "hg.sr.ht".origin = mkDefault "http://hg.${cfg.originBase}";
2841 - # Address and port to bind the debug server to
2842 - "hg.sr.ht".debug-host = mkDefault "0.0.0.0";
2843 - "hg.sr.ht".debug-port = mkDefault port;
2844 - # Configures the SQLAlchemy connection string for the database.
2845 - "hg.sr.ht".connection-string = mkDefault "postgresql:///${database}?user=${user}&host=/var/run/postgresql";
2846 - # The redis connection used for the webhooks worker
2847 - "hg.sr.ht".webhooks = mkDefault "redis://${rcfg.bind}:${toString rcfg.port}/1";
2848 - # A post-update script which is installed in every mercurial repo.
2849 - "hg.sr.ht".changegroup-script = mkDefault "${cfg.python}/bin/hgsrht-hook-changegroup";
2850 - # hg.sr.ht's OAuth client ID and secret for meta.sr.ht
2851 - # Register your client at meta.example.org/oauth
2852 - "hg.sr.ht".oauth-client-id = mkDefault null;
2853 - "hg.sr.ht".oauth-client-secret = mkDefault null;
2854 - # Path to mercurial repositories on disk
2855 - "hg.sr.ht".repos = mkDefault "/var/lib/hg";
2856 - # Path to the srht mercurial extension
2857 - # (defaults to where the hgsrht code is)
2858 - # "hg.sr.ht".srhtext = mkDefault null;
2859 - # .hg/store size (in MB) past which the nightly job generates clone bundles.
2860 - # "hg.sr.ht".clone_bundle_threshold = mkDefault 50;
2861 - # Path to hg-ssh (if not in $PATH)
2862 - # "hg.sr.ht".hg_ssh = mkDefault /path/to/hg-ssh;
2863 -
2864 - # The authorized keys hook uses this to dispatch to various handlers
2865 - # The format is a program to exec into as the key, and the user to match as the
2866 - # value. When someone tries to log in as this user, this program is executed
2867 - # and is expected to omit an AuthorizedKeys file.
2868 - #
2869 - # Uncomment the relevant lines to enable the various sr.ht dispatchers.
2870 - "hg.sr.ht::dispatch"."/run/current-system/sw/bin/hgsrht-keys" = mkDefault "${user}:${user}";
2871 - };
2872 -
2873 - # TODO: requires testing and addition of hg-specific requirements
2874 - services.nginx.virtualHosts."hg.${cfg.originBase}" = {
2875 - forceSSL = true;
2876 - locations."/".proxyPass = "http://${cfg.address}:${toString port}";
2877 - locations."/query".proxyPass = "http://${cfg.address}:${toString (port + 100)}";
2878 - locations."/static".root = "${pkgs.sourcehut.hgsrht}/${pkgs.sourcehut.python.sitePackages}/hgsrht";
2879 - };
2880 - };
2881 -}
2882 diff --git a/nixos/modules/services/misc/sourcehut/hub.nix b/nixos/modules/services/misc/sourcehut/hub.nix
2883 deleted file mode 100644
2884 index be3ea21011c..00000000000
2885 --- a/nixos/modules/services/misc/sourcehut/hub.nix
2886 +++ /dev/null
2887 @@ -1,118 +0,0 @@
2888 -{ config, lib, pkgs, ... }:
2889 -
2890 -with lib;
2891 -let
2892 - cfg = config.services.sourcehut;
2893 - cfgIni = cfg.settings;
2894 - scfg = cfg.hub;
2895 - iniKey = "hub.sr.ht";
2896 -
2897 - drv = pkgs.sourcehut.hubsrht;
2898 -in
2899 -{
2900 - options.services.sourcehut.hub = {
2901 - user = mkOption {
2902 - type = types.str;
2903 - default = "hubsrht";
2904 - description = ''
2905 - User for hub.sr.ht.
2906 - '';
2907 - };
2908 -
2909 - port = mkOption {
2910 - type = types.port;
2911 - default = 5014;
2912 - description = ''
2913 - Port on which the "hub" module should listen.
2914 - '';
2915 - };
2916 -
2917 - database = mkOption {
2918 - type = types.str;
2919 - default = "hub.sr.ht";
2920 - description = ''
2921 - PostgreSQL database name for hub.sr.ht.
2922 - '';
2923 - };
2924 -
2925 - statePath = mkOption {
2926 - type = types.path;
2927 - default = "${cfg.statePath}/hubsrht";
2928 - description = ''
2929 - State path for hub.sr.ht.
2930 - '';
2931 - };
2932 - };
2933 -
2934 - config = with scfg; lib.mkIf (cfg.enable && elem "hub" cfg.services) {
2935 - users = {
2936 - users = {
2937 - "${user}" = {
2938 - isSystemUser = true;
2939 - group = user;
2940 - description = "hub.sr.ht user";
2941 - };
2942 - };
2943 -
2944 - groups = {
2945 - "${user}" = { };
2946 - };
2947 - };
2948 -
2949 - services.postgresql = {
2950 - authentication = ''
2951 - local ${database} ${user} trust
2952 - '';
2953 - ensureDatabases = [ database ];
2954 - ensureUsers = [
2955 - {
2956 - name = user;
2957 - ensurePermissions = { "DATABASE \"${database}\"" = "ALL PRIVILEGES"; };
2958 - }
2959 - ];
2960 - };
2961 -
2962 - systemd = {
2963 - tmpfiles.rules = [
2964 - "d ${statePath} 0750 ${user} ${user} -"
2965 - ];
2966 -
2967 - services.hubsrht = import ./service.nix { inherit config pkgs lib; } scfg drv iniKey {
2968 - after = [ "postgresql.service" "network.target" ];
2969 - requires = [ "postgresql.service" ];
2970 - wantedBy = [ "multi-user.target" ];
2971 -
2972 - description = "hub.sr.ht website service";
2973 -
2974 - serviceConfig.ExecStart = "${cfg.python}/bin/gunicorn ${drv.pname}.app:app -b ${cfg.address}:${toString port}";
2975 - };
2976 - };
2977 -
2978 - services.sourcehut.settings = {
2979 - # URL hub.sr.ht is being served at (protocol://domain)
2980 - "hub.sr.ht".origin = mkDefault "http://hub.${cfg.originBase}";
2981 - # Address and port to bind the debug server to
2982 - "hub.sr.ht".debug-host = mkDefault "0.0.0.0";
2983 - "hub.sr.ht".debug-port = mkDefault port;
2984 - # Configures the SQLAlchemy connection string for the database.
2985 - "hub.sr.ht".connection-string = mkDefault "postgresql:///${database}?user=${user}&host=/var/run/postgresql";
2986 - # Set to "yes" to automatically run migrations on package upgrade.
2987 - "hub.sr.ht".migrate-on-upgrade = mkDefault "yes";
2988 - # hub.sr.ht's OAuth client ID and secret for meta.sr.ht
2989 - # Register your client at meta.example.org/oauth
2990 - "hub.sr.ht".oauth-client-id = mkDefault null;
2991 - "hub.sr.ht".oauth-client-secret = mkDefault null;
2992 - };
2993 -
2994 - services.nginx.virtualHosts."${cfg.originBase}" = {
2995 - forceSSL = true;
2996 - locations."/".proxyPass = "http://${cfg.address}:${toString port}";
2997 - locations."/query".proxyPass = "http://${cfg.address}:${toString (port + 100)}";
2998 - locations."/static".root = "${pkgs.sourcehut.hubsrht}/${pkgs.sourcehut.python.sitePackages}/hubsrht";
2999 - };
3000 - services.nginx.virtualHosts."hub.${cfg.originBase}" = {
3001 - globalRedirect = "${cfg.originBase}";
3002 - forceSSL = true;
3003 - };
3004 - };
3005 -}
3006 diff --git a/nixos/modules/services/misc/sourcehut/lists.nix b/nixos/modules/services/misc/sourcehut/lists.nix
3007 deleted file mode 100644
3008 index 7b1fe9fd463..00000000000
3009 --- a/nixos/modules/services/misc/sourcehut/lists.nix
3010 +++ /dev/null
3011 @@ -1,185 +0,0 @@
3012 -# Email setup is fairly involved, useful references:
3013 -# https://drewdevault.com/2018/08/05/Local-mail-server.html
3014 -
3015 -{ config, lib, pkgs, ... }:
3016 -
3017 -with lib;
3018 -let
3019 - cfg = config.services.sourcehut;
3020 - cfgIni = cfg.settings;
3021 - scfg = cfg.lists;
3022 - iniKey = "lists.sr.ht";
3023 -
3024 - rcfg = config.services.redis;
3025 - drv = pkgs.sourcehut.listssrht;
3026 -in
3027 -{
3028 - options.services.sourcehut.lists = {
3029 - user = mkOption {
3030 - type = types.str;
3031 - default = "listssrht";
3032 - description = ''
3033 - User for lists.sr.ht.
3034 - '';
3035 - };
3036 -
3037 - port = mkOption {
3038 - type = types.port;
3039 - default = 5006;
3040 - description = ''
3041 - Port on which the "lists" module should listen.
3042 - '';
3043 - };
3044 -
3045 - database = mkOption {
3046 - type = types.str;
3047 - default = "lists.sr.ht";
3048 - description = ''
3049 - PostgreSQL database name for lists.sr.ht.
3050 - '';
3051 - };
3052 -
3053 - statePath = mkOption {
3054 - type = types.path;
3055 - default = "${cfg.statePath}/listssrht";
3056 - description = ''
3057 - State path for lists.sr.ht.
3058 - '';
3059 - };
3060 - };
3061 -
3062 - config = with scfg; lib.mkIf (cfg.enable && elem "lists" cfg.services) {
3063 - users = {
3064 - users = {
3065 - "${user}" = {
3066 - isSystemUser = true;
3067 - group = user;
3068 - extraGroups = [ "postfix" ];
3069 - description = "lists.sr.ht user";
3070 - };
3071 - };
3072 - groups = {
3073 - "${user}" = { };
3074 - };
3075 - };
3076 -
3077 - services.postgresql = {
3078 - authentication = ''
3079 - local ${database} ${user} trust
3080 - '';
3081 - ensureDatabases = [ database ];
3082 - ensureUsers = [
3083 - {
3084 - name = user;
3085 - ensurePermissions = { "DATABASE \"${database}\"" = "ALL PRIVILEGES"; };
3086 - }
3087 - ];
3088 - };
3089 -
3090 - systemd = {
3091 - tmpfiles.rules = [
3092 - "d ${statePath} 0750 ${user} ${user} -"
3093 - ];
3094 -
3095 - services = {
3096 - listssrht = import ./service.nix { inherit config pkgs lib; } scfg drv iniKey {
3097 - after = [ "postgresql.service" "network.target" ];
3098 - requires = [ "postgresql.service" ];
3099 - wantedBy = [ "multi-user.target" ];
3100 -
3101 - description = "lists.sr.ht website service";
3102 -
3103 - serviceConfig.ExecStart = "${cfg.python}/bin/gunicorn ${drv.pname}.app:app -b ${cfg.address}:${toString port}";
3104 - };
3105 -
3106 - listssrht-process = {
3107 - after = [ "postgresql.service" "network.target" ];
3108 - requires = [ "postgresql.service" ];
3109 - wantedBy = [ "multi-user.target" ];
3110 -
3111 - description = "lists.sr.ht process service";
3112 - serviceConfig = {
3113 - Type = "simple";
3114 - User = user;
3115 - Restart = "always";
3116 - ExecStart = "${cfg.python}/bin/celery -A ${drv.pname}.process worker --loglevel=info";
3117 - };
3118 - };
3119 -
3120 - listssrht-lmtp = {
3121 - after = [ "postgresql.service" "network.target" ];
3122 - requires = [ "postgresql.service" ];
3123 - wantedBy = [ "multi-user.target" ];
3124 -
3125 - description = "lists.sr.ht process service";
3126 - serviceConfig = {
3127 - Type = "simple";
3128 - User = user;
3129 - Restart = "always";
3130 - ExecStart = "${cfg.python}/bin/listssrht-lmtp";
3131 - };
3132 - };
3133 -
3134 -
3135 - listssrht-webhooks = {
3136 - after = [ "postgresql.service" "network.target" ];
3137 - requires = [ "postgresql.service" ];
3138 - wantedBy = [ "multi-user.target" ];
3139 -
3140 - description = "lists.sr.ht webhooks service";
3141 - serviceConfig = {
3142 - Type = "simple";
3143 - User = user;
3144 - Restart = "always";
3145 - ExecStart = "${cfg.python}/bin/celery -A ${drv.pname}.webhooks worker --loglevel=info";
3146 - };
3147 - };
3148 - };
3149 - };
3150 -
3151 - services.sourcehut.settings = {
3152 - # URL lists.sr.ht is being served at (protocol://domain)
3153 - "lists.sr.ht".origin = mkDefault "http://lists.${cfg.originBase}";
3154 - # Address and port to bind the debug server to
3155 - "lists.sr.ht".debug-host = mkDefault "0.0.0.0";
3156 - "lists.sr.ht".debug-port = mkDefault port;
3157 - # Configures the SQLAlchemy connection string for the database.
3158 - "lists.sr.ht".connection-string = mkDefault "postgresql:///${database}?user=${user}&host=/var/run/postgresql";
3159 - # Set to "yes" to automatically run migrations on package upgrade.
3160 - "lists.sr.ht".migrate-on-upgrade = mkDefault "yes";
3161 - # lists.sr.ht's OAuth client ID and secret for meta.sr.ht
3162 - # Register your client at meta.example.org/oauth
3163 - "lists.sr.ht".oauth-client-id = mkDefault null;
3164 - "lists.sr.ht".oauth-client-secret = mkDefault null;
3165 - # Outgoing email for notifications generated by users
3166 - "lists.sr.ht".notify-from = mkDefault "CHANGEME@example.org";
3167 - # The redis connection used for the webhooks worker
3168 - "lists.sr.ht".webhooks = mkDefault "redis://${rcfg.bind}:${toString rcfg.port}/2";
3169 - # The redis connection used for the celery worker
3170 - "lists.sr.ht".redis = mkDefault "redis://${rcfg.bind}:${toString rcfg.port}/4";
3171 - # Network-key
3172 - "lists.sr.ht".network-key = mkDefault null;
3173 - # Allow creation
3174 - "lists.sr.ht".allow-new-lists = mkDefault "no";
3175 - # Posting Domain
3176 - "lists.sr.ht".posting-domain = mkDefault "lists.${cfg.originBase}";
3177 -
3178 - # Path for the lmtp daemon's unix socket. Direct incoming mail to this socket.
3179 - # Alternatively, specify IP:PORT and an SMTP server will be run instead.
3180 - "lists.sr.ht::worker".sock = mkDefault "/tmp/lists.sr.ht-lmtp.sock";
3181 - # The lmtp daemon will make the unix socket group-read/write for users in this
3182 - # group.
3183 - "lists.sr.ht::worker".sock-group = mkDefault "postfix";
3184 - "lists.sr.ht::worker".reject-url = mkDefault "https://man.sr.ht/lists.sr.ht/etiquette.md";
3185 - "lists.sr.ht::worker".reject-mimetypes = mkDefault "text/html";
3186 -
3187 - };
3188 -
3189 - services.nginx.virtualHosts."lists.${cfg.originBase}" = {
3190 - forceSSL = true;
3191 - locations."/".proxyPass = "http://${cfg.address}:${toString port}";
3192 - locations."/query".proxyPass = "http://${cfg.address}:${toString (port + 100)}";
3193 - locations."/static".root = "${pkgs.sourcehut.listssrht}/${pkgs.sourcehut.python.sitePackages}/listssrht";
3194 - };
3195 - };
3196 -}
3197 diff --git a/nixos/modules/services/misc/sourcehut/man.nix b/nixos/modules/services/misc/sourcehut/man.nix
3198 deleted file mode 100644
3199 index 7693396d187..00000000000
3200 --- a/nixos/modules/services/misc/sourcehut/man.nix
3201 +++ /dev/null
3202 @@ -1,122 +0,0 @@
3203 -{ config, lib, pkgs, ... }:
3204 -
3205 -with lib;
3206 -let
3207 - cfg = config.services.sourcehut;
3208 - cfgIni = cfg.settings;
3209 - scfg = cfg.man;
3210 - iniKey = "man.sr.ht";
3211 -
3212 - drv = pkgs.sourcehut.mansrht;
3213 -in
3214 -{
3215 - options.services.sourcehut.man = {
3216 - user = mkOption {
3217 - type = types.str;
3218 - default = "mansrht";
3219 - description = ''
3220 - User for man.sr.ht.
3221 - '';
3222 - };
3223 -
3224 - port = mkOption {
3225 - type = types.port;
3226 - default = 5004;
3227 - description = ''
3228 - Port on which the "man" module should listen.
3229 - '';
3230 - };
3231 -
3232 - database = mkOption {
3233 - type = types.str;
3234 - default = "man.sr.ht";
3235 - description = ''
3236 - PostgreSQL database name for man.sr.ht.
3237 - '';
3238 - };
3239 -
3240 - statePath = mkOption {
3241 - type = types.path;
3242 - default = "${cfg.statePath}/mansrht";
3243 - description = ''
3244 - State path for man.sr.ht.
3245 - '';
3246 - };
3247 - };
3248 -
3249 - config = with scfg; lib.mkIf (cfg.enable && elem "man" cfg.services) {
3250 - assertions =
3251 - [
3252 - {
3253 - assertion = hasAttrByPath [ "git.sr.ht" "oauth-client-id" ] cfgIni;
3254 - message = "man.sr.ht needs access to git.sr.ht.";
3255 - }
3256 - ];
3257 -
3258 - users = {
3259 - users = {
3260 - "${user}" = {
3261 - isSystemUser = true;
3262 - group = user;
3263 - description = "man.sr.ht user";
3264 - };
3265 - };
3266 -
3267 - groups = {
3268 - "${user}" = { };
3269 - };
3270 - };
3271 -
3272 - services.postgresql = {
3273 - authentication = ''
3274 - local ${database} ${user} trust
3275 - '';
3276 - ensureDatabases = [ database ];
3277 - ensureUsers = [
3278 - {
3279 - name = user;
3280 - ensurePermissions = { "DATABASE \"${database}\"" = "ALL PRIVILEGES"; };
3281 - }
3282 - ];
3283 - };
3284 -
3285 - systemd = {
3286 - tmpfiles.rules = [
3287 - "d ${statePath} 0750 ${user} ${user} -"
3288 - ];
3289 -
3290 - services.mansrht = import ./service.nix { inherit config pkgs lib; } scfg drv iniKey {
3291 - after = [ "postgresql.service" "network.target" ];
3292 - requires = [ "postgresql.service" ];
3293 - wantedBy = [ "multi-user.target" ];
3294 -
3295 - description = "man.sr.ht website service";
3296 -
3297 - serviceConfig.ExecStart = "${cfg.python}/bin/gunicorn ${drv.pname}.app:app -b ${cfg.address}:${toString port}";
3298 - };
3299 - };
3300 -
3301 - services.sourcehut.settings = {
3302 - # URL man.sr.ht is being served at (protocol://domain)
3303 - "man.sr.ht".origin = mkDefault "http://man.${cfg.originBase}";
3304 - # Address and port to bind the debug server to
3305 - "man.sr.ht".debug-host = mkDefault "0.0.0.0";
3306 - "man.sr.ht".debug-port = mkDefault port;
3307 - # Configures the SQLAlchemy connection string for the database.
3308 - "man.sr.ht".connection-string = mkDefault "postgresql:///${database}?user=${user}&host=/var/run/postgresql";
3309 - # Set to "yes" to automatically run migrations on package upgrade.
3310 - "man.sr.ht".migrate-on-upgrade = mkDefault "yes";
3311 - # man.sr.ht's OAuth client ID and secret for meta.sr.ht
3312 - # Register your client at meta.example.org/oauth
3313 - "man.sr.ht".oauth-client-id = mkDefault null;
3314 - "man.sr.ht".oauth-client-secret = mkDefault null;
3315 - };
3316 -
3317 - services.nginx.virtualHosts."man.${cfg.originBase}" = {
3318 - forceSSL = true;
3319 - locations."/".proxyPass = "http://${cfg.address}:${toString port}";
3320 - locations."/query".proxyPass = "http://${cfg.address}:${toString (port + 100)}";
3321 - locations."/static".root = "${pkgs.sourcehut.mansrht}/${pkgs.sourcehut.python.sitePackages}/mansrht";
3322 - };
3323 - };
3324 -}
3325 diff --git a/nixos/modules/services/misc/sourcehut/meta.nix b/nixos/modules/services/misc/sourcehut/meta.nix
3326 deleted file mode 100644
3327 index 56127a824eb..00000000000
3328 --- a/nixos/modules/services/misc/sourcehut/meta.nix
3329 +++ /dev/null
3330 @@ -1,211 +0,0 @@
3331 -{ config, lib, pkgs, ... }:
3332 -
3333 -with lib;
3334 -let
3335 - cfg = config.services.sourcehut;
3336 - cfgIni = cfg.settings;
3337 - scfg = cfg.meta;
3338 - iniKey = "meta.sr.ht";
3339 -
3340 - rcfg = config.services.redis;
3341 - drv = pkgs.sourcehut.metasrht;
3342 -in
3343 -{
3344 - options.services.sourcehut.meta = {
3345 - user = mkOption {
3346 - type = types.str;
3347 - default = "metasrht";
3348 - description = ''
3349 - User for meta.sr.ht.
3350 - '';
3351 - };
3352 -
3353 - port = mkOption {
3354 - type = types.port;
3355 - default = 5000;
3356 - description = ''
3357 - Port on which the "meta" module should listen.
3358 - '';
3359 - };
3360 -
3361 - database = mkOption {
3362 - type = types.str;
3363 - default = "meta.sr.ht";
3364 - description = ''
3365 - PostgreSQL database name for meta.sr.ht.
3366 - '';
3367 - };
3368 -
3369 - statePath = mkOption {
3370 - type = types.path;
3371 - default = "${cfg.statePath}/metasrht";
3372 - description = ''
3373 - State path for meta.sr.ht.
3374 - '';
3375 - };
3376 - };
3377 -
3378 - config = with scfg; lib.mkIf (cfg.enable && elem "meta" cfg.services) {
3379 - assertions =
3380 - [
3381 - {
3382 - assertion = with cfgIni."meta.sr.ht::billing"; enabled == "yes" -> (stripe-public-key != null && stripe-secret-key != null);
3383 - message = "If meta.sr.ht::billing is enabled, the keys should be defined.";
3384 - }
3385 - ];
3386 -
3387 - users = {
3388 - users = {
3389 - ${user} = {
3390 - isSystemUser = true;
3391 - group = user;
3392 - description = "meta.sr.ht user";
3393 - };
3394 - };
3395 -
3396 - groups = {
3397 - "${user}" = { };
3398 - };
3399 - };
3400 -
3401 - services.cron.systemCronJobs = [ "0 0 * * * ${cfg.python}/bin/metasrht-daily" ];
3402 - services.postgresql = {
3403 - authentication = ''
3404 - local ${database} ${user} trust
3405 - '';
3406 - ensureDatabases = [ database ];
3407 - ensureUsers = [
3408 - {
3409 - name = user;
3410 - ensurePermissions = { "DATABASE \"${database}\"" = "ALL PRIVILEGES"; };
3411 - }
3412 - ];
3413 - };
3414 -
3415 - systemd = {
3416 - tmpfiles.rules = [
3417 - "d ${statePath} 0750 ${user} ${user} -"
3418 - ];
3419 -
3420 - services = {
3421 - metasrht = import ./service.nix { inherit config pkgs lib; } scfg drv iniKey {
3422 - after = [ "postgresql.service" "network.target" ];
3423 - requires = [ "postgresql.service" ];
3424 - wantedBy = [ "multi-user.target" ];
3425 -
3426 - description = "meta.sr.ht website service";
3427 -
3428 - preStart = ''
3429 - # Configure client(s) as "preauthorized"
3430 - ${concatMapStringsSep "\n\n"
3431 - (attr: ''
3432 - if ! test -e "${statePath}/${attr}.oauth" || [ "$(cat ${statePath}/${attr}.oauth)" != "${cfgIni."${attr}".oauth-client-id}" ]; then
3433 - # Configure ${attr}'s OAuth client as "preauthorized"
3434 - psql ${database} \
3435 - -c "UPDATE oauthclient SET preauthorized = true WHERE client_id = '${cfgIni."${attr}".oauth-client-id}'"
3436 -
3437 - printf "%s" "${cfgIni."${attr}".oauth-client-id}" > "${statePath}/${attr}.oauth"
3438 - fi
3439 - '')
3440 - (builtins.attrNames (filterAttrs
3441 - (k: v: !(hasInfix "::" k) && builtins.hasAttr "oauth-client-id" v && v.oauth-client-id != null)
3442 - cfg.settings))}
3443 - '';
3444 -
3445 - serviceConfig.ExecStart = "${cfg.python}/bin/gunicorn ${drv.pname}.app:app -b ${cfg.address}:${toString port}";
3446 - };
3447 -
3448 - metasrht-api = import ./service.nix { inherit config pkgs lib; } scfg drv iniKey {
3449 - after = [ "postgresql.service" "network.target" ];
3450 - requires = [ "postgresql.service" ];
3451 - wantedBy = [ "multi-user.target" ];
3452 -
3453 - description = "meta.sr.ht api service";
3454 -
3455 - preStart = ''
3456 - # Configure client(s) as "preauthorized"
3457 - ${concatMapStringsSep "\n\n"
3458 - (attr: ''
3459 - if ! test -e "${statePath}/${attr}.oauth" || [ "$(cat ${statePath}/${attr}.oauth)" != "${cfgIni."${attr}".oauth-client-id}" ]; then
3460 - # Configure ${attr}'s OAuth client as "preauthorized"
3461 - psql ${database} \
3462 - -c "UPDATE oauthclient SET preauthorized = true WHERE client_id = '${cfgIni."${attr}".oauth-client-id}'"
3463 -
3464 - printf "%s" "${cfgIni."${attr}".oauth-client-id}" > "${statePath}/${attr}.oauth"
3465 - fi
3466 - '')
3467 - (builtins.attrNames (filterAttrs
3468 - (k: v: !(hasInfix "::" k) && builtins.hasAttr "oauth-client-id" v && v.oauth-client-id != null)
3469 - cfg.settings))}
3470 - '';
3471 -
3472 - serviceConfig.ExecStart = "${pkgs.sourcehut.metasrht}/bin/metasrht-api -b :${toString (port + 100)}";
3473 - };
3474 -
3475 - metasrht-webhooks = {
3476 - after = [ "postgresql.service" "network.target" ];
3477 - requires = [ "postgresql.service" ];
3478 - wantedBy = [ "multi-user.target" ];
3479 -
3480 - description = "meta.sr.ht webhooks service";
3481 - serviceConfig = {
3482 - Type = "simple";
3483 - User = user;
3484 - Restart = "always";
3485 - ExecStart = "${cfg.python}/bin/celery -A ${drv.pname}.webhooks worker --loglevel=info";
3486 - };
3487 -
3488 - };
3489 - };
3490 - };
3491 -
3492 - services.sourcehut.settings = {
3493 - # URL meta.sr.ht is being served at (protocol://domain)
3494 - "meta.sr.ht".origin = mkDefault "https://meta.${cfg.originBase}";
3495 - # Address and port to bind the debug server to
3496 - "meta.sr.ht".debug-host = mkDefault "0.0.0.0";
3497 - "meta.sr.ht".debug-port = mkDefault port;
3498 - # Configures the SQLAlchemy connection string for the database.
3499 - "meta.sr.ht".connection-string = mkDefault "postgresql:///${database}?user=${user}&host=/var/run/postgresql";
3500 - # Set to "yes" to automatically run migrations on package upgrade.
3501 - "meta.sr.ht".migrate-on-upgrade = mkDefault "yes";
3502 - # If "yes", the user will be sent the stock sourcehut welcome emails after
3503 - # signup (requires cron to be configured properly). These are specific to the
3504 - # sr.ht instance so you probably want to patch these before enabling this.
3505 - "meta.sr.ht".welcome-emails = mkDefault "no";
3506 -
3507 - # The redis connection used for the webhooks worker
3508 - "meta.sr.ht".webhooks = mkDefault "redis://${rcfg.bind}:${toString rcfg.port}/6";
3509 -
3510 - # If "no", public registration will not be permitted.
3511 - "meta.sr.ht::settings".registration = mkDefault "no";
3512 - # Where to redirect new users upon registration
3513 - "meta.sr.ht::settings".onboarding-redirect = mkDefault "https://meta.${cfg.originBase}";
3514 - # How many invites each user is issued upon registration (only applicable if
3515 - # open registration is disabled)
3516 - "meta.sr.ht::settings".user-invites = mkDefault 5;
3517 -
3518 - # Origin URL for API, 100 more than web
3519 - "meta.sr.ht".api-origin = mkDefault "http://localhost:5100";
3520 -
3521 - # You can add aliases for the client IDs of commonly used OAuth clients here.
3522 - #
3523 - # Example:
3524 - "meta.sr.ht::aliases" = mkDefault { };
3525 - # "meta.sr.ht::aliases"."git.sr.ht" = 12345;
3526 -
3527 - # "yes" to enable the billing system
3528 - "meta.sr.ht::billing".enabled = mkDefault "no";
3529 - # Get your keys at https://dashboard.stripe.com/account/apikeys
3530 - "meta.sr.ht::billing".stripe-public-key = mkDefault null;
3531 - "meta.sr.ht::billing".stripe-secret-key = mkDefault null;
3532 - };
3533 -
3534 - services.nginx.virtualHosts."meta.${cfg.originBase}" = {
3535 - forceSSL = true;
3536 - locations."/".proxyPass = "http://${cfg.address}:${toString port}";
3537 - locations."/query".proxyPass = "http://${cfg.address}:${toString (port + 100)}";
3538 - locations."/static".root = "${pkgs.sourcehut.metasrht}/${pkgs.sourcehut.python.sitePackages}/metasrht";
3539 - };
3540 - };
3541 -}
3542 diff --git a/nixos/modules/services/misc/sourcehut/paste.nix b/nixos/modules/services/misc/sourcehut/paste.nix
3543 deleted file mode 100644
3544 index b2d5151969e..00000000000
3545 --- a/nixos/modules/services/misc/sourcehut/paste.nix
3546 +++ /dev/null
3547 @@ -1,133 +0,0 @@
3548 -{ config, lib, pkgs, ... }:
3549 -
3550 -with lib;
3551 -let
3552 - cfg = config.services.sourcehut;
3553 - cfgIni = cfg.settings;
3554 - scfg = cfg.paste;
3555 - iniKey = "paste.sr.ht";
3556 -
3557 - rcfg = config.services.redis;
3558 - drv = pkgs.sourcehut.pastesrht;
3559 -in
3560 -{
3561 - options.services.sourcehut.paste = {
3562 - user = mkOption {
3563 - type = types.str;
3564 - default = "pastesrht";
3565 - description = ''
3566 - User for paste.sr.ht.
3567 - '';
3568 - };
3569 -
3570 - port = mkOption {
3571 - type = types.port;
3572 - default = 5011;
3573 - description = ''
3574 - Port on which the "paste" module should listen.
3575 - '';
3576 - };
3577 -
3578 - database = mkOption {
3579 - type = types.str;
3580 - default = "paste.sr.ht";
3581 - description = ''
3582 - PostgreSQL database name for paste.sr.ht.
3583 - '';
3584 - };
3585 -
3586 - statePath = mkOption {
3587 - type = types.path;
3588 - default = "${cfg.statePath}/pastesrht";
3589 - description = ''
3590 - State path for pastesrht.sr.ht.
3591 - '';
3592 - };
3593 - };
3594 -
3595 - config = with scfg; lib.mkIf (cfg.enable && elem "paste" cfg.services) {
3596 - users = {
3597 - users = {
3598 - "${user}" = {
3599 - isSystemUser = true;
3600 - group = user;
3601 - description = "paste.sr.ht user";
3602 - };
3603 - };
3604 -
3605 - groups = {
3606 - "${user}" = { };
3607 - };
3608 - };
3609 -
3610 - services.postgresql = {
3611 - authentication = ''
3612 - local ${database} ${user} trust
3613 - '';
3614 - ensureDatabases = [ database ];
3615 - ensureUsers = [
3616 - {
3617 - name = user;
3618 - ensurePermissions = { "DATABASE \"${database}\"" = "ALL PRIVILEGES"; };
3619 - }
3620 - ];
3621 - };
3622 -
3623 - systemd = {
3624 - tmpfiles.rules = [
3625 - "d ${statePath} 0750 ${user} ${user} -"
3626 - ];
3627 -
3628 - services = {
3629 - pastesrht = import ./service.nix { inherit config pkgs lib; } scfg drv iniKey {
3630 - after = [ "postgresql.service" "network.target" ];
3631 - requires = [ "postgresql.service" ];
3632 - wantedBy = [ "multi-user.target" ];
3633 -
3634 - description = "paste.sr.ht website service";
3635 -
3636 - serviceConfig.ExecStart = "${cfg.python}/bin/gunicorn ${drv.pname}.app:app -b ${cfg.address}:${toString port}";
3637 - };
3638 -
3639 - pastesrht-webhooks = {
3640 - after = [ "postgresql.service" "network.target" ];
3641 - requires = [ "postgresql.service" ];
3642 - wantedBy = [ "multi-user.target" ];
3643 -
3644 - description = "paste.sr.ht webhooks service";
3645 - serviceConfig = {
3646 - Type = "simple";
3647 - User = user;
3648 - Restart = "always";
3649 - ExecStart = "${cfg.python}/bin/celery -A ${drv.pname}.webhooks worker --loglevel=info";
3650 - };
3651 -
3652 - };
3653 - };
3654 - };
3655 -
3656 - services.sourcehut.settings = {
3657 - # URL paste.sr.ht is being served at (protocol://domain)
3658 - "paste.sr.ht".origin = mkDefault "http://paste.${cfg.originBase}";
3659 - # Address and port to bind the debug server to
3660 - "paste.sr.ht".debug-host = mkDefault "0.0.0.0";
3661 - "paste.sr.ht".debug-port = mkDefault port;
3662 - # Configures the SQLAlchemy connection string for the database.
3663 - "paste.sr.ht".connection-string = mkDefault "postgresql:///${database}?user=${user}&host=/var/run/postgresql";
3664 - # Set to "yes" to automatically run migrations on package upgrade.
3665 - "paste.sr.ht".migrate-on-upgrade = mkDefault "yes";
3666 - # paste.sr.ht's OAuth client ID and secret for meta.sr.ht
3667 - # Register your client at meta.example.org/oauth
3668 - "paste.sr.ht".oauth-client-id = mkDefault null;
3669 - "paste.sr.ht".oauth-client-secret = mkDefault null;
3670 - "paste.sr.ht".webhooks = mkDefault "redis://${rcfg.bind}:${toString rcfg.port}/5";
3671 - };
3672 -
3673 - services.nginx.virtualHosts."paste.${cfg.originBase}" = {
3674 - forceSSL = true;
3675 - locations."/".proxyPass = "http://${cfg.address}:${toString port}";
3676 - locations."/query".proxyPass = "http://${cfg.address}:${toString (port + 100)}";
3677 - locations."/static".root = "${pkgs.sourcehut.pastesrht}/${pkgs.sourcehut.python.sitePackages}/pastesrht";
3678 - };
3679 - };
3680 -}
3681 diff --git a/nixos/modules/services/misc/sourcehut/service.nix b/nixos/modules/services/misc/sourcehut/service.nix
3682 index 65b4ad020f9..b3c0efc07dd 100644
3683 --- a/nixos/modules/services/misc/sourcehut/service.nix
3684 +++ b/nixos/modules/services/misc/sourcehut/service.nix
3685 @@ -1,66 +1,375 @@
3686 -{ config, pkgs, lib }:
3687 -serviceCfg: serviceDrv: iniKey: attrs:
3688 +srv:
3689 +{ configIniOfService
3690 +, srvsrht ? "${srv}srht" # Because "buildsrht" does not follow that pattern (missing an "s").
3691 +, iniKey ? "${srv}.sr.ht"
3692 +, webhooks ? false
3693 +, extraTimers ? {}
3694 +, mainService ? {}
3695 +, extraServices ? {}
3696 +, extraConfig ? {}
3697 +, port
3698 +}:
3699 +{ config, lib, pkgs, ... }:
3700 +
3701 +with lib;
3702 let
3703 + inherit (config.services) postgresql;
3704 + redis = config.services.redis.servers."sourcehut-${srvsrht}";
3705 + inherit (config.users) users;
3706 cfg = config.services.sourcehut;
3707 - cfgIni = cfg.settings."${iniKey}";
3708 - pgSuperUser = config.services.postgresql.superUser;
3709 -
3710 - setupDB = pkgs.writeScript "${serviceDrv.pname}-gen-db" ''
3711 - #! ${cfg.python}/bin/python
3712 - from ${serviceDrv.pname}.app import db
3713 - db.create()
3714 - '';
3715 + configIni = configIniOfService srv;
3716 + srvCfg = cfg.${srv};
3717 + baseService = serviceName: { allowStripe ? false }: extraService: let
3718 + runDir = "/run/sourcehut/${serviceName}";
3719 + rootDir = "/run/sourcehut/chroots/${serviceName}";
3720 + in
3721 + mkMerge [ extraService {
3722 + after = [ "network.target" ] ++
3723 + optional cfg.postgresql.enable "postgresql.service" ++
3724 + optional cfg.redis.enable "redis-sourcehut-${srvsrht}.service";
3725 + requires =
3726 + optional cfg.postgresql.enable "postgresql.service" ++
3727 + optional cfg.redis.enable "redis-sourcehut-${srvsrht}.service";
3728 + path = [ pkgs.gawk ];
3729 + environment.HOME = runDir;
3730 + serviceConfig = {
3731 + User = mkDefault srvCfg.user;
3732 + Group = mkDefault srvCfg.group;
3733 + RuntimeDirectory = [
3734 + "sourcehut/${serviceName}"
3735 + # Used by *srht-keys which reads ../config.ini
3736 + "sourcehut/${serviceName}/subdir"
3737 + "sourcehut/chroots/${serviceName}"
3738 + ];
3739 + RuntimeDirectoryMode = "2750";
3740 + # No need for the chroot path once inside the chroot
3741 + InaccessiblePaths = [ "-+${rootDir}" ];
3742 + # g+rx is for group members (eg. fcgiwrap or nginx)
3743 + # to read Git/Mercurial repositories, buildlogs, etc.
3744 + # o+x is for intermediate directories created by BindPaths= and like,
3745 + # as they're owned by root:root.
3746 + UMask = "0026";
3747 + RootDirectory = rootDir;
3748 + RootDirectoryStartOnly = true;
3749 + PrivateTmp = true;
3750 + MountAPIVFS = true;
3751 + # config.ini is looked up in there, before /etc/srht/config.ini
3752 + # Note that it fails to be set in ExecStartPre=
3753 + WorkingDirectory = mkDefault ("-"+runDir);
3754 + BindReadOnlyPaths = [
3755 + builtins.storeDir
3756 + "/etc"
3757 + "/run/booted-system"
3758 + "/run/current-system"
3759 + "/run/systemd"
3760 + ] ++
3761 + optional cfg.postgresql.enable "/run/postgresql" ++
3762 + optional cfg.redis.enable "/run/redis-sourcehut-${srvsrht}";
3763 + # LoadCredential= are unfortunately not available in ExecStartPre=
3764 + # Hence this one is run as root (the +) with RootDirectoryStartOnly=
3765 + # to reach credentials wherever they are.
3766 + # Note that each systemd service gets its own ${runDir}/config.ini file.
3767 + ExecStartPre = mkBefore [("+"+pkgs.writeShellScript "${serviceName}-credentials" ''
3768 + set -x
3769 + # Replace values begining with a '<' by the content of the file whose name is after.
3770 + gawk '{ if (match($0,/^([^=]+=)<(.+)/,m)) { getline f < m[2]; print m[1] f } else print $0 }' ${configIni} |
3771 + ${optionalString (!allowStripe) "gawk '!/^stripe-secret-key=/' |"}
3772 + install -o ${srvCfg.user} -g root -m 400 /dev/stdin ${runDir}/config.ini
3773 + '')];
3774 + # The following options are only for optimizing:
3775 + # systemd-analyze security
3776 + AmbientCapabilities = "";
3777 + CapabilityBoundingSet = "";
3778 + # ProtectClock= adds DeviceAllow=char-rtc r
3779 + DeviceAllow = "";
3780 + LockPersonality = true;
3781 + MemoryDenyWriteExecute = true;
3782 + NoNewPrivileges = true;
3783 + PrivateDevices = true;
3784 + PrivateMounts = true;
3785 + PrivateNetwork = mkDefault false;
3786 + PrivateUsers = true;
3787 + ProcSubset = "pid";
3788 + ProtectClock = true;
3789 + ProtectControlGroups = true;
3790 + ProtectHome = true;
3791 + ProtectHostname = true;
3792 + ProtectKernelLogs = true;
3793 + ProtectKernelModules = true;
3794 + ProtectKernelTunables = true;
3795 + ProtectProc = "invisible";
3796 + ProtectSystem = "strict";
3797 + RemoveIPC = true;
3798 + RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
3799 + RestrictNamespaces = true;
3800 + RestrictRealtime = true;
3801 + RestrictSUIDSGID = true;
3802 + #SocketBindAllow = [ "tcp:${toString srvCfg.port}" "tcp:${toString srvCfg.prometheusPort}" ];
3803 + #SocketBindDeny = "any";
3804 + SystemCallFilter = [
3805 + "@system-service"
3806 + "~@aio" "~@keyring" "~@memlock" "~@privileged" "~@resources" "~@timer"
3807 + "@chown" "@setuid"
3808 + ];
3809 + SystemCallArchitectures = "native";
3810 + };
3811 + } ];
3812 in
3813 -with serviceCfg; with lib; recursiveUpdate
3814 {
3815 - environment.HOME = statePath;
3816 - path = [ config.services.postgresql.package ] ++ (attrs.path or [ ]);
3817 - restartTriggers = [ config.environment.etc."sr.ht/config.ini".source ];
3818 - serviceConfig = {
3819 - Type = "simple";
3820 - User = user;
3821 - Group = user;
3822 - Restart = "always";
3823 - WorkingDirectory = statePath;
3824 - } // (if (cfg.statePath == "/var/lib/sourcehut/${serviceDrv.pname}") then {
3825 - StateDirectory = [ "sourcehut/${serviceDrv.pname}" ];
3826 - } else {})
3827 - ;
3828 + options.services.sourcehut.${srv} = {
3829 + enable = mkEnableOption "${srv} service";
3830
3831 - preStart = ''
3832 - if ! test -e ${statePath}/db; then
3833 - # Setup the initial database
3834 - ${setupDB}
3835 + user = mkOption {
3836 + type = types.str;
3837 + default = srvsrht;
3838 + description = ''
3839 + User for ${srv}.sr.ht.
3840 + '';
3841 + };
3842
3843 - # Set the initial state of the database for future database upgrades
3844 - if test -e ${cfg.python}/bin/${serviceDrv.pname}-migrate; then
3845 - # Run alembic stamp head once to tell alembic the schema is up-to-date
3846 - ${cfg.python}/bin/${serviceDrv.pname}-migrate stamp head
3847 - fi
3848 + group = mkOption {
3849 + type = types.str;
3850 + default = srvsrht;
3851 + description = ''
3852 + Group for ${srv}.sr.ht.
3853 + Membership grants access to the Git/Mercurial repositories by default,
3854 + but not to the config.ini file (where secrets are).
3855 + '';
3856 + };
3857
3858 - printf "%s" "${serviceDrv.version}" > ${statePath}/db
3859 - fi
3860 + port = mkOption {
3861 + type = types.port;
3862 + default = port;
3863 + description = ''
3864 + Port on which the "${srv}" backend should listen.
3865 + '';
3866 + };
3867
3868 - # Update copy of each users' profile to the latest
3869 - # See https://lists.sr.ht/~sircmpwn/sr.ht-admins/<20190302181207.GA13778%40cirno.my.domain>
3870 - if ! test -e ${statePath}/webhook; then
3871 - # Update ${iniKey}'s users' profile copy to the latest
3872 - ${cfg.python}/bin/srht-update-profiles ${iniKey}
3873 + redis = {
3874 + host = mkOption {
3875 + type = types.str;
3876 + default = "unix:/run/redis-sourcehut-${srvsrht}/redis.sock?db=0";
3877 + example = "redis://shared.wireguard:6379/0";
3878 + description = ''
3879 + The redis host URL. This is used for caching and temporary storage, and must
3880 + be shared between nodes (e.g. git1.sr.ht and git2.sr.ht), but need not be
3881 + shared between services. It may be shared between services, however, with no
3882 + ill effect, if this better suits your infrastructure.
3883 + '';
3884 + };
3885 + };
3886
3887 - touch ${statePath}/webhook
3888 - fi
3889 + postgresql = {
3890 + database = mkOption {
3891 + type = types.str;
3892 + default = "${srv}.sr.ht";
3893 + description = ''
3894 + PostgreSQL database name for the ${srv}.sr.ht service,
3895 + used if <xref linkend="opt-services.sourcehut.postgresql.enable"/> is <literal>true</literal>.
3896 + '';
3897 + };
3898 + };
3899
3900 - ${optionalString (builtins.hasAttr "migrate-on-upgrade" cfgIni && cfgIni.migrate-on-upgrade == "yes") ''
3901 - if [ "$(cat ${statePath}/db)" != "${serviceDrv.version}" ]; then
3902 - # Manage schema migrations using alembic
3903 - ${cfg.python}/bin/${serviceDrv.pname}-migrate -a upgrade head
3904 + gunicorn = {
3905 + extraArgs = mkOption {
3906 + type = with types; listOf str;
3907 + default = ["--timeout 120" "--workers 1" "--log-level=info"];
3908 + description = "Extra arguments passed to Gunicorn.";
3909 + };
3910 + };
3911 + } // optionalAttrs webhooks {
3912 + webhooks = {
3913 + extraArgs = mkOption {
3914 + type = with types; listOf str;
3915 + default = ["--loglevel DEBUG" "--pool eventlet" "--without-heartbeat"];
3916 + description = "Extra arguments passed to the Celery responsible for webhooks.";
3917 + };
3918 + celeryConfig = mkOption {
3919 + type = types.lines;
3920 + default = "";
3921 + description = "Content of the <literal>celeryconfig.py</literal> used by the Celery responsible for webhooks.";
3922 + };
3923 + };
3924 + };
3925
3926 - # Mark down current package version
3927 - printf "%s" "${serviceDrv.version}" > ${statePath}/db
3928 - fi
3929 - ''}
3930 + config = lib.mkIf (cfg.enable && srvCfg.enable) (mkMerge [ extraConfig {
3931 + users = {
3932 + users = {
3933 + "${srvCfg.user}" = {
3934 + isSystemUser = true;
3935 + group = mkDefault srvCfg.group;
3936 + description = mkDefault "sourcehut user for ${srv}.sr.ht";
3937 + };
3938 + };
3939 + groups = {
3940 + "${srvCfg.group}" = { };
3941 + } // optionalAttrs (cfg.postgresql.enable
3942 + && hasSuffix "0" (postgresql.settings.unix_socket_permissions or "")) {
3943 + "postgres".members = [ srvCfg.user ];
3944 + } // optionalAttrs (cfg.redis.enable
3945 + && hasSuffix "0" (redis.settings.unixsocketperm or "")) {
3946 + "redis-sourcehut-${srvsrht}".members = [ srvCfg.user ];
3947 + };
3948 + };
3949
3950 - ${attrs.preStart or ""}
3951 - '';
3952 + services.nginx = mkIf cfg.nginx.enable {
3953 + virtualHosts."${srv}.${cfg.settings."sr.ht".global-domain}" = mkMerge [ {
3954 + forceSSL = true;
3955 + locations."/".proxyPass = "http://${cfg.listenAddress}:${toString srvCfg.port}";
3956 + locations."/static" = {
3957 + root = "${pkgs.sourcehut.${srvsrht}}/${pkgs.sourcehut.python.sitePackages}/${srvsrht}";
3958 + extraConfig = mkDefault ''
3959 + expires 30d;
3960 + '';
3961 + };
3962 + } cfg.nginx.virtualHost ];
3963 + };
3964 +
3965 + services.postgresql = mkIf cfg.postgresql.enable {
3966 + authentication = ''
3967 + local ${srvCfg.postgresql.database} ${srvCfg.user} trust
3968 + '';
3969 + ensureDatabases = [ srvCfg.postgresql.database ];
3970 + ensureUsers = map (name: {
3971 + inherit name;
3972 + ensurePermissions = { "DATABASE \"${srvCfg.postgresql.database}\"" = "ALL PRIVILEGES"; };
3973 + }) [srvCfg.user];
3974 + };
3975 +
3976 + services.sourcehut.services = mkDefault (filter (s: cfg.${s}.enable)
3977 + [ "builds" "dispatch" "git" "hg" "hub" "lists" "man" "meta" "pages" "paste" "todo" ]);
3978 +
3979 + services.sourcehut.settings = mkMerge [
3980 + {
3981 + "${srv}.sr.ht".origin = mkDefault "https://${srv}.${cfg.settings."sr.ht".global-domain}";
3982 + }
3983 +
3984 + (mkIf cfg.postgresql.enable {
3985 + "${srv}.sr.ht".connection-string = mkDefault "postgresql:///${srvCfg.postgresql.database}?user=${srvCfg.user}&host=/run/postgresql";
3986 + })
3987 + ];
3988 +
3989 + services.redis.servers."sourcehut-${srvsrht}" = mkIf cfg.redis.enable {
3990 + enable = true;
3991 + databases = 3;
3992 + syslog = true;
3993 + # TODO: set a more informed value
3994 + save = mkDefault [ [1800 10] [300 100] ];
3995 + settings = {
3996 + # TODO: set a more informed value
3997 + maxmemory = "128MB";
3998 + maxmemory-policy = "volatile-ttl";
3999 + };
4000 + };
4001 +
4002 + systemd.services = mkMerge [
4003 + {
4004 + "${srvsrht}" = baseService srvsrht { allowStripe = srv == "meta"; } (mkMerge [
4005 + {
4006 + description = "sourcehut ${srv}.sr.ht website service";
4007 + before = optional cfg.nginx.enable "nginx.service";
4008 + wants = optional cfg.nginx.enable "nginx.service";
4009 + wantedBy = [ "multi-user.target" ];
4010 + path = optional cfg.postgresql.enable postgresql.package;
4011 + # Beware: change in credentials' content will not trigger restart.
4012 + restartTriggers = [ configIni ];
4013 + serviceConfig = {
4014 + Type = "simple";
4015 + Restart = mkDefault "always";
4016 + #RestartSec = mkDefault "2min";
4017 + StateDirectory = [ "sourcehut/${srvsrht}" ];
4018 + StateDirectoryMode = "2750";
4019 + ExecStart = "${cfg.python}/bin/gunicorn ${srvsrht}.app:app --name ${srvsrht} --bind ${cfg.listenAddress}:${toString srvCfg.port} " + concatStringsSep " " srvCfg.gunicorn.extraArgs;
4020 + };
4021 + preStart = let
4022 + version = pkgs.sourcehut.${srvsrht}.version;
4023 + stateDir = "/var/lib/sourcehut/${srvsrht}";
4024 + in mkBefore ''
4025 + set -x
4026 + # Use the /run/sourcehut/${srvsrht}/config.ini
4027 + # installed by a previous ExecStartPre= in baseService
4028 + cd /run/sourcehut/${srvsrht}
4029 +
4030 + if test ! -e ${stateDir}/db; then
4031 + # Setup the initial database.
4032 + # Note that it stamps the alembic head afterward
4033 + ${cfg.python}/bin/${srvsrht}-initdb
4034 + echo ${version} >${stateDir}/db
4035 + fi
4036 +
4037 + ${optionalString cfg.settings.${iniKey}.migrate-on-upgrade ''
4038 + if [ "$(cat ${stateDir}/db)" != "${version}" ]; then
4039 + # Manage schema migrations using alembic
4040 + ${cfg.python}/bin/${srvsrht}-migrate -a upgrade head
4041 + echo ${version} >${stateDir}/db
4042 + fi
4043 + ''}
4044 +
4045 + # Update copy of each users' profile to the latest
4046 + # See https://lists.sr.ht/~sircmpwn/sr.ht-admins/<20190302181207.GA13778%40cirno.my.domain>
4047 + if test ! -e ${stateDir}/webhook; then
4048 + # Update ${iniKey}'s users' profile copy to the latest
4049 + ${cfg.python}/bin/srht-update-profiles ${iniKey}
4050 + touch ${stateDir}/webhook
4051 + fi
4052 + '';
4053 + } mainService ]);
4054 + }
4055 +
4056 + (mkIf webhooks {
4057 + "${srvsrht}-webhooks" = baseService "${srvsrht}-webhooks" {}
4058 + {
4059 + description = "sourcehut ${srv}.sr.ht webhooks service";
4060 + after = [ "${srvsrht}.service" ];
4061 + wantedBy = [ "${srvsrht}.service" ];
4062 + partOf = [ "${srvsrht}.service" ];
4063 + preStart = ''
4064 + cp ${pkgs.writeText "${srvsrht}-webhooks-celeryconfig.py" srvCfg.webhooks.celeryConfig} \
4065 + /run/sourcehut/${srvsrht}-webhooks/celeryconfig.py
4066 + '';
4067 + serviceConfig = {
4068 + Type = "simple";
4069 + Restart = "always";
4070 + ExecStart = "${cfg.python}/bin/celery --app ${srvsrht}.webhooks worker --hostname ${srvsrht}-webhooks@%%h " + concatStringsSep " " srvCfg.webhooks.extraArgs;
4071 + # Avoid crashing: os.getloadavg()
4072 + ProcSubset = mkForce "all";
4073 + };
4074 + };
4075 + })
4076 +
4077 + (mapAttrs (timerName: timer: (baseService timerName {} (mkMerge [
4078 + {
4079 + description = "sourcehut ${timerName} service";
4080 + after = [ "network.target" "${srvsrht}.service" ];
4081 + serviceConfig = {
4082 + Type = "oneshot";
4083 + ExecStart = "${cfg.python}/bin/${timerName}";
4084 + };
4085 + }
4086 + (timer.service or {})
4087 + ]))) extraTimers)
4088 +
4089 + (mapAttrs (serviceName: extraService: baseService serviceName {} (mkMerge [
4090 + {
4091 + description = "sourcehut ${serviceName} service";
4092 + # So that extraServices have the PostgreSQL database initialized.
4093 + after = [ "${srvsrht}.service" ];
4094 + wantedBy = [ "${srvsrht}.service" ];
4095 + partOf = [ "${srvsrht}.service" ];
4096 + serviceConfig = {
4097 + Type = "simple";
4098 + Restart = mkDefault "always";
4099 + };
4100 + }
4101 + extraService
4102 + ])) extraServices)
4103 + ];
4104 +
4105 + systemd.timers = mapAttrs (timerName: timer:
4106 + {
4107 + description = "sourcehut timer for ${timerName}";
4108 + wantedBy = [ "timers.target" ];
4109 + inherit (timer) timerConfig;
4110 + }) extraTimers;
4111 + } ]);
4112 }
4113 - (builtins.removeAttrs attrs [ "path" "preStart" ])
4114 diff --git a/nixos/modules/services/misc/sourcehut/sourcehut.xml b/nixos/modules/services/misc/sourcehut/sourcehut.xml
4115 index ab9a8c6cb4b..41094f65a94 100644
4116 --- a/nixos/modules/services/misc/sourcehut/sourcehut.xml
4117 +++ b/nixos/modules/services/misc/sourcehut/sourcehut.xml
4118 @@ -14,13 +14,12 @@
4119 <title>Basic usage</title>
4120 <para>
4121 Sourcehut is a Python and Go based set of applications.
4122 - <literal><link linkend="opt-services.sourcehut.enable">services.sourcehut</link></literal>
4123 - by default will use
4124 + This NixOS module also provides basic configuration integrating Sourcehut into locally running
4125 <literal><link linkend="opt-services.nginx.enable">services.nginx</link></literal>,
4126 - <literal><link linkend="opt-services.nginx.enable">services.redis</link></literal>,
4127 - <literal><link linkend="opt-services.nginx.enable">services.cron</link></literal>,
4128 + <literal><link linkend="opt-services.redis.servers">services.redis.servers.sourcehut</link></literal>,
4129 + <literal><link linkend="opt-services.postfix.enable">services.postfix</link></literal>
4130 and
4131 - <literal><link linkend="opt-services.postgresql.enable">services.postgresql</link></literal>.
4132 + <literal><link linkend="opt-services.postgresql.enable">services.postgresql</link></literal> services.
4133 </para>
4134
4135 <para>
4136 @@ -42,18 +41,23 @@ in {
4137
4138 services.sourcehut = {
4139 <link linkend="opt-services.sourcehut.enable">enable</link> = true;
4140 - <link linkend="opt-services.sourcehut.originBase">originBase</link> = fqdn;
4141 - <link linkend="opt-services.sourcehut.services">services</link> = [ "meta" "man" "git" ];
4142 + <link linkend="opt-services.sourcehut.git.enable">git.enable</link> = true;
4143 + <link linkend="opt-services.sourcehut.man.enable">man.enable</link> = true;
4144 + <link linkend="opt-services.sourcehut.meta.enable">meta.enable</link> = true;
4145 + <link linkend="opt-services.sourcehut.nginx.enable">nginx.enable</link> = true;
4146 + <link linkend="opt-services.sourcehut.postfix.enable">postfix.enable</link> = true;
4147 + <link linkend="opt-services.sourcehut.postgresql.enable">postgresql.enable</link> = true;
4148 + <link linkend="opt-services.sourcehut.redis.enable">redis.enable</link> = true;
4149 <link linkend="opt-services.sourcehut.settings">settings</link> = {
4150 "sr.ht" = {
4151 environment = "production";
4152 global-domain = fqdn;
4153 origin = "https://${fqdn}";
4154 # Produce keys with srht-keygen from <package>sourcehut.coresrht</package>.
4155 - network-key = "SECRET";
4156 - service-key = "SECRET";
4157 + network-key = "/run/keys/path/to/network-key";
4158 + service-key = "/run/keys/path/to/service-key";
4159 };
4160 - webhooks.private-key= "SECRET";
4161 + webhooks.private-key= "/run/keys/path/to/webhook-key";
4162 };
4163 };
4164
4165 diff --git a/nixos/modules/services/misc/sourcehut/todo.nix b/nixos/modules/services/misc/sourcehut/todo.nix
4166 deleted file mode 100644
4167 index aec773b0669..00000000000
4168 --- a/nixos/modules/services/misc/sourcehut/todo.nix
4169 +++ /dev/null
4170 @@ -1,161 +0,0 @@
4171 -{ config, lib, pkgs, ... }:
4172 -
4173 -with lib;
4174 -let
4175 - cfg = config.services.sourcehut;
4176 - cfgIni = cfg.settings;
4177 - scfg = cfg.todo;
4178 - iniKey = "todo.sr.ht";
4179 -
4180 - rcfg = config.services.redis;
4181 - drv = pkgs.sourcehut.todosrht;
4182 -in
4183 -{
4184 - options.services.sourcehut.todo = {
4185 - user = mkOption {
4186 - type = types.str;
4187 - default = "todosrht";
4188 - description = ''
4189 - User for todo.sr.ht.
4190 - '';
4191 - };
4192 -
4193 - port = mkOption {
4194 - type = types.port;
4195 - default = 5003;
4196 - description = ''
4197 - Port on which the "todo" module should listen.
4198 - '';
4199 - };
4200 -
4201 - database = mkOption {
4202 - type = types.str;
4203 - default = "todo.sr.ht";
4204 - description = ''
4205 - PostgreSQL database name for todo.sr.ht.
4206 - '';
4207 - };
4208 -
4209 - statePath = mkOption {
4210 - type = types.path;
4211 - default = "${cfg.statePath}/todosrht";
4212 - description = ''
4213 - State path for todo.sr.ht.
4214 - '';
4215 - };
4216 - };
4217 -
4218 - config = with scfg; lib.mkIf (cfg.enable && elem "todo" cfg.services) {
4219 - users = {
4220 - users = {
4221 - "${user}" = {
4222 - isSystemUser = true;
4223 - group = user;
4224 - extraGroups = [ "postfix" ];
4225 - description = "todo.sr.ht user";
4226 - };
4227 - };
4228 - groups = {
4229 - "${user}" = { };
4230 - };
4231 - };
4232 -
4233 - services.postgresql = {
4234 - authentication = ''
4235 - local ${database} ${user} trust
4236 - '';
4237 - ensureDatabases = [ database ];
4238 - ensureUsers = [
4239 - {
4240 - name = user;
4241 - ensurePermissions = { "DATABASE \"${database}\"" = "ALL PRIVILEGES"; };
4242 - }
4243 - ];
4244 - };
4245 -
4246 - systemd = {
4247 - tmpfiles.rules = [
4248 - "d ${statePath} 0750 ${user} ${user} -"
4249 - ];
4250 -
4251 - services = {
4252 - todosrht = import ./service.nix { inherit config pkgs lib; } scfg drv iniKey {
4253 - after = [ "postgresql.service" "network.target" ];
4254 - requires = [ "postgresql.service" ];
4255 - wantedBy = [ "multi-user.target" ];
4256 -
4257 - description = "todo.sr.ht website service";
4258 -
4259 - serviceConfig.ExecStart = "${cfg.python}/bin/gunicorn ${drv.pname}.app:app -b ${cfg.address}:${toString port}";
4260 - };
4261 -
4262 - todosrht-lmtp = {
4263 - after = [ "postgresql.service" "network.target" ];
4264 - bindsTo = [ "postgresql.service" ];
4265 - wantedBy = [ "multi-user.target" ];
4266 -
4267 - description = "todo.sr.ht process service";
4268 - serviceConfig = {
4269 - Type = "simple";
4270 - User = user;
4271 - Restart = "always";
4272 - ExecStart = "${cfg.python}/bin/todosrht-lmtp";
4273 - };
4274 - };
4275 -
4276 - todosrht-webhooks = {
4277 - after = [ "postgresql.service" "network.target" ];
4278 - requires = [ "postgresql.service" ];
4279 - wantedBy = [ "multi-user.target" ];
4280 -
4281 - description = "todo.sr.ht webhooks service";
4282 - serviceConfig = {
4283 - Type = "simple";
4284 - User = user;
4285 - Restart = "always";
4286 - ExecStart = "${cfg.python}/bin/celery -A ${drv.pname}.webhooks worker --loglevel=info";
4287 - };
4288 -
4289 - };
4290 - };
4291 - };
4292 -
4293 - services.sourcehut.settings = {
4294 - # URL todo.sr.ht is being served at (protocol://domain)
4295 - "todo.sr.ht".origin = mkDefault "http://todo.${cfg.originBase}";
4296 - # Address and port to bind the debug server to
4297 - "todo.sr.ht".debug-host = mkDefault "0.0.0.0";
4298 - "todo.sr.ht".debug-port = mkDefault port;
4299 - # Configures the SQLAlchemy connection string for the database.
4300 - "todo.sr.ht".connection-string = mkDefault "postgresql:///${database}?user=${user}&host=/var/run/postgresql";
4301 - # Set to "yes" to automatically run migrations on package upgrade.
4302 - "todo.sr.ht".migrate-on-upgrade = mkDefault "yes";
4303 - # todo.sr.ht's OAuth client ID and secret for meta.sr.ht
4304 - # Register your client at meta.example.org/oauth
4305 - "todo.sr.ht".oauth-client-id = mkDefault null;
4306 - "todo.sr.ht".oauth-client-secret = mkDefault null;
4307 - # Outgoing email for notifications generated by users
4308 - "todo.sr.ht".notify-from = mkDefault "CHANGEME@example.org";
4309 - # The redis connection used for the webhooks worker
4310 - "todo.sr.ht".webhooks = mkDefault "redis://${rcfg.bind}:${toString rcfg.port}/1";
4311 - # Network-key
4312 - "todo.sr.ht".network-key = mkDefault null;
4313 -
4314 - # Path for the lmtp daemon's unix socket. Direct incoming mail to this socket.
4315 - # Alternatively, specify IP:PORT and an SMTP server will be run instead.
4316 - "todo.sr.ht::mail".sock = mkDefault "/tmp/todo.sr.ht-lmtp.sock";
4317 - # The lmtp daemon will make the unix socket group-read/write for users in this
4318 - # group.
4319 - "todo.sr.ht::mail".sock-group = mkDefault "postfix";
4320 -
4321 - "todo.sr.ht::mail".posting-domain = mkDefault "todo.${cfg.originBase}";
4322 - };
4323 -
4324 - services.nginx.virtualHosts."todo.${cfg.originBase}" = {
4325 - forceSSL = true;
4326 - locations."/".proxyPass = "http://${cfg.address}:${toString port}";
4327 - locations."/query".proxyPass = "http://${cfg.address}:${toString (port + 100)}";
4328 - locations."/static".root = "${pkgs.sourcehut.todosrht}/${pkgs.sourcehut.python.sitePackages}/todosrht";
4329 - };
4330 - };
4331 -}
4332 diff --git a/pkgs/applications/version-management/sourcehut/builds.nix b/pkgs/applications/version-management/sourcehut/builds.nix
4333 index c8163caf8ea..374736e80f5 100644
4334 --- a/pkgs/applications/version-management/sourcehut/builds.nix
4335 +++ b/pkgs/applications/version-management/sourcehut/builds.nix
4336 @@ -11,13 +11,13 @@
4337 , python
4338 }:
4339 let
4340 - version = "0.66.7";
4341 + version = "0.71.0";
4342
4343 buildWorker = src: buildGoModule {
4344 inherit src version;
4345 pname = "builds-sr-ht-worker";
4346
4347 - vendorSha256 = "sha256-giOaldV46aBqXyFH/cQVsbUr6Rb4VMhbBO86o48tRZY=";
4348 + vendorSha256 = "sha256-ZEarWM/33t+pNXUEIpfd/DkBkhu3UUg17Hh8XXWOepA=";
4349 };
4350 in
4351 buildPythonPackage rec {
4352 @@ -28,7 +28,7 @@ buildPythonPackage rec {
4353 owner = "~sircmpwn";
4354 repo = "builds.sr.ht";
4355 rev = version;
4356 - sha256 = "sha256-2MLs/DOXHjEYarXDVUcPZe3o0fmZbzVxn528SE72lhM=";
4357 + sha256 = "sha256-S3mMndUdVGi+YxAOI3wSNlSZrH3cwumxatXpErS2yQI=";
4358 };
4359
4360 nativeBuildInputs = srht.nativeBuildInputs;
4361 @@ -56,10 +56,12 @@ buildPythonPackage rec {
4362 cp ${buildWorker "${src}/worker"}/bin/worker $out/bin/builds.sr.ht-worker
4363 '';
4364
4365 + pythonImportsCheck = [ "buildsrht" ];
4366 +
4367 meta = with lib; {
4368 homepage = "https://git.sr.ht/~sircmpwn/builds.sr.ht";
4369 description = "Continuous integration service for the sr.ht network";
4370 - license = licenses.agpl3;
4371 + license = licenses.agpl3Only;
4372 maintainers = with maintainers; [ eadwu ];
4373 };
4374 }
4375 diff --git a/pkgs/applications/version-management/sourcehut/core.nix b/pkgs/applications/version-management/sourcehut/core.nix
4376 index d359d524eb2..939d9d7b750 100644
4377 --- a/pkgs/applications/version-management/sourcehut/core.nix
4378 +++ b/pkgs/applications/version-management/sourcehut/core.nix
4379 @@ -25,17 +25,16 @@
4380 , sassc
4381 , nodejs
4382 , redis
4383 -, writeText
4384 }:
4385
4386 buildPythonPackage rec {
4387 pname = "srht";
4388 - version = "0.67.4";
4389 + version = "0.67.18";
4390
4391 src = fetchgit {
4392 url = "https://git.sr.ht/~sircmpwn/core.sr.ht";
4393 rev = version;
4394 - sha256 = "sha256-XvzFfcBK5Mq8p7xEBAF/eupUE1kkUBh5k+ByM/WA9bc=";
4395 + sha256 = "sha256-lPPB1DRWHANGBBfWeu0pUn1xBkd37ZhyreozLlxX6cA=";
4396 fetchSubmodules = true;
4397 };
4398
4399 @@ -46,7 +45,12 @@ buildPythonPackage rec {
4400 };
4401
4402 patches = [
4403 - ./disable-npm-install.patch
4404 + # Disable check for npm
4405 + patches/disable-npm-install.patch
4406 + # Fix broken hack: removing dots from "builds.sr.ht" does not produce "buildsrht"
4407 + patches/srht-update-profiles/0001-fix-disgusting-hack-in-the-case-of-buildsrht.patch
4408 + # Add Unix socket support for redis-host=
4409 + patches/redis-socket/core/v2-0001-add-Unix-socket-support-for-redis-host.patch
4410 ];
4411
4412 nativeBuildInputs = [
4413 @@ -87,6 +91,7 @@ buildPythonPackage rec {
4414 '';
4415
4416 dontUseSetuptoolsCheck = true;
4417 + pythonImportsCheck = [ "srht" ];
4418
4419 meta = with lib; {
4420 homepage = "https://git.sr.ht/~sircmpwn/srht";
4421 diff --git a/pkgs/applications/version-management/sourcehut/default.nix b/pkgs/applications/version-management/sourcehut/default.nix
4422 index 401a1437b7d..7dde841b543 100644
4423 --- a/pkgs/applications/version-management/sourcehut/default.nix
4424 +++ b/pkgs/applications/version-management/sourcehut/default.nix
4425 @@ -22,10 +22,12 @@ let
4426 listssrht = self.callPackage ./lists.nix { };
4427 mansrht = self.callPackage ./man.nix { };
4428 metasrht = self.callPackage ./meta.nix { };
4429 + pagessrht = self.callPackage ./pages.nix { };
4430 pastesrht = self.callPackage ./paste.nix { };
4431 todosrht = self.callPackage ./todo.nix { };
4432
4433 scmsrht = self.callPackage ./scm.nix { };
4434 + srht-keys = self.scmsrht.srht-keys;
4435 };
4436 };
4437 in
4438 @@ -40,6 +42,8 @@ with python.pkgs; recurseIntoAttrs {
4439 listssrht = toPythonApplication listssrht;
4440 mansrht = toPythonApplication mansrht;
4441 metasrht = toPythonApplication metasrht;
4442 + pagessrht = pagessrht;
4443 pastesrht = toPythonApplication pastesrht;
4444 todosrht = toPythonApplication todosrht;
4445 + srht-keys = scmsrht.srht-keys;
4446 }
4447 diff --git a/pkgs/applications/version-management/sourcehut/dispatch.nix b/pkgs/applications/version-management/sourcehut/dispatch.nix
4448 index 637c6f9c1df..9456d0c998c 100644
4449 --- a/pkgs/applications/version-management/sourcehut/dispatch.nix
4450 +++ b/pkgs/applications/version-management/sourcehut/dispatch.nix
4451 @@ -9,13 +9,13 @@
4452
4453 buildPythonPackage rec {
4454 pname = "dispatchsrht";
4455 - version = "0.15.8";
4456 + version = "0.15.32";
4457
4458 src = fetchFromSourcehut {
4459 owner = "~sircmpwn";
4460 repo = "dispatch.sr.ht";
4461 rev = version;
4462 - sha256 = "sha256-zWCGPjIgMKHXHJUs9aciV7IFgo0rpahon6KXHDwcfss=";
4463 + sha256 = "sha256-4P4cXhjcZ8IBzpRfmYIJkzl9U4Plo36a48Pf/KjmhFY=";
4464 };
4465
4466 nativeBuildInputs = srht.nativeBuildInputs;
4467 @@ -31,10 +31,12 @@ buildPythonPackage rec {
4468 export SRHT_PATH=${srht}/${python.sitePackages}/srht
4469 '';
4470
4471 + pythonImportsCheck = [ "dispatchsrht" ];
4472 +
4473 meta = with lib; {
4474 homepage = "https://dispatch.sr.ht/~sircmpwn/dispatch.sr.ht";
4475 description = "Task dispatcher and service integration tool for the sr.ht network";
4476 - license = licenses.agpl3;
4477 + license = licenses.agpl3Only;
4478 maintainers = with maintainers; [ eadwu ];
4479 };
4480 }
4481 diff --git a/pkgs/applications/version-management/sourcehut/git.nix b/pkgs/applications/version-management/sourcehut/git.nix
4482 index e44fb9cd6c6..0a09530b14b 100644
4483 --- a/pkgs/applications/version-management/sourcehut/git.nix
4484 +++ b/pkgs/applications/version-management/sourcehut/git.nix
4485 @@ -6,42 +6,109 @@
4486 , srht
4487 , pygit2
4488 , scmsrht
4489 +, srht-keys
4490 }:
4491 let
4492 - version = "0.72.8";
4493 + version = "0.72.44";
4494
4495 src = fetchFromSourcehut {
4496 owner = "~sircmpwn";
4497 repo = "git.sr.ht";
4498 rev = version;
4499 - sha256 = "sha256-AB2uzajO5PtcpJfbOOTfuDFM6is5K39v3AZJ1hShRNc=";
4500 + sha256 = "sha256-U+hJiQpAJIHBC//M/Lfw82DLhZF46qHhz8zZW1ZvJoo=";
4501 };
4502
4503 - buildShell = src: buildGoModule {
4504 + gitsrht-shell = buildGoModule {
4505 inherit src version;
4506 + sourceRoot = "source/gitsrht-shell";
4507 pname = "gitsrht-shell";
4508 vendorSha256 = "sha256-aqUFICp0C2reqb2p6JCPAUIRsxzSv0t9BHoNWrTYfqk=";
4509 };
4510
4511 - buildDispatcher = src: buildGoModule {
4512 + gitsrht-dispatch = buildGoModule {
4513 inherit src version;
4514 + sourceRoot = "source/gitsrht-dispatch";
4515 pname = "gitsrht-dispatcher";
4516 vendorSha256 = "sha256-qWXPHo86s6iuRBhRMtmD5jxnAWKdrWHtA/iSUkdw89M=";
4517 + patches = [
4518 + # Add support for supplementary groups
4519 + patches/redis-socket/git/v2-0003-gitsrht-dispatch-add-support-for-supplementary-gr.patch
4520 + ];
4521 + patchFlags = "-p2";
4522 };
4523
4524 - buildKeys = src: buildGoModule {
4525 + gitsrht-keys = buildGoModule rec {
4526 inherit src version;
4527 + sourceRoot = "source/gitsrht-keys";
4528 pname = "gitsrht-keys";
4529 - vendorSha256 = "1d94cqy7x0q0agwg515xxsbl70b3qrzxbzsyjhn1pbyj532brn7f";
4530 + vendorSha256 = "sha256-0Rnyo4IRQFhM4LFi0499+xJaboMiKEYOgoR5BumzRE8=";
4531 +
4532 + # What follows is only to update go-redis
4533 + # go.{mod,sum} could be patched directly but that would be less resilient
4534 + # to changes from upstream, and thus harder to maintain the patching
4535 + # while it hasn't been merged upstream.
4536 +
4537 + overrideModAttrs = old: {
4538 + inherit patches patchFlags;
4539 + preBuild = ''
4540 + # This is a fixed-output derivation so it is not allowed to reference other derivations,
4541 + # but here srht-keys will be copied to vendor/ by go mod vendor
4542 + ln -s ${srht-keys} srht-keys
4543 + go mod edit -replace git.sr.ht/~sircmpwn/scm.sr.ht/srht-keys=$PWD/srht-keys
4544 + go get github.com/go-redis/redis/v8
4545 + go get github.com/go-redis/redis@none
4546 + go mod tidy
4547 + '';
4548 + # Pass updated go.{mod,sum} from go-modules to gitsrht-keys' vendor/go.{mod,sum}
4549 + postInstall = ''
4550 + cp --reflink=auto go.* $out/
4551 + '';
4552 + };
4553 +
4554 + patches = [
4555 + # Update go-redis to support Unix sockets
4556 + patches/redis-socket/git/v2-0001-gitsrht-keys-update-go-redis-to-support-Unix-sock.patch
4557 + ];
4558 + patchFlags = "-p2";
4559 + postConfigure = ''
4560 + cp -v vendor/go.{mod,sum} .
4561 + '';
4562 };
4563
4564 - buildUpdateHook = src: buildGoModule {
4565 + gitsrht-update-hook = buildGoModule rec {
4566 inherit src version;
4567 + sourceRoot = "source/gitsrht-update-hook";
4568 pname = "gitsrht-update-hook";
4569 - vendorSha256 = "0fwzqpjv8x5y3w3bfjd0x0cvqjjak23m0zj88hf32jpw49xmjkih";
4570 - };
4571 + vendorSha256 = "sha256-UoHxGVYEgTDqFzVQ2Dv6BRT4jVt+/QpNqEH3G2UWFjs=";
4572
4573 - updateHook = buildUpdateHook "${src}/gitsrht-update-hook";
4574 + # What follows is only to update go-redis
4575 +
4576 + overrideModAttrs = old: {
4577 + inherit patches patchFlags;
4578 + preBuild = ''
4579 + # This is a fixed-output derivation so it is not allowed to reference other derivations,
4580 + # but here srht-keys will be copied to vendor/ by go mod vendor
4581 + ln -s ${srht-keys} srht-keys
4582 + go mod edit -replace git.sr.ht/~sircmpwn/scm.sr.ht/srht-keys=$PWD/srht-keys
4583 + go get github.com/go-redis/redis/v8
4584 + go get github.com/go-redis/redis@none
4585 + go mod tidy
4586 + '';
4587 + # Pass updated go.{mod,sum} from go-modules to gitsrht-keys' vendor/go.{mod,sum}
4588 + postInstall = ''
4589 + cp --reflink=auto go.* $out/
4590 + '';
4591 + };
4592 +
4593 + patches = [
4594 + # Update go-redis to support Unix sockets
4595 + patches/redis-socket/git/v2-0002-gitsrht-update-hook-update-go-redis-to-support-Un.patch
4596 + ];
4597 + patchFlags = "-p2";
4598 + postConfigure = ''
4599 + cp -v vendor/go.{mod,sum} .
4600 + '';
4601 + };
4602
4603 in
4604 buildPythonPackage rec {
4605 @@ -63,19 +130,21 @@ buildPythonPackage rec {
4606
4607 postInstall = ''
4608 mkdir -p $out/bin
4609 - cp ${buildShell "${src}/gitsrht-shell"}/bin/gitsrht-shell $out/bin/gitsrht-shell
4610 - cp ${buildDispatcher "${src}/gitsrht-dispatch"}/bin/gitsrht-dispatch $out/bin/gitsrht-dispatch
4611 - cp ${buildKeys "${src}/gitsrht-keys"}/bin/gitsrht-keys $out/bin/gitsrht-keys
4612 - cp ${updateHook}/bin/gitsrht-update-hook $out/bin/gitsrht-update-hook
4613 + cp ${gitsrht-shell}/bin/gitsrht-shell $out/bin/gitsrht-shell
4614 + cp ${gitsrht-dispatch}/bin/gitsrht-dispatch $out/bin/gitsrht-dispatch
4615 + cp ${gitsrht-keys}/bin/gitsrht-keys $out/bin/gitsrht-keys
4616 + cp ${gitsrht-update-hook}/bin/gitsrht-update-hook $out/bin/gitsrht-update-hook
4617 '';
4618 passthru = {
4619 - inherit updateHook;
4620 + inherit gitsrht-shell gitsrht-dispatch gitsrht-keys gitsrht-update-hook;
4621 };
4622
4623 + pythonImportsCheck = [ "gitsrht" ];
4624 +
4625 meta = with lib; {
4626 homepage = "https://git.sr.ht/~sircmpwn/git.sr.ht";
4627 description = "Git repository hosting service for the sr.ht network";
4628 - license = licenses.agpl3;
4629 + license = licenses.agpl3Only;
4630 maintainers = with maintainers; [ eadwu ];
4631 };
4632 }
4633 diff --git a/pkgs/applications/version-management/sourcehut/hg.nix b/pkgs/applications/version-management/sourcehut/hg.nix
4634 index cddb76cabf2..1d6062d81cc 100644
4635 --- a/pkgs/applications/version-management/sourcehut/hg.nix
4636 +++ b/pkgs/applications/version-management/sourcehut/hg.nix
4637 @@ -10,12 +10,12 @@
4638
4639 buildPythonPackage rec {
4640 pname = "hgsrht";
4641 - version = "0.27.4";
4642 + version = "0.27.6";
4643
4644 src = fetchhg {
4645 url = "https://hg.sr.ht/~sircmpwn/hg.sr.ht";
4646 rev = version;
4647 - sha256 = "1c0qfi0gmbfngvds6917fy9ii2iglawn429757rh7b4bvzn7n6mr";
4648 + sha256 = "ibijvKjS4CiWTYrO6Qdh3RkD0EUE7BY8wjdPwrD6vkA=";
4649 };
4650
4651 nativeBuildInputs = srht.nativeBuildInputs;
4652 @@ -32,10 +32,12 @@ buildPythonPackage rec {
4653 export SRHT_PATH=${srht}/${python.sitePackages}/srht
4654 '';
4655
4656 + pythonImportsCheck = [ "hgsrht" ];
4657 +
4658 meta = with lib; {
4659 homepage = "https://git.sr.ht/~sircmpwn/hg.sr.ht";
4660 description = "Mercurial repository hosting service for the sr.ht network";
4661 - license = licenses.agpl3;
4662 + license = licenses.agpl3Only;
4663 maintainers = with maintainers; [ eadwu ];
4664 };
4665 }
4666 diff --git a/pkgs/applications/version-management/sourcehut/hub.nix b/pkgs/applications/version-management/sourcehut/hub.nix
4667 index 17cb3fe4b61..31191cba713 100644
4668 --- a/pkgs/applications/version-management/sourcehut/hub.nix
4669 +++ b/pkgs/applications/version-management/sourcehut/hub.nix
4670 @@ -6,13 +6,13 @@
4671
4672 buildPythonPackage rec {
4673 pname = "hubsrht";
4674 - version = "0.13.1";
4675 + version = "0.13.8";
4676
4677 src = fetchFromSourcehut {
4678 owner = "~sircmpwn";
4679 repo = "hub.sr.ht";
4680 rev = version;
4681 - sha256 = "sha256-Kqzy4mh5Nn1emzHBco/LVuXro/tW3NX+OYqdEwBSQ/U=";
4682 + sha256 = "sha256-RsRJxwViEoQLh86o+8kQE5PBlLrOyIFM7hkSGjXhqdg=";
4683 };
4684
4685 nativeBuildInputs = srht.nativeBuildInputs;
4686 @@ -26,11 +26,12 @@ buildPythonPackage rec {
4687 '';
4688
4689 dontUseSetuptoolsCheck = true;
4690 + pythonImportsCheck = [ "hubsrht" ];
4691
4692 meta = with lib; {
4693 homepage = "https://git.sr.ht/~sircmpwn/hub.sr.ht";
4694 description = "Project hub service for the sr.ht network";
4695 - license = licenses.agpl3;
4696 + license = licenses.agpl3Only;
4697 maintainers = with maintainers; [ eadwu ];
4698 };
4699 }
4700 diff --git a/pkgs/applications/version-management/sourcehut/lists.nix b/pkgs/applications/version-management/sourcehut/lists.nix
4701 index b419b49f7b5..0882241401a 100644
4702 --- a/pkgs/applications/version-management/sourcehut/lists.nix
4703 +++ b/pkgs/applications/version-management/sourcehut/lists.nix
4704 @@ -12,13 +12,13 @@
4705
4706 buildPythonPackage rec {
4707 pname = "listssrht";
4708 - version = "0.48.19";
4709 + version = "0.49.3";
4710
4711 src = fetchFromSourcehut {
4712 owner = "~sircmpwn";
4713 repo = "lists.sr.ht";
4714 rev = version;
4715 - sha256 = "sha256-bsakEMyvWaxiE4/SGcAP4mlGG9jkdHfFxpt9H+TJn/8=";
4716 + sha256 = "sha256-kEzKgB8godIL7hEXMrqaxVte6RJAegjT4keZifXbOq0=";
4717 };
4718
4719 nativeBuildInputs = srht.nativeBuildInputs;
4720 @@ -37,10 +37,12 @@ buildPythonPackage rec {
4721 export SRHT_PATH=${srht}/${python.sitePackages}/srht
4722 '';
4723
4724 + pythonImportsCheck = [ "listssrht" ];
4725 +
4726 meta = with lib; {
4727 homepage = "https://git.sr.ht/~sircmpwn/lists.sr.ht";
4728 description = "Mailing list service for the sr.ht network";
4729 - license = licenses.agpl3;
4730 + license = licenses.agpl3Only;
4731 maintainers = with maintainers; [ eadwu ];
4732 };
4733 }
4734 diff --git a/pkgs/applications/version-management/sourcehut/man.nix b/pkgs/applications/version-management/sourcehut/man.nix
4735 index bd331f000a7..47c6bb0ac4f 100644
4736 --- a/pkgs/applications/version-management/sourcehut/man.nix
4737 +++ b/pkgs/applications/version-management/sourcehut/man.nix
4738 @@ -8,13 +8,13 @@
4739
4740 buildPythonPackage rec {
4741 pname = "mansrht";
4742 - version = "0.15.12";
4743 + version = "0.15.20";
4744
4745 src = fetchFromSourcehut {
4746 owner = "~sircmpwn";
4747 repo = "man.sr.ht";
4748 rev = version;
4749 - sha256 = "sha256-MqH/8K9XRvEg6P7GHE6XXtWnhDP3wT8iGoNaFtYQbio=";
4750 + sha256 = "sha256-ulwdrVrw2bwdafgc3NrJ1J15evQ5btpHLTaiqsyA58U=";
4751 };
4752
4753 nativeBuildInputs = srht.nativeBuildInputs;
4754 @@ -29,10 +29,12 @@ buildPythonPackage rec {
4755 export SRHT_PATH=${srht}/${python.sitePackages}/srht
4756 '';
4757
4758 + pythonImportsCheck = [ "mansrht" ];
4759 +
4760 meta = with lib; {
4761 homepage = "https://git.sr.ht/~sircmpwn/man.sr.ht";
4762 description = "Wiki service for the sr.ht network";
4763 - license = licenses.agpl3;
4764 + license = licenses.agpl3Only;
4765 maintainers = with maintainers; [ eadwu ];
4766 };
4767 }
4768 diff --git a/pkgs/applications/version-management/sourcehut/meta.nix b/pkgs/applications/version-management/sourcehut/meta.nix
4769 index a285d484ed2..dbb0483c5bb 100644
4770 --- a/pkgs/applications/version-management/sourcehut/meta.nix
4771 +++ b/pkgs/applications/version-management/sourcehut/meta.nix
4772 @@ -18,19 +18,19 @@
4773 , python
4774 }:
4775 let
4776 - version = "0.53.14";
4777 + version = "0.54.4";
4778
4779 src = fetchFromSourcehut {
4780 owner = "~sircmpwn";
4781 repo = "meta.sr.ht";
4782 rev = version;
4783 - sha256 = "sha256-/+r/XLDkcSTW647xPMh5bcJmR2xZNNH74AJ5jemna2k=";
4784 + sha256 = "sha256-MleyF6aqFWbYtxRdMHXpy7HBgJKL9doBmDcYLLe8bW4=";
4785 };
4786
4787 buildApi = src: buildGoModule {
4788 inherit src version;
4789 pname = "metasrht-api";
4790 - vendorSha256 = "sha256-eZyDrr2VcNMxI++18qUy7LA1Q1YDlWCoRtl00L8lfR4=";
4791 + vendorSha256 = "sha256-gi+dGQPVzrZI+1s9SSa2M3bdgi8vwpR/ofaLG2ZX4kU=";
4792 };
4793
4794 in
4795 @@ -66,10 +66,12 @@ buildPythonPackage rec {
4796 cp ${buildApi "${src}/api/"}/bin/api $out/bin/metasrht-api
4797 '';
4798
4799 + pythonImportsCheck = [ "metasrht" ];
4800 +
4801 meta = with lib; {
4802 homepage = "https://git.sr.ht/~sircmpwn/meta.sr.ht";
4803 description = "Account management service for the sr.ht network";
4804 - license = licenses.agpl3;
4805 + license = licenses.agpl3Only;
4806 maintainers = with maintainers; [ eadwu ];
4807 };
4808 }
4809 diff --git a/pkgs/applications/version-management/sourcehut/pages-fix-syntax-error-in-schema.sql.patch b/pkgs/applications/version-management/sourcehut/pages-fix-syntax-error-in-schema.sql.patch
4810 new file mode 100644
4811 index 00000000000..9b3f6fbc6fb
4812 --- /dev/null
4813 +++ b/pkgs/applications/version-management/sourcehut/pages-fix-syntax-error-in-schema.sql.patch
4814 @@ -0,0 +1,27 @@
4815 +From 3df160ad289b25574322f587095d00d6641f057c Mon Sep 17 00:00:00 2001
4816 +From: Juan Picca <juan.picca@jumapico.uy>
4817 +Date: Wed, 21 Jul 2021 08:26:56 -0300
4818 +Subject: [PATCH] Fix syntax error in schema.sql
4819 +
4820 +---
4821 + schema.sql | 4 ++--
4822 + 1 file changed, 2 insertions(+), 2 deletions(-)
4823 +
4824 +diff --git a/schema.sql b/schema.sql
4825 +index 168377f..2e473ea 100644
4826 +--- a/schema.sql
4827 ++++ b/schema.sql
4828 +@@ -28,8 +28,8 @@ CREATE TABLE sites (
4829 + user_id integer NOT NULL references "user"(id),
4830 + domain varchar NOT NULL,
4831 + protocol protocol NOT NULL,
4832 +- version varchar NOT NULL
4833 +- UNIQUE (domain, protocol),
4834 ++ version varchar NOT NULL,
4835 ++ UNIQUE (domain, protocol)
4836 + );
4837 +
4838 + COMMIT;
4839 +--
4840 +2.32.0
4841 +
4842 diff --git a/pkgs/applications/version-management/sourcehut/pages.nix b/pkgs/applications/version-management/sourcehut/pages.nix
4843 new file mode 100644
4844 index 00000000000..d04182251a7
4845 --- /dev/null
4846 +++ b/pkgs/applications/version-management/sourcehut/pages.nix
4847 @@ -0,0 +1,34 @@
4848 +{ lib
4849 +, fetchFromSourcehut
4850 +, buildGoModule
4851 +}:
4852 +buildGoModule rec {
4853 + pname = "pagessrht";
4854 + version = "0.4.8";
4855 +
4856 + src = fetchFromSourcehut {
4857 + owner = "~sircmpwn";
4858 + repo = "pages.sr.ht";
4859 + rev = version;
4860 + sha256 = "sha256-z9w8v5e6LY6VUEczltyD55KEUUH7Gw1vUO00KPmT+D8=";
4861 + };
4862 +
4863 + vendorSha256 = "sha256-xOd9i+PNlLxZrw/+z/C9V+AbOLEociW2YHY+x1K+mJI=";
4864 +
4865 + patches = [
4866 + # Upstream after 0.4.8
4867 + ./pages-fix-syntax-error-in-schema.sql.patch
4868 + ];
4869 +
4870 + postInstall = ''
4871 + mkdir -p $out/share/sql/
4872 + cp -r -t $out/share/sql/ schema.sql migrations
4873 + '';
4874 +
4875 + meta = with lib; {
4876 + homepage = "https://git.sr.ht/~sircmpwn/pages.sr.ht";
4877 + description = "Web hosting service for the sr.ht network";
4878 + license = licenses.agpl3Only;
4879 + maintainers = with maintainers; [ eadwu ];
4880 + };
4881 +}
4882 diff --git a/pkgs/applications/version-management/sourcehut/paste.nix b/pkgs/applications/version-management/sourcehut/paste.nix
4883 index 0d8c9135493..ecd31c25deb 100644
4884 --- a/pkgs/applications/version-management/sourcehut/paste.nix
4885 +++ b/pkgs/applications/version-management/sourcehut/paste.nix
4886 @@ -8,13 +8,13 @@
4887
4888 buildPythonPackage rec {
4889 pname = "pastesrht";
4890 - version = "0.12.1";
4891 + version = "0.12.4";
4892
4893 src = fetchFromSourcehut {
4894 owner = "~sircmpwn";
4895 repo = "paste.sr.ht";
4896 rev = version;
4897 - sha256 = "sha256-QQhd2LeH9BLmlHilhsv+9fZ+RPNmEMSmOpFA3dsMBFc=";
4898 + sha256 = "sha256-hFjWa7L7JiQoG3Hm9NyoP2FNypDiW+nGDmQ2DoZkAIw=";
4899 };
4900
4901 nativeBuildInputs = srht.nativeBuildInputs;
4902 @@ -29,10 +29,12 @@ buildPythonPackage rec {
4903 export SRHT_PATH=${srht}/${python.sitePackages}/srht
4904 '';
4905
4906 + pythonImportsCheck = [ "pastesrht" ];
4907 +
4908 meta = with lib; {
4909 homepage = "https://git.sr.ht/~sircmpwn/paste.sr.ht";
4910 description = "Ad-hoc text file hosting service for the sr.ht network";
4911 - license = licenses.agpl3;
4912 + license = licenses.agpl3Only;
4913 maintainers = with maintainers; [ eadwu ];
4914 };
4915 }
4916 diff --git a/pkgs/applications/version-management/sourcehut/disable-npm-install.patch b/pkgs/applications/version-management/sourcehut/patches/disable-npm-install.patch
4917 similarity index 100%
4918 rename from pkgs/applications/version-management/sourcehut/disable-npm-install.patch
4919 rename to pkgs/applications/version-management/sourcehut/patches/disable-npm-install.patch
4920 diff --git a/pkgs/applications/version-management/sourcehut/patches/redis-socket/core/v2-0001-add-Unix-socket-support-for-redis-host.patch b/pkgs/applications/version-management/sourcehut/patches/redis-socket/core/v2-0001-add-Unix-socket-support-for-redis-host.patch
4921 new file mode 100644
4922 index 00000000000..46241bc847f
4923 --- /dev/null
4924 +++ b/pkgs/applications/version-management/sourcehut/patches/redis-socket/core/v2-0001-add-Unix-socket-support-for-redis-host.patch
4925 @@ -0,0 +1,30 @@
4926 +From c0ccc8db051a2f8278edf59b41ed238fa71aa4c0 Mon Sep 17 00:00:00 2001
4927 +From: Julien Moutinho <julm+srht@sourcephile.fr>
4928 +Date: Mon, 23 Aug 2021 18:43:18 +0200
4929 +Subject: [PATCH core.sr.ht v2] add Unix socket support for redis-host=
4930 +
4931 +---
4932 + srht/redis.py | 11 ++---------
4933 + 1 file changed, 2 insertions(+), 9 deletions(-)
4934 +
4935 +diff --git a/srht/redis.py b/srht/redis.py
4936 +index 8a9347c..2e91c35 100644
4937 +--- a/srht/redis.py
4938 ++++ b/srht/redis.py
4939 +@@ -1,11 +1,4 @@
4940 +-from redis import Redis
4941 ++from redis import from_url
4942 + from srht.config import cfg
4943 +-from urllib.parse import urlparse
4944 +
4945 +-url = cfg("sr.ht", "redis-host", "redis://localhost")
4946 +-url = urlparse(url)
4947 +-
4948 +-redis = Redis(host=url.hostname,
4949 +- port=(url.port or 6379),
4950 +- password=url.password,
4951 +- db=int(url.path[1:]) if url.path else 0)
4952 ++redis = from_url(cfg("sr.ht", "redis-host", "redis://localhost"))
4953 +--
4954 +2.32.0
4955 +
4956 diff --git a/pkgs/applications/version-management/sourcehut/patches/redis-socket/git/v2-0001-gitsrht-keys-update-go-redis-to-support-Unix-sock.patch b/pkgs/applications/version-management/sourcehut/patches/redis-socket/git/v2-0001-gitsrht-keys-update-go-redis-to-support-Unix-sock.patch
4957 new file mode 100644
4958 index 00000000000..24fbc26399c
4959 --- /dev/null
4960 +++ b/pkgs/applications/version-management/sourcehut/patches/redis-socket/git/v2-0001-gitsrht-keys-update-go-redis-to-support-Unix-sock.patch
4961 @@ -0,0 +1,40 @@
4962 +From 466528eabef3123c715420472dc2cc15e8807bdf Mon Sep 17 00:00:00 2001
4963 +From: Julien Moutinho <julm+srht@sourcephile.fr>
4964 +Date: Fri, 27 Aug 2021 15:38:28 +0200
4965 +Subject: [PATCH git.sr.ht v2 1/5] gitsrht-keys: update go-redis to support
4966 + Unix sockets
4967 +
4968 +---
4969 + gitsrht-keys/main.go | 6 +++---
4970 + 1 file changed, 3 insertions(+), 3 deletions(-)
4971 +
4972 +diff --git a/gitsrht-keys/main.go b/gitsrht-keys/main.go
4973 +index 0c1aea1..b4278c3 100644
4974 +--- a/gitsrht-keys/main.go
4975 ++++ b/gitsrht-keys/main.go
4976 +@@ -5,7 +5,7 @@ import (
4977 + "os"
4978 + "path"
4979 +
4980 +- goredis "github.com/go-redis/redis"
4981 ++ goRedis "github.com/go-redis/redis/v8"
4982 + "github.com/vaughan0/go-ini"
4983 + "git.sr.ht/~sircmpwn/scm.sr.ht/srht-keys"
4984 + )
4985 +@@ -53,11 +53,11 @@ func main() {
4986 + if redisHost == "" {
4987 + redisHost = "redis://localhost:6379"
4988 + }
4989 +- ropts, err := goredis.ParseURL(redisHost)
4990 ++ ropts, err := goRedis.ParseURL(redisHost)
4991 + if err != nil {
4992 + logger.Fatalf("Failed to parse redis host: %v", err)
4993 + }
4994 +- redis := goredis.NewClient(ropts)
4995 ++ redis := goRedis.NewClient(ropts)
4996 +
4997 + keyType, b64key, prefix, err = srhtkeys.ParseArgs(logger)
4998 + if err != nil {
4999 +--
5000 +2.32.0
5001 +
5002 diff --git a/pkgs/applications/version-management/sourcehut/patches/redis-socket/git/v2-0002-gitsrht-update-hook-update-go-redis-to-support-Un.patch b/pkgs/applications/version-management/sourcehut/patches/redis-socket/git/v2-0002-gitsrht-update-hook-update-go-redis-to-support-Un.patch
5003 new file mode 100644
5004 index 00000000000..36566a6f7d9
5005 --- /dev/null
5006 +++ b/pkgs/applications/version-management/sourcehut/patches/redis-socket/git/v2-0002-gitsrht-update-hook-update-go-redis-to-support-Un.patch
5007 @@ -0,0 +1,139 @@
5008 +From 4f947b26e42d3bcab6d675718eed28ca2fdf4762 Mon Sep 17 00:00:00 2001
5009 +From: Julien Moutinho <julm+srht@sourcephile.fr>
5010 +Date: Fri, 27 Aug 2021 15:39:29 +0200
5011 +Subject: [PATCH git.sr.ht v2 2/5] gitsrht-update-hook: update go-redis to
5012 + support Unix sockets
5013 +
5014 +---
5015 + gitsrht-update-hook/options.go | 16 +++++++++-------
5016 + gitsrht-update-hook/post-update.go | 8 ++++----
5017 + gitsrht-update-hook/update.go | 8 ++++----
5018 + 3 files changed, 17 insertions(+), 15 deletions(-)
5019 +
5020 +diff --git a/gitsrht-update-hook/options.go b/gitsrht-update-hook/options.go
5021 +index 8efbb0a..4e9d294 100644
5022 +--- a/gitsrht-update-hook/options.go
5023 ++++ b/gitsrht-update-hook/options.go
5024 +@@ -1,15 +1,17 @@
5025 + package main
5026 +
5027 + import (
5028 ++ "context"
5029 + "fmt"
5030 + "os"
5031 + "strconv"
5032 + "strings"
5033 + "time"
5034 +
5035 +- goredis "github.com/go-redis/redis"
5036 ++ goRedis "github.com/go-redis/redis/v8"
5037 + )
5038 +
5039 ++var ctx = context.Background()
5040 + var options map[string]string
5041 +
5042 + func loadOptions() {
5043 +@@ -26,19 +28,19 @@ func loadOptions() {
5044 + if !ok {
5045 + redisHost = "redis://localhost:6379"
5046 + }
5047 +- ropts, err := goredis.ParseURL(redisHost)
5048 ++ ropts, err := goRedis.ParseURL(redisHost)
5049 + if err != nil {
5050 + logger.Fatalf("Failed to parse redis host: %v", err)
5051 + }
5052 +- redis := goredis.NewClient(ropts)
5053 ++ redis := goRedis.NewClient(ropts)
5054 +
5055 + var n int
5056 + if nopts, ok := os.LookupEnv("GIT_PUSH_OPTION_COUNT"); ok {
5057 + n, _ = strconv.Atoi(nopts)
5058 +- redis.Set(fmt.Sprintf("git.sr.ht.options.%s", uuid),
5059 ++ redis.Set(ctx, fmt.Sprintf("git.sr.ht.options.%s", uuid),
5060 + nopts, 10*time.Minute)
5061 + } else {
5062 +- nopts, err := redis.Get(fmt.Sprintf(
5063 ++ nopts, err := redis.Get(ctx, fmt.Sprintf(
5064 + "git.sr.ht.options.%s", uuid)).Result()
5065 + if err != nil {
5066 + return
5067 +@@ -51,12 +53,12 @@ func loadOptions() {
5068 + opt, ok := os.LookupEnv(fmt.Sprintf("GIT_PUSH_OPTION_%d", i))
5069 + optkey := fmt.Sprintf("git.sr.ht.options.%s.%d", uuid, i)
5070 + if !ok {
5071 +- opt, err = redis.Get(optkey).Result()
5072 ++ opt, err = redis.Get(ctx, optkey).Result()
5073 + if err != nil {
5074 + return
5075 + }
5076 + } else {
5077 +- redis.Set(optkey, opt, 10*time.Minute)
5078 ++ redis.Set(ctx, optkey, opt, 10*time.Minute)
5079 + }
5080 + parts := strings.SplitN(opt, "=", 2)
5081 + if len(parts) == 1 {
5082 +diff --git a/gitsrht-update-hook/post-update.go b/gitsrht-update-hook/post-update.go
5083 +index d14d616..37c08b3 100644
5084 +--- a/gitsrht-update-hook/post-update.go
5085 ++++ b/gitsrht-update-hook/post-update.go
5086 +@@ -15,7 +15,7 @@ import (
5087 + "github.com/go-git/go-git/v5/plumbing"
5088 + "github.com/go-git/go-git/v5/plumbing/object"
5089 + "github.com/go-git/go-git/v5/plumbing/storer"
5090 +- goredis "github.com/go-redis/redis"
5091 ++ goRedis "github.com/go-redis/redis/v8"
5092 + _ "github.com/lib/pq"
5093 + )
5094 +
5095 +@@ -210,17 +210,17 @@ func postUpdate() {
5096 + if !ok {
5097 + redisHost = "redis://localhost:6379"
5098 + }
5099 +- ropts, err := goredis.ParseURL(redisHost)
5100 ++ ropts, err := goRedis.ParseURL(redisHost)
5101 + if err != nil {
5102 + logger.Fatalf("Failed to parse redis host: %v", err)
5103 + }
5104 + nbuilds := 0
5105 +- redis := goredis.NewClient(ropts)
5106 ++ redis := goRedis.NewClient(ropts)
5107 + for i, refname := range refs {
5108 + var oldref, newref string
5109 + var oldobj, newobj object.Object
5110 + updateKey := fmt.Sprintf("update.%s.%s", pushUuid, refname)
5111 +- update, err := redis.Get(updateKey).Result()
5112 ++ update, err := redis.Get(ctx, updateKey).Result()
5113 + if update == "" || err != nil {
5114 + logger.Println("redis.Get: missing key")
5115 + continue
5116 +diff --git a/gitsrht-update-hook/update.go b/gitsrht-update-hook/update.go
5117 +index 72c661a..0968cfb 100644
5118 +--- a/gitsrht-update-hook/update.go
5119 ++++ b/gitsrht-update-hook/update.go
5120 +@@ -5,7 +5,7 @@ import (
5121 + "os"
5122 + "time"
5123 +
5124 +- goredis "github.com/go-redis/redis"
5125 ++ goRedis "github.com/go-redis/redis/v8"
5126 + )
5127 +
5128 + // XXX: This is run once for every single ref that's pushed. If someone pushes
5129 +@@ -26,11 +26,11 @@ func update() {
5130 + if !ok {
5131 + redisHost = "redis://localhost:6379"
5132 + }
5133 +- ropts, err := goredis.ParseURL(redisHost)
5134 ++ ropts, err := goRedis.ParseURL(redisHost)
5135 + if err != nil {
5136 + logger.Fatalf("Failed to parse redis host: %v", err)
5137 + }
5138 +- redis := goredis.NewClient(ropts)
5139 +- redis.Set(fmt.Sprintf("update.%s.%s", pushUuid, refname),
5140 ++ redis := goRedis.NewClient(ropts)
5141 ++ redis.Set(ctx, fmt.Sprintf("update.%s.%s", pushUuid, refname),
5142 + fmt.Sprintf("%s:%s", oldref, newref), 10*time.Minute)
5143 + }
5144 +--
5145 +2.32.0
5146 +
5147 diff --git a/pkgs/applications/version-management/sourcehut/patches/redis-socket/git/v2-0003-gitsrht-dispatch-add-support-for-supplementary-gr.patch b/pkgs/applications/version-management/sourcehut/patches/redis-socket/git/v2-0003-gitsrht-dispatch-add-support-for-supplementary-gr.patch
5148 new file mode 100644
5149 index 00000000000..a96f6430b56
5150 --- /dev/null
5151 +++ b/pkgs/applications/version-management/sourcehut/patches/redis-socket/git/v2-0003-gitsrht-dispatch-add-support-for-supplementary-gr.patch
5152 @@ -0,0 +1,57 @@
5153 +From ef02ce68925888b2bca77713c6321cb33023e026 Mon Sep 17 00:00:00 2001
5154 +From: Julien Moutinho <julm+srht@sourcephile.fr>
5155 +Date: Fri, 27 Aug 2021 17:42:33 +0200
5156 +Subject: [PATCH git.sr.ht v2 3/5] gitsrht-dispatch: add support for
5157 + supplementary groups
5158 +
5159 +---
5160 + gitsrht-dispatch/main.go | 17 ++++++++++++++---
5161 + 1 file changed, 14 insertions(+), 3 deletions(-)
5162 +
5163 +diff --git a/gitsrht-dispatch/main.go b/gitsrht-dispatch/main.go
5164 +index d7aee14..5f17b75 100644
5165 +--- a/gitsrht-dispatch/main.go
5166 ++++ b/gitsrht-dispatch/main.go
5167 +@@ -17,6 +17,7 @@ type Dispatcher struct {
5168 + cmd string
5169 + uid int
5170 + gid int
5171 ++ gids []int
5172 + }
5173 +
5174 + func main() {
5175 +@@ -70,11 +71,20 @@ AuthorizedKeysUser=root`, os.Args[0])
5176 + if err != nil {
5177 + logger.Fatalf("Error looking up group %s: %v", spec[1], err)
5178 + }
5179 ++ groups, err := user.GroupIds()
5180 ++ if err != nil {
5181 ++ logger.Fatalf("Error looking up supplementary groups of user %s: %v", spec[0], err)
5182 ++ }
5183 ++ gids := make([]int, len(groups))
5184 ++ for i, grp := range groups {
5185 ++ sgid, _ := strconv.Atoi(grp)
5186 ++ gids[i] = sgid
5187 ++ }
5188 + uid, _ := strconv.Atoi(user.Uid)
5189 + gid, _ := strconv.Atoi(group.Gid)
5190 +- dispatchers[uid] = Dispatcher{cmd, uid, gid}
5191 +- logger.Printf("Registered dispatcher for %s(%d):%s(%d): %s",
5192 +- spec[0], uid, spec[1], gid, cmd)
5193 ++ dispatchers[uid] = Dispatcher{cmd, uid, gid, gids}
5194 ++ logger.Printf("Registered dispatcher for %s(%d):%s(%d):(%s): %s",
5195 ++ spec[0], uid, spec[1], gid, strings.Join(groups, ","), cmd)
5196 + }
5197 +
5198 + var user *osuser.User
5199 +@@ -93,6 +103,7 @@ AuthorizedKeysUser=root`, os.Args[0])
5200 +
5201 + if dispatcher, ok := dispatchers[uid]; ok {
5202 + logger.Printf("Dispatching to %s", dispatcher.cmd)
5203 ++ syscall.Setgroups(dispatcher.gids)
5204 + syscall.Setgid(dispatcher.gid)
5205 + syscall.Setuid(dispatcher.uid)
5206 + if err := syscall.Exec(dispatcher.cmd, append([]string{
5207 +--
5208 +2.32.0
5209 +
5210 diff --git a/pkgs/applications/version-management/sourcehut/patches/redis-socket/scm/v2-0001-srht-keys-update-go-redis-to-support-Unix-sockets.patch b/pkgs/applications/version-management/sourcehut/patches/redis-socket/scm/v2-0001-srht-keys-update-go-redis-to-support-Unix-sockets.patch
5211 new file mode 100644
5212 index 00000000000..389cd71751a
5213 --- /dev/null
5214 +++ b/pkgs/applications/version-management/sourcehut/patches/redis-socket/scm/v2-0001-srht-keys-update-go-redis-to-support-Unix-sockets.patch
5215 @@ -0,0 +1,74 @@
5216 +From 538ff956141f5b56b77233664d4d4ea5eff8ad08 Mon Sep 17 00:00:00 2001
5217 +From: Julien Moutinho <julm+srht@sourcephile.fr>
5218 +Date: Fri, 27 Aug 2021 12:48:56 +0200
5219 +Subject: [PATCH v2 1/2] srht-keys: update go-redis to support Unix sockets
5220 +
5221 +---
5222 + srht-keys/srhtkeys.go | 13 ++++++++-----
5223 + 1 file changed, 8 insertions(+), 5 deletions(-)
5224 +
5225 +diff --git a/srht-keys/srhtkeys.go b/srht-keys/srhtkeys.go
5226 +index be925ed..4cc144c 100644
5227 +--- a/srht-keys/srhtkeys.go
5228 ++++ b/srht-keys/srhtkeys.go
5229 +@@ -1,6 +1,7 @@
5230 + package srhtkeys
5231 +
5232 + import (
5233 ++ "context"
5234 + "database/sql"
5235 + "encoding/json"
5236 + "errors"
5237 +@@ -12,7 +13,7 @@ import (
5238 + "path"
5239 + "time"
5240 +
5241 +- goredis "github.com/go-redis/redis"
5242 ++ goRedis "github.com/go-redis/redis/v8"
5243 + "github.com/google/uuid"
5244 + _ "github.com/lib/pq"
5245 + "github.com/vaughan0/go-ini"
5246 +@@ -37,6 +38,8 @@ type MetaSSHKey struct {
5247 + Owner MetaUser `json:"owner"`
5248 + }
5249 +
5250 ++var ctx = context.Background()
5251 ++
5252 + // Stores the SSH key in the database and returns the user's ID.
5253 + func storeKey(logger *log.Logger, db *sql.DB, key *MetaSSHKey) (int, error) {
5254 + logger.Println("Storing meta.sr.ht key in database")
5255 +@@ -84,7 +87,7 @@ func storeKey(logger *log.Logger, db *sql.DB, key *MetaSSHKey) (int, error) {
5256 + }
5257 +
5258 + func fetchKeysFromMeta(logger *log.Logger, config ini.File,
5259 +- redis *goredis.Client, service string, b64key string) (string, int) {
5260 ++ redis *goRedis.Client, service string, b64key string) (string, int) {
5261 +
5262 + meta, ok := config.Get("meta.sr.ht", "internal-origin")
5263 + if !ok {
5264 +@@ -145,7 +148,7 @@ func fetchKeysFromMeta(logger *log.Logger, config ini.File,
5265 + if err != nil {
5266 + logger.Printf("Caching SSH key in redis failed: %v", err)
5267 + } else {
5268 +- redis.Set(cacheKey, cacheBytes, 7*24*time.Hour)
5269 ++ redis.Set(ctx, cacheKey, cacheBytes, 7*24*time.Hour)
5270 + }
5271 +
5272 + return key.Owner.Username, userId
5273 +@@ -164,11 +167,11 @@ func ParseArgs(logger *log.Logger) (string, string, string, error) {
5274 + }
5275 +
5276 + func UserFromKey(logger *log.Logger, config ini.File,
5277 +- redis *goredis.Client, service string, b64key string) (string, int) {
5278 ++ redis *goRedis.Client, service string, b64key string) (string, int) {
5279 +
5280 + cacheKey := fmt.Sprintf("%s.ssh-keys.%s", service, b64key)
5281 + logger.Printf("Cache key for SSH key lookup: %s", cacheKey)
5282 +- cacheBytes, err := redis.Get(cacheKey).Bytes()
5283 ++ cacheBytes, err := redis.Get(ctx, cacheKey).Bytes()
5284 + var (
5285 + username string
5286 + userId int
5287 +--
5288 +2.32.0
5289 +
5290 diff --git a/pkgs/applications/version-management/sourcehut/patches/redis-socket/scm/v2-0002-srht-keys-update-go.-mod-sum-for-go-redis.patch b/pkgs/applications/version-management/sourcehut/patches/redis-socket/scm/v2-0002-srht-keys-update-go.-mod-sum-for-go-redis.patch
5291 new file mode 100644
5292 index 00000000000..8db7dc674c7
5293 --- /dev/null
5294 +++ b/pkgs/applications/version-management/sourcehut/patches/redis-socket/scm/v2-0002-srht-keys-update-go.-mod-sum-for-go-redis.patch
5295 @@ -0,0 +1,155 @@
5296 +From d0862969b1470701edded4772337822ca52c8509 Mon Sep 17 00:00:00 2001
5297 +From: Julien Moutinho <julm+srht@sourcephile.fr>
5298 +Date: Fri, 27 Aug 2021 13:06:27 +0200
5299 +Subject: [PATCH v2 2/2] srht-keys: update go.{mod,sum} for go-redis
5300 +
5301 +---
5302 + srht-keys/go.mod | 2 +-
5303 + srht-keys/go.sum | 103 ++++++++++++++++++++++++++++++++++++++++++++---
5304 + 2 files changed, 99 insertions(+), 6 deletions(-)
5305 +
5306 +diff --git a/srht-keys/go.mod b/srht-keys/go.mod
5307 +index d275913..8d1c10a 100644
5308 +--- a/srht-keys/go.mod
5309 ++++ b/srht-keys/go.mod
5310 +@@ -4,7 +4,7 @@ go 1.13
5311 +
5312 + require (
5313 + git.sr.ht/~sircmpwn/core-go v0.0.0-20201005173246-a9e49d17a1e6
5314 +- github.com/go-redis/redis v6.15.9+incompatible
5315 ++ github.com/go-redis/redis/v8 v8.11.3
5316 + github.com/google/uuid v1.1.1
5317 + github.com/lib/pq v1.8.0
5318 + github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec
5319 +diff --git a/srht-keys/go.sum b/srht-keys/go.sum
5320 +index 974326e..a264a26 100644
5321 +--- a/srht-keys/go.sum
5322 ++++ b/srht-keys/go.sum
5323 +@@ -1,26 +1,119 @@
5324 +-git.sr.ht/~sircmpwn/core-go v0.0.0-20200820135923-98806e712f5e h1:TJqf/neVU5peFAS9WcR1aADXcflPOvAd7ABEirmU7m0=
5325 +-git.sr.ht/~sircmpwn/core-go v0.0.0-20200820135923-98806e712f5e/go.mod h1:aXSNgRsGoI3tTFKlwD0xm2htbEzKlR2xUm1osRxfhOM=
5326 + git.sr.ht/~sircmpwn/core-go v0.0.0-20201005173246-a9e49d17a1e6 h1:Ky6HzcRmbMUxOrWXv04+mb97GkyxO/Nx7v8uJBUdpNk=
5327 + git.sr.ht/~sircmpwn/core-go v0.0.0-20201005173246-a9e49d17a1e6/go.mod h1:HpPX22ilJUWKOA4NDhrOcIyblQhdiKHPg4oMJFYdh0Y=
5328 ++github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
5329 ++github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
5330 + github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
5331 ++github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
5332 ++github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
5333 ++github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
5334 ++github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
5335 ++github.com/fernet/fernet-go v0.0.0-20191111064656-eff2850e6001 h1:/UMxx5lGDg30aioUL9e7xJnbJfJeX7vhcm57fa5udaI=
5336 + github.com/fernet/fernet-go v0.0.0-20191111064656-eff2850e6001/go.mod h1:2H9hjfbpSMHwY503FclkV/lZTBh2YlOmLLSda12uL8c=
5337 +-github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
5338 +-github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
5339 ++github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
5340 ++github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
5341 ++github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
5342 ++github.com/go-redis/redis/v8 v8.11.3 h1:GCjoYp8c+yQTJfc0n69iwSiHjvuAdruxl7elnZCxgt8=
5343 ++github.com/go-redis/redis/v8 v8.11.3/go.mod h1:xNJ9xDG09FsIPwh3bWdk+0oDWHbtF9rPN0F/oD9XeKc=
5344 ++github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
5345 ++github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
5346 ++github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
5347 ++github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
5348 ++github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
5349 ++github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
5350 ++github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
5351 ++github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
5352 ++github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
5353 ++github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
5354 ++github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
5355 ++github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
5356 ++github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
5357 ++github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
5358 ++github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
5359 ++github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
5360 ++github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
5361 + github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
5362 + github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
5363 ++github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
5364 + github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg=
5365 + github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
5366 ++github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
5367 ++github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
5368 ++github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
5369 ++github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
5370 ++github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
5371 ++github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
5372 ++github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
5373 ++github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
5374 ++github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
5375 ++github.com/onsi/gomega v1.15.0 h1:WjP/FQ/sk43MRmnEcT+MlDw2TFvkrXlprrPST/IudjU=
5376 ++github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0=
5377 ++github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
5378 + github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
5379 + github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
5380 ++github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
5381 ++github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
5382 + github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
5383 + github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec h1:DGmKwyZwEB8dI7tbLt/I/gQuP559o/0FrAkHKlQM/Ks=
5384 + github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec/go.mod h1:owBmyHYMLkxyrugmfwE/DLJyW8Ro9mkphwuVErQ0iUw=
5385 ++github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
5386 + golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
5387 +-golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig=
5388 ++golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
5389 ++golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
5390 + golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
5391 ++golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
5392 ++golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
5393 + golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
5394 ++golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
5395 ++golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
5396 ++golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
5397 ++golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0=
5398 ++golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
5399 ++golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
5400 ++golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
5401 ++golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
5402 ++golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
5403 + golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
5404 + golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
5405 ++golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
5406 ++golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
5407 ++golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
5408 ++golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
5409 ++golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
5410 ++golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
5411 ++golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
5412 ++golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c=
5413 ++golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
5414 ++golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
5415 + golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
5416 ++golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
5417 ++golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
5418 ++golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
5419 ++golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
5420 ++golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
5421 ++golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
5422 ++golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
5423 ++golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
5424 ++golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
5425 ++golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
5426 ++golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
5427 ++google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
5428 ++google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
5429 ++google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
5430 ++google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
5431 ++google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
5432 ++google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
5433 ++google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
5434 ++google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
5435 ++google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
5436 ++gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
5437 + gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
5438 ++gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
5439 ++gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
5440 ++gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
5441 ++gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
5442 ++gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
5443 ++gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
5444 ++gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
5445 ++gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
5446 ++gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
5447 + gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
5448 +--
5449 +2.32.0
5450 +
5451 diff --git a/pkgs/applications/version-management/sourcehut/patches/srht-update-profiles/0001-fix-disgusting-hack-in-the-case-of-buildsrht.patch b/pkgs/applications/version-management/sourcehut/patches/srht-update-profiles/0001-fix-disgusting-hack-in-the-case-of-buildsrht.patch
5452 new file mode 100644
5453 index 00000000000..9db31c9e554
5454 --- /dev/null
5455 +++ b/pkgs/applications/version-management/sourcehut/patches/srht-update-profiles/0001-fix-disgusting-hack-in-the-case-of-buildsrht.patch
5456 @@ -0,0 +1,25 @@
5457 +From 8bb7c927c815582b26d026f4b2ea72f0245ccab7 Mon Sep 17 00:00:00 2001
5458 +From: Julien Moutinho <julm+srht@sourcephile.fr>
5459 +Date: Mon, 23 Aug 2021 18:45:07 +0200
5460 +Subject: [PATCH core.sr.ht] fix "disgusting hack" in the case of buildsrht
5461 +
5462 +---
5463 + srht-update-profiles | 2 +-
5464 + 1 file changed, 1 insertion(+), 1 deletion(-)
5465 +
5466 +diff --git a/srht-update-profiles b/srht-update-profiles
5467 +index 0d9588c..063d6e2 100755
5468 +--- a/srht-update-profiles
5469 ++++ b/srht-update-profiles
5470 +@@ -3,7 +3,7 @@ import sys
5471 + import os
5472 + sys.path.append(os.getcwd())
5473 + site = sys.argv[1]
5474 +-app = __import__(site.replace(".", "") + ".app").app.app # disgusting hack
5475 ++app = __import__(site.replace(".", "").replace("builds","build") + ".app").app.app # disgusting hack
5476 + from srht.config import cfg
5477 + from srht.database import db, DbSession
5478 + db = DbSession(cfg(site, "connection-string"))
5479 +--
5480 +2.32.0
5481 +
5482 diff --git a/pkgs/applications/version-management/sourcehut/scm.nix b/pkgs/applications/version-management/sourcehut/scm.nix
5483 index 1f385265360..6737251f833 100644
5484 --- a/pkgs/applications/version-management/sourcehut/scm.nix
5485 +++ b/pkgs/applications/version-management/sourcehut/scm.nix
5486 @@ -1,22 +1,59 @@
5487 { lib
5488 , fetchFromSourcehut
5489 +, buildGoModule
5490 , buildPythonPackage
5491 , srht
5492 , redis
5493 , pyyaml
5494 , buildsrht
5495 -, writeText
5496 +, applyPatches
5497 }:
5498
5499 buildPythonPackage rec {
5500 pname = "scmsrht";
5501 - version = "0.22.9";
5502 + version = "0.22.13";
5503
5504 src = fetchFromSourcehut {
5505 owner = "~sircmpwn";
5506 repo = "scm.sr.ht";
5507 rev = version;
5508 - sha256 = "sha256-327G6C8FW+iZx+167D7TQsFtV6FGc8MpMVo9L/cUUqU=";
5509 + sha256 = "sha256-9iRmQBt4Cxr5itTk34b8iDRyXYDHTDfZjV0SIDT/kkM=";
5510 + };
5511 +
5512 + passthru = {
5513 + srht-keys = buildGoModule rec {
5514 + inherit src version;
5515 + sourceRoot = "source/srht-keys";
5516 + pname = "srht-keys";
5517 + vendorSha256 = "sha256-lQk1dymMCefHMFJhO3yC/muBP/cxI//5Yz991D2YZY4=";
5518 +
5519 + # What follows is only to update go-redis
5520 + # go.{mod,sum} could be patched directly but that would be less resilient
5521 + # to changes from upstream, and thus harder to maintain the patching
5522 + # while it hasn't been merged upstream.
5523 +
5524 + overrideModAttrs = old: {
5525 + inherit patches patchFlags;
5526 + preBuild = ''
5527 + go get github.com/go-redis/redis/v8
5528 + go get github.com/go-redis/redis@none
5529 + go mod tidy
5530 + '';
5531 + # Pass updated go.{mod,sum} from go-modules to srht-keys's vendor/go.{mod,sum}
5532 + postInstall = ''
5533 + cp --reflink=auto go.* $out
5534 + '';
5535 + };
5536 +
5537 + patches = [
5538 + # Update go-redis to support Unix sockets
5539 + patches/redis-socket/scm/v2-0001-srht-keys-update-go-redis-to-support-Unix-sockets.patch
5540 + ];
5541 + patchFlags = "-p2";
5542 + postInstall = ''
5543 + cp --reflink=auto *.go vendor/go.* $out
5544 + '';
5545 + };
5546 };
5547
5548 nativeBuildInputs = srht.nativeBuildInputs;
5549 @@ -33,11 +70,12 @@ buildPythonPackage rec {
5550 '';
5551
5552 dontUseSetuptoolsCheck = true;
5553 + pythonImportsCheck = [ "scmsrht" ];
5554
5555 meta = with lib; {
5556 homepage = "https://git.sr.ht/~sircmpwn/git.sr.ht";
5557 description = "Shared support code for sr.ht source control services.";
5558 - license = licenses.agpl3;
5559 + license = licenses.agpl3Only;
5560 maintainers = with maintainers; [ eadwu ];
5561 };
5562 }
5563 diff --git a/pkgs/applications/version-management/sourcehut/todo.nix b/pkgs/applications/version-management/sourcehut/todo.nix
5564 index 85e1f5637b6..e091341c7dd 100644
5565 --- a/pkgs/applications/version-management/sourcehut/todo.nix
5566 +++ b/pkgs/applications/version-management/sourcehut/todo.nix
5567 @@ -12,13 +12,13 @@
5568
5569 buildPythonPackage rec {
5570 pname = "todosrht";
5571 - version = "0.64.14";
5572 + version = "0.64.24";
5573
5574 src = fetchFromSourcehut {
5575 owner = "~sircmpwn";
5576 repo = "todo.sr.ht";
5577 rev = version;
5578 - sha256 = "sha256-huIAhn6h1F5w5ST4/yBwr82kAzyYwhLu+gpRuOQgnsE=";
5579 + sha256 = "sha256-H2XGOxHyurXw3GekZJXSO6RMChRjNbjqxik/mvFVqfY=";
5580 };
5581
5582 nativeBuildInputs = srht.nativeBuildInputs;
5583 @@ -42,11 +42,12 @@ buildPythonPackage rec {
5584 ];
5585
5586 dontUseSetuptoolsCheck = true;
5587 + pythonImportsCheck = [ "todosrht" ];
5588
5589 meta = with lib; {
5590 homepage = "https://todo.sr.ht/~sircmpwn/todo.sr.ht";
5591 description = "Ticket tracking service for the sr.ht network";
5592 - license = licenses.agpl3;
5593 + license = licenses.agpl3Only;
5594 maintainers = with maintainers; [ eadwu ];
5595 };
5596 }
5597 diff --git a/pkgs/applications/version-management/sourcehut/update.sh b/pkgs/applications/version-management/sourcehut/update.sh
5598 index 156d4cc35e4..6733046d000 100755
5599 --- a/pkgs/applications/version-management/sourcehut/update.sh
5600 +++ b/pkgs/applications/version-management/sourcehut/update.sh
5601 @@ -1,8 +1,11 @@
5602 #! /usr/bin/env nix-shell
5603 #! nix-shell -i bash -p git mercurial common-updater-scripts
5604 +set -x
5605
5606 -cd "$(dirname "${BASH_SOURCE[0]}")"
5607 +cd "$(dirname "${BASH_SOURCE[0]}")" || exit 1
5608 root=../../../..
5609 +tmp=$(mktemp -d)
5610 +trap 'rm -rf "$tmp"' EXIT
5611
5612 default() {
5613 (cd "$root" && nix-instantiate --eval --strict -A "sourcehut.python.pkgs.$1.meta.position" | sed -re 's/^"(.*):[0-9]+"$/\1/')
5614 @@ -13,19 +16,18 @@ version() {
5615 }
5616
5617 src_url() {
5618 - (cd "$root" && nix-instantiate --eval --strict -A "sourcehut.python.pkgs.$1.src.drvAttrs.url" | tr -d '"')
5619 + nix-instantiate --eval --strict --expr " with import $root {}; let src = sourcehut.python.pkgs.$1.drvAttrs.src; in src.url or src.meta.homepage" | tr -d '"'
5620 }
5621
5622 get_latest_version() {
5623 src="$(src_url "$1")"
5624 - tmp=$(mktemp -d)
5625 -
5626 + rm -rf "$tmp"
5627 if [ "$1" = "hgsrht" ]; then
5628 - hg clone "$src" "$tmp" &> /dev/null
5629 + hg clone "$src" "$tmp" >/dev/null
5630 printf "%s" "$(cd "$tmp" && hg log --limit 1 --template '{latesttag}')"
5631 else
5632 - git clone "$src" "$tmp"
5633 - printf "%s" "$(cd "$tmp" && git describe $(git rev-list --tags --max-count=1))"
5634 + git clone "$src" "$tmp" >/dev/null
5635 + printf "%s" "$(cd "$tmp" && git describe "$(git rev-list --tags --max-count=1)")"
5636 fi
5637 }
5638
5639 @@ -36,19 +38,33 @@ update_version() {
5640
5641 (cd "$root" && update-source-version "sourcehut.python.pkgs.$1" "$version")
5642
5643 + # Update vendorSha256 of Go modules
5644 + nixFile="${1%srht}".nix
5645 + nixFile="${nixFile/build/builds}"
5646 + retry=true
5647 + while "$retry"; do
5648 + retry=false;
5649 + exec < <(exec nix -L build -f "$root" sourcehut.python.pkgs."$1" 2>&1)
5650 + while IFS=' :' read -r origin hash; do
5651 + case "$origin" in
5652 + (expected|specified) oldHash="$hash";;
5653 + (got) sed -i "s|$oldHash|$(nix hash to-sri --type sha256 "$hash")|" "$nixFile"; retry=true; break;;
5654 + (*) printf >&2 "%s\n" "$origin${hash:+:$hash}"
5655 + esac
5656 + done
5657 + done
5658 +
5659 git add "$default_nix"
5660 - git commit -m "$1: $version_old -> $version"
5661 + git commit -m "sourcehut.$1: $version_old -> $version"
5662 }
5663
5664 -services=( "srht" "buildsrht" "dispatchsrht" "gitsrht" "hgsrht" "hubsrht" "listssrht" "mansrht"
5665 - "metasrht" "pastesrht" "todosrht" "scmsrht" )
5666 -
5667 -# Whether or not a specific service is requested
5668 -if [ -n "$1" ]; then
5669 - version="$(get_latest_version "$1")"
5670 - (cd "$root" && update-source-version "sourcehut.python.pkgs.$1" "$version")
5671 +if [ $# -gt 0 ]; then
5672 + services=("$@")
5673 else
5674 - for service in "${services[@]}"; do
5675 - update_version "$service"
5676 - done
5677 + services=( "srht" "buildsrht" "dispatchsrht" "gitsrht" "hgsrht" "hubsrht" "listssrht" "mansrht"
5678 + "metasrht" "pagessrht" "pastesrht" "todosrht" "scmsrht" )
5679 fi
5680 +
5681 +for service in "${services[@]}"; do
5682 + update_version "$service"
5683 +done