···
{ config, pkgs, lib, ... }:
4
+
inherit (config.services) nginx postfix postgresql redis;
5
+
inherit (config.users) users groups;
cfg = config.services.sourcehut;
6
-
cfgIni = cfg.settings;
7
-
settingsFormat = pkgs.formats.ini { };
7
+
domain = cfg.settings."sr.ht".global-domain;
8
+
settingsFormat = pkgs.formats.ini {
9
+
listToValue = concatMapStringsSep "," (generators.mkValueStringDefault {});
11
+
if v == null then ""
12
+
else generators.mkKeyValueDefault {
14
+
if v == true then "yes"
15
+
else if v == false then "no"
16
+
else generators.mkValueStringDefault {} v;
19
+
configIniOfService = srv: settingsFormat.generate "sourcehut-${srv}-config.ini"
20
+
# Each service needs access to only a subset of sections (and secrets).
21
+
(filterAttrs (k: v: v != null)
22
+
(mapAttrs (section: v:
23
+
let srvMatch = builtins.match "^([a-z]*)\\.sr\\.ht(::.*)?$" section; in
24
+
if srvMatch == null # Include sections shared by all services
25
+
|| head srvMatch == srv # Include sections for the service being configured
27
+
# Enable Web links and integrations between services.
28
+
else if tail srvMatch == [ null ] && elem (head srvMatch) cfg.services
31
+
# mansrht crashes without it
32
+
oauth-client-id = v.oauth-client-id or null;
34
+
# Drop sub-sections of other services
36
+
(recursiveUpdate cfg.settings {
37
+
# Those paths are mounted using BindPaths= or BindReadOnlyPaths=
38
+
# for services needing access to them.
39
+
"builds.sr.ht::worker".buildlogs = "/var/log/sourcehut/buildsrht/logs";
40
+
"git.sr.ht".post-update-script = "/var/lib/sourcehut/gitsrht/bin/post-update-script";
41
+
"git.sr.ht".repos = "/var/lib/sourcehut/gitsrht/repos";
42
+
"hg.sr.ht".changegroup-script = "/var/lib/sourcehut/hgsrht/bin/changegroup-script";
43
+
"hg.sr.ht".repos = "/var/lib/sourcehut/hgsrht/repos";
44
+
# Making this a per service option despite being in a global section,
45
+
# so that it uses the redis-server used by the service.
46
+
"sr.ht".redis-host = cfg.${srv}.redis.host;
48
+
commonServiceSettings = srv: {
50
+
description = "URL ${srv}.sr.ht is being served at (protocol://domain)";
52
+
default = "https://${srv}.${domain}";
53
+
defaultText = "https://${srv}.example.com";
55
+
debug-host = mkOption {
56
+
description = "Address to bind the debug server to.";
57
+
type = with types; nullOr str;
60
+
debug-port = mkOption {
61
+
description = "Port to bind the debug server to.";
62
+
type = with types; nullOr str;
65
+
connection-string = mkOption {
66
+
description = "SQLAlchemy connection string for the database.";
68
+
default = "postgresql:///localhost?user=${srv}srht&host=/run/postgresql";
70
+
migrate-on-upgrade = mkEnableOption "automatic migrations on package upgrade" // { default = true; };
71
+
oauth-client-id = mkOption {
72
+
description = "${srv}.sr.ht's OAuth client id for meta.sr.ht.";
75
+
oauth-client-secret = mkOption {
76
+
description = "${srv}.sr.ht's OAuth client secret for meta.sr.ht.";
78
+
apply = s: "<" + toString s;
# Specialized python containing all the modules
python = pkgs.sourcehut.python.withPackages (ps: with ps; [
86
+
# For monitoring Celery: sudo -u listssrht celery --app listssrht.process -b redis+socket:///run/redis-sourcehut/redis.sock?virtual_host=5 flower
···
98
+
# Not a python package
103
+
mkOptionNullOrStr = description: mkOption {
104
+
inherit description;
105
+
type = with types; nullOr str;
39
-
(mkRemovedOptionModule [ "services" "sourcehut" "nginx" "enable" ] ''
40
-
The sourcehut module supports `nginx` as a local reverse-proxy by default and doesn't
41
-
support other reverse-proxies officially.
43
-
However it's possible to use an alternative reverse-proxy by
46
-
* adjusting the relevant settings for server addresses and ports directly
48
-
Further details about this can be found in the `Sourcehut`-section of the NixOS-manual.
options.services.sourcehut = {
57
-
Enable sourcehut - git hosting, continuous integration, mailing list, ticket tracking,
58
-
task dispatching, wiki and account management services
111
+
enable = mkEnableOption ''
112
+
sourcehut - git hosting, continuous integration, mailing list, ticket tracking,
113
+
task dispatching, wiki and account management services
63
-
type = types.nonEmptyListOf (types.enum [ "builds" "dispatch" "git" "hub" "hg" "lists" "man" "meta" "paste" "todo" ]);
64
-
default = [ "man" "meta" "paste" ];
65
-
example = [ "builds" "dispatch" "git" "hub" "hg" "lists" "man" "meta" "paste" "todo" ];
67
-
Services to enable on the sourcehut network.
71
-
originBase = mkOption {
73
-
default = with config.networking; hostName + lib.optionalString (domain != null) ".${domain}";
74
-
defaultText = literalExpression ''
75
-
with config.networking; hostName + optionalString (domain != null) ".''${domain}"
117
+
type = with types; listOf (enum
118
+
[ "builds" "dispatch" "git" "hg" "hub" "lists" "man" "meta" "pages" "paste" "todo" ]);
119
+
defaultText = "locally enabled services";
78
-
Host name used by reverse-proxy and for default settings. Will host services at git."''${originBase}". For example: git.sr.ht
121
+
Services that may be displayed as links in the title bar of the Web interface.
82
-
address = mkOption {
125
+
listenAddress = mkOption {
84
-
default = "127.0.0.1";
127
+
default = "localhost";
128
+
description = "Address to bind to.";
···
100
-
statePath = mkOption {
102
-
default = "/var/lib/sourcehut";
104
-
Root state path for the sourcehut network. If left as the default value
105
-
this directory will automatically be created before the sourcehut server
106
-
starts, otherwise the sysadmin is responsible for ensuring the
107
-
directory exists with appropriate ownership and permissions.
142
+
enable = mkEnableOption ''local minio integration'';
146
+
enable = mkEnableOption ''local nginx integration'';
147
+
virtualHost = mkOption {
148
+
type = types.attrs;
150
+
description = "Virtual-host configuration merged with all Sourcehut's virtual-hosts.";
155
+
enable = mkEnableOption ''local postfix integration'';
159
+
enable = mkEnableOption ''local postgresql integration'';
163
+
enable = mkEnableOption ''local redis integration in a dedicated redis-server'';
type = lib.types.submodule {
freeformType = settingsFormat.type;
169
+
options."sr.ht" = {
170
+
global-domain = mkOption {
171
+
description = "Global domain name.";
173
+
example = "example.com";
175
+
environment = mkOption {
176
+
description = "Values other than \"production\" adds a banner to each page.";
177
+
type = types.enum [ "development" "production" ];
178
+
default = "development";
180
+
network-key = mkOption {
182
+
An absolute file path (which should be outside the Nix-store)
183
+
to a secret key to encrypt internal messages with. Use <code>srht-keygen network</code> to
184
+
generate this key. It must be consistent between all services and nodes.
187
+
apply = s: "<" + toString s;
189
+
owner-email = mkOption {
190
+
description = "Owner's email.";
192
+
default = "contact@example.com";
194
+
owner-name = mkOption {
195
+
description = "Owner's name.";
197
+
default = "John Doe";
199
+
site-blurb = mkOption {
200
+
description = "Blurb for your site.";
202
+
default = "the hacker's forge";
204
+
site-info = mkOption {
205
+
description = "The top-level info page for your site.";
207
+
default = "https://sourcehut.org";
209
+
service-key = mkOption {
211
+
An absolute file path (which should be outside the Nix-store)
212
+
to a key used for encrypting session cookies. Use <code>srht-keygen service</code> to
213
+
generate the service key. This must be shared between each node of the same
214
+
service (e.g. git1.sr.ht and git2.sr.ht), but different services may use
215
+
different keys. If you configure all of your services with the same
216
+
config.ini, you may use the same service-key for all of them.
219
+
apply = s: "<" + toString s;
221
+
site-name = mkOption {
222
+
description = "The name of your network of sr.ht-based sites.";
224
+
default = "sourcehut";
226
+
source-url = mkOption {
227
+
description = "The source code for your fork of sr.ht.";
229
+
default = "https://git.sr.ht/~sircmpwn/srht";
233
+
smtp-host = mkOptionNullOrStr "Outgoing SMTP host.";
234
+
smtp-port = mkOption {
235
+
description = "Outgoing SMTP port.";
236
+
type = with types; nullOr port;
239
+
smtp-user = mkOptionNullOrStr "Outgoing SMTP user.";
240
+
smtp-password = mkOptionNullOrStr "Outgoing SMTP password.";
241
+
smtp-from = mkOptionNullOrStr "Outgoing SMTP FROM.";
242
+
error-to = mkOptionNullOrStr "Address receiving application exceptions";
243
+
error-from = mkOptionNullOrStr "Address sending application exceptions";
244
+
pgp-privkey = mkOptionNullOrStr ''
245
+
An absolute file path (which should be outside the Nix-store)
246
+
to an OpenPGP private key.
248
+
Your PGP key information (DO NOT mix up pub and priv here)
249
+
You must remove the password from your secret key, if present.
250
+
You can do this with <code>gpg --edit-key [key-id]</code>,
251
+
then use the <code>passwd</code> command and do not enter a new password.
253
+
pgp-pubkey = mkOptionNullOrStr "OpenPGP public key.";
254
+
pgp-key-id = mkOptionNullOrStr "OpenPGP key identifier.";
256
+
options.objects = {
257
+
s3-upstream = mkOption {
258
+
description = "Configure the S3-compatible object storage service.";
259
+
type = with types; nullOr str;
262
+
s3-access-key = mkOption {
263
+
description = "Access key to the S3-compatible object storage service";
264
+
type = with types; nullOr str;
267
+
s3-secret-key = mkOption {
269
+
An absolute file path (which should be outside the Nix-store)
270
+
to the secret key of the S3-compatible object storage service.
272
+
type = with types; nullOr path;
274
+
apply = mapNullable (s: "<" + toString s);
277
+
options.webhooks = {
278
+
private-key = mkOption {
280
+
An absolute file path (which should be outside the Nix-store)
281
+
to a base64-encoded Ed25519 key for signing webhook payloads.
282
+
This should be consistent for all *.sr.ht sites,
283
+
as this key will be used to verify signatures
284
+
from other sites in your network.
285
+
Use the <code>srht-keygen webhook</code> command to generate a key.
288
+
apply = s: "<" + toString s;
292
+
options."dispatch.sr.ht" = commonServiceSettings "dispatch" // {
294
+
options."dispatch.sr.ht::github" = {
295
+
oauth-client-id = mkOptionNullOrStr "OAuth client id.";
296
+
oauth-client-secret = mkOptionNullOrStr "OAuth client secret.";
298
+
options."dispatch.sr.ht::gitlab" = {
299
+
enabled = mkEnableOption "GitLab integration";
300
+
canonical-upstream = mkOption {
302
+
description = "Canonical upstream.";
303
+
default = "gitlab.com";
305
+
repo-cache = mkOption {
307
+
description = "Repository cache directory.";
308
+
default = "./repo-cache";
310
+
"gitlab.com" = mkOption {
311
+
type = with types; nullOr str;
312
+
description = "GitLab id and secret.";
314
+
example = "GitLab:application id:secret";
318
+
options."builds.sr.ht" = commonServiceSettings "builds" // {
319
+
allow-free = mkEnableOption "nonpaying users to submit builds";
321
+
description = "The Redis connection used for the Celery worker.";
323
+
default = "redis+socket:///run/redis-sourcehut-buildsrht/redis.sock?virtual_host=2";
327
+
Scripts used to launch on SSH connection.
328
+
<literal>/usr/bin/master-shell</literal> on master,
329
+
<literal>/usr/bin/runner-shell</literal> on runner.
330
+
If master and worker are on the same system
331
+
set to <literal>/usr/bin/runner-shell</literal>.
333
+
type = types.enum ["/usr/bin/master-shell" "/usr/bin/runner-shell"];
334
+
default = "/usr/bin/master-shell";
337
+
options."builds.sr.ht::worker" = {
338
+
bind-address = mkOption {
340
+
HTTP bind address for serving local build information/monitoring.
343
+
default = "localhost:8080";
345
+
buildlogs = mkOption {
346
+
description = "Path to write build logs.";
348
+
default = "/var/log/sourcehut/buildsrht";
352
+
Listening address and listening port
353
+
of the build runner (with HTTP port if not 80).
356
+
default = "localhost:5020";
358
+
timeout = mkOption {
360
+
Max build duration.
361
+
See <link xlink:href="https://golang.org/pkg/time/#ParseDuration"/>.
368
+
options."git.sr.ht" = commonServiceSettings "git" // {
369
+
outgoing-domain = mkOption {
370
+
description = "Outgoing domain.";
372
+
default = "https://git.localhost.localdomain";
374
+
post-update-script = mkOption {
376
+
A post-update script which is installed in every git repo.
377
+
This setting is propagated to newer and existing repositories.
380
+
default = "${pkgs.sourcehut.gitsrht}/bin/gitsrht-update-hook";
381
+
defaultText = "\${pkgs.sourcehut.gitsrht}/bin/gitsrht-update-hook";
382
+
# Git hooks are run relative to their repository's directory,
383
+
# but gitsrht-update-hook looks up ../config.ini
384
+
apply = p: pkgs.writeShellScript "update-hook-wrapper" ''
386
+
test -e "''${PWD%/*}"/config.ini ||
387
+
ln -s /run/sourcehut/gitsrht/config.ini "''${PWD%/*}"/config.ini
388
+
exec -a "$0" '${p}' "$@"
393
+
Path to git repositories on disk.
394
+
If changing the default, you must ensure that
395
+
the gitsrht's user as read and write access to it.
398
+
default = "/var/lib/sourcehut/gitsrht/repos";
400
+
webhooks = mkOption {
401
+
description = "The Redis connection used for the webhooks worker.";
403
+
default = "redis+socket:///run/redis-sourcehut-gitsrht/redis.sock?virtual_host=1";
406
+
options."git.sr.ht::api" = {
407
+
internal-ipnet = mkOption {
409
+
Set of IP subnets which are permitted to utilize internal API
410
+
authentication. This should be limited to the subnets
411
+
from which your *.sr.ht services are running.
412
+
See <xref linkend="opt-services.sourcehut.listenAddress"/>.
414
+
type = with types; listOf str;
415
+
default = [ "127.0.0.0/8" "::1/128" ];
419
+
options."hg.sr.ht" = commonServiceSettings "hg" // {
420
+
changegroup-script = mkOption {
422
+
A changegroup script which is installed in every mercurial repo.
423
+
This setting is propagated to newer and existing repositories.
426
+
default = "${cfg.python}/bin/hgsrht-hook-changegroup";
427
+
defaultText = "\${cfg.python}/bin/hgsrht-hook-changegroup";
428
+
# Mercurial's changegroup hooks are run relative to their repository's directory,
429
+
# but hgsrht-hook-changegroup looks up ./config.ini
430
+
apply = p: pkgs.writeShellScript "hook-changegroup-wrapper" ''
432
+
test -e "''$PWD"/config.ini ||
433
+
ln -s /run/sourcehut/hgsrht/config.ini "''$PWD"/config.ini
434
+
exec -a "$0" '${p}' "$@"
439
+
Path to mercurial repositories on disk.
440
+
If changing the default, you must ensure that
441
+
the hgsrht's user as read and write access to it.
444
+
default = "/var/lib/sourcehut/hgsrht/repos";
446
+
srhtext = mkOptionNullOrStr ''
447
+
Path to the srht mercurial extension
448
+
(defaults to where the hgsrht code is)
450
+
clone_bundle_threshold = mkOption {
451
+
description = ".hg/store size (in MB) past which the nightly job generates clone bundles.";
452
+
type = types.ints.unsigned;
455
+
hg_ssh = mkOption {
456
+
description = "Path to hg-ssh (if not in $PATH).";
458
+
default = "${pkgs.mercurial}/bin/hg-ssh";
459
+
defaultText = "\${pkgs.mercurial}/bin/hg-ssh";
461
+
webhooks = mkOption {
462
+
description = "The Redis connection used for the webhooks worker.";
464
+
default = "redis+socket:///run/redis-sourcehut-hgsrht/redis.sock?virtual_host=1";
468
+
options."hub.sr.ht" = commonServiceSettings "hub" // {
471
+
options."lists.sr.ht" = commonServiceSettings "lists" // {
472
+
allow-new-lists = mkEnableOption "Allow creation of new lists.";
473
+
notify-from = mkOption {
474
+
description = "Outgoing email for notifications generated by users.";
476
+
default = "lists-notify@localhost.localdomain";
478
+
posting-domain = mkOption {
479
+
description = "Posting domain.";
481
+
default = "lists.localhost.localdomain";
484
+
description = "The Redis connection used for the Celery worker.";
486
+
default = "redis+socket:///run/redis-sourcehut-listssrht/redis.sock?virtual_host=2";
488
+
webhooks = mkOption {
489
+
description = "The Redis connection used for the webhooks worker.";
491
+
default = "redis+socket:///run/redis-sourcehut-listssrht/redis.sock?virtual_host=1";
494
+
options."lists.sr.ht::worker" = {
495
+
reject-mimetypes = mkOption {
497
+
Comma-delimited list of Content-Types to reject. Messages with Content-Types
498
+
included in this list are rejected. Multipart messages are always supported,
499
+
and each part is checked against this list.
501
+
Uses fnmatch for wildcard expansion.
503
+
type = with types; listOf str;
504
+
default = ["text/html"];
506
+
reject-url = mkOption {
507
+
description = "Reject URL.";
509
+
default = "https://man.sr.ht/lists.sr.ht/etiquette.md";
513
+
Path for the lmtp daemon's unix socket. Direct incoming mail to this socket.
514
+
Alternatively, specify IP:PORT and an SMTP server will be run instead.
517
+
default = "/tmp/lists.sr.ht-lmtp.sock";
519
+
sock-group = mkOption {
521
+
The lmtp daemon will make the unix socket group-read/write
522
+
for users in this group.
525
+
default = "postfix";
529
+
options."man.sr.ht" = commonServiceSettings "man" // {
532
+
options."meta.sr.ht" =
533
+
removeAttrs (commonServiceSettings "meta")
534
+
["oauth-client-id" "oauth-client-secret"] // {
535
+
api-origin = mkOption {
536
+
description = "Origin URL for API, 100 more than web.";
538
+
default = "http://${cfg.listenAddress}:${toString (cfg.meta.port + 100)}";
539
+
defaultText = ''http://<xref linkend="opt-services.sourcehut.listenAddress"/>:''${toString (<xref linkend="opt-services.sourcehut.meta.port"/> + 100)}'';
541
+
webhooks = mkOption {
542
+
description = "The Redis connection used for the webhooks worker.";
544
+
default = "redis+socket:///run/redis-sourcehut-metasrht/redis.sock?virtual_host=1";
546
+
welcome-emails = mkEnableOption "sending stock sourcehut welcome emails after signup";
548
+
options."meta.sr.ht::api" = {
549
+
internal-ipnet = mkOption {
551
+
Set of IP subnets which are permitted to utilize internal API
552
+
authentication. This should be limited to the subnets
553
+
from which your *.sr.ht services are running.
554
+
See <xref linkend="opt-services.sourcehut.listenAddress"/>.
556
+
type = with types; listOf str;
557
+
default = [ "127.0.0.0/8" "::1/128" ];
560
+
options."meta.sr.ht::aliases" = mkOption {
561
+
description = "Aliases for the client IDs of commonly used OAuth clients.";
562
+
type = with types; attrsOf int;
564
+
example = { "git.sr.ht" = 12345; };
566
+
options."meta.sr.ht::billing" = {
567
+
enabled = mkEnableOption "the billing system";
568
+
stripe-public-key = mkOptionNullOrStr "Public key for Stripe. Get your keys at https://dashboard.stripe.com/account/apikeys";
569
+
stripe-secret-key = mkOptionNullOrStr ''
570
+
An absolute file path (which should be outside the Nix-store)
571
+
to a secret key for Stripe. Get your keys at https://dashboard.stripe.com/account/apikeys
573
+
apply = mapNullable (s: "<" + toString s);
576
+
options."meta.sr.ht::settings" = {
577
+
registration = mkEnableOption "public registration";
578
+
onboarding-redirect = mkOption {
579
+
description = "Where to redirect new users upon registration.";
581
+
default = "https://meta.localhost.localdomain";
583
+
user-invites = mkOption {
585
+
How many invites each user is issued upon registration
586
+
(only applicable if open registration is disabled).
588
+
type = types.ints.unsigned;
593
+
options."pages.sr.ht" = commonServiceSettings "pages" // {
594
+
gemini-certs = mkOption {
596
+
An absolute file path (which should be outside the Nix-store)
597
+
to Gemini certificates.
599
+
type = with types; nullOr path;
602
+
max-site-size = mkOption {
603
+
description = "Maximum size of any given site (post-gunzip), in MiB.";
607
+
user-domain = mkOption {
609
+
Configures the user domain, if enabled.
610
+
All users are given <username>.this.domain.
612
+
type = with types; nullOr str;
616
+
options."pages.sr.ht::api" = {
617
+
internal-ipnet = mkOption {
619
+
Set of IP subnets which are permitted to utilize internal API
620
+
authentication. This should be limited to the subnets
621
+
from which your *.sr.ht services are running.
622
+
See <xref linkend="opt-services.sourcehut.listenAddress"/>.
624
+
type = with types; listOf str;
625
+
default = [ "127.0.0.0/8" "::1/128" ];
629
+
options."paste.sr.ht" = commonServiceSettings "paste" // {
632
+
options."todo.sr.ht" = commonServiceSettings "todo" // {
633
+
notify-from = mkOption {
634
+
description = "Outgoing email for notifications generated by users.";
636
+
default = "todo-notify@localhost.localdomain";
638
+
webhooks = mkOption {
639
+
description = "The Redis connection used for the webhooks worker.";
641
+
default = "redis+socket:///run/redis-sourcehut-todosrht/redis.sock?virtual_host=1";
644
+
options."todo.sr.ht::mail" = {
645
+
posting-domain = mkOption {
646
+
description = "Posting domain.";
648
+
default = "todo.localhost.localdomain";
652
+
Path for the lmtp daemon's unix socket. Direct incoming mail to this socket.
653
+
Alternatively, specify IP:PORT and an SMTP server will be run instead.
656
+
default = "/tmp/todo.sr.ht-lmtp.sock";
658
+
sock-group = mkOption {
660
+
The lmtp daemon will make the unix socket group-read/write
661
+
for users in this group.
664
+
default = "postfix";
The configuration for the sourcehut network.
675
+
enableWorker = mkEnableOption "worker for builds.sr.ht";
677
+
images = mkOption {
678
+
type = with types; attrsOf (attrsOf (attrsOf package));
680
+
example = lib.literalExpression ''(let
681
+
# Pinning unstable to allow usage with flakes and limit rebuilds.
682
+
pkgs_unstable = builtins.fetchGit {
683
+
url = "https://github.com/NixOS/nixpkgs";
684
+
rev = "ff96a0fa5635770390b184ae74debea75c3fd534";
685
+
ref = "nixos-unstable";
687
+
image_from_nixpkgs = (import ("${pkgs.sourcehut.buildsrht}/lib/images/nixos/image.nix") {
688
+
pkgs = (import pkgs_unstable {});
692
+
nixos.unstable.x86_64 = image_from_nixpkgs;
696
+
Images for builds.sr.ht. Each package should be distro.release.arch and point to a /nix/store/package/root.img.qcow2.
702
+
package = mkOption {
703
+
type = types.package;
704
+
default = pkgs.git;
705
+
example = literalExpression "pkgs.gitFull";
707
+
Git package for git.sr.ht. This can help silence collisions.
710
+
fcgiwrap.preforkProcess = mkOption {
711
+
description = "Number of fcgiwrap processes to prefork.";
718
+
package = mkOption {
719
+
type = types.package;
720
+
default = pkgs.mercurial;
722
+
Mercurial package for hg.sr.ht. This can help silence collisions.
725
+
cloneBundles = mkOption {
729
+
Generate clonebundles (which require more disk space but dramatically speed up cloning large repositories).
736
+
extraArgs = mkOption {
737
+
type = with types; listOf str;
738
+
default = [ "--loglevel DEBUG" "--pool eventlet" "--without-heartbeat" ];
739
+
description = "Extra arguments passed to the Celery responsible for processing mails.";
741
+
celeryConfig = mkOption {
742
+
type = types.lines;
744
+
description = "Content of the <literal>celeryconfig.py</literal> used by the Celery of <literal>listssrht-process</literal>.";
122
-
config = mkIf cfg.enable {
750
+
config = mkIf cfg.enable (mkMerge [
752
+
environment.systemPackages = [ pkgs.sourcehut.coresrht ];
754
+
services.sourcehut.settings = {
755
+
"git.sr.ht".outgoing-domain = mkDefault "https://git.${domain}";
756
+
"lists.sr.ht".notify-from = mkDefault "lists-notify@${domain}";
757
+
"lists.sr.ht".posting-domain = mkDefault "lists.${domain}";
758
+
"meta.sr.ht::settings".onboarding-redirect = mkDefault "https://meta.${domain}";
759
+
"todo.sr.ht".notify-from = mkDefault "todo-notify@${domain}";
760
+
"todo.sr.ht::mail".posting-domain = mkDefault "todo.${domain}";
763
+
(mkIf cfg.postgresql.enable {
765
+
{ assertion = postgresql.enable;
766
+
message = "postgresql must be enabled and configured";
770
+
(mkIf cfg.postfix.enable {
772
+
{ assertion = postfix.enable;
773
+
message = "postfix must be enabled and configured";
776
+
# Needed for sharing the LMTP sockets with JoinsNamespaceOf=
777
+
systemd.services.postfix.serviceConfig.PrivateTmp = true;
779
+
(mkIf cfg.redis.enable {
780
+
services.redis.vmOverCommit = mkDefault true;
782
+
(mkIf cfg.nginx.enable {
784
+
{ assertion = nginx.enable;
785
+
message = "nginx must be enabled and configured";
788
+
# For proxyPass= in virtual-hosts for Sourcehut services.
789
+
services.nginx.recommendedProxySettings = mkDefault true;
791
+
(mkIf (cfg.builds.enable || cfg.git.enable || cfg.hg.enable) {
792
+
services.openssh = {
793
+
# Note that sshd will continue to honor AuthorizedKeysFile.
794
+
# Note that you may want automatically rotate
795
+
# or link to /dev/null the following log files:
796
+
# - /var/log/gitsrht-dispatch
797
+
# - /var/log/{build,git,hg}srht-keys
798
+
# - /var/log/{git,hg}srht-shell
799
+
# - /var/log/gitsrht-update-hook
800
+
authorizedKeysCommand = ''/etc/ssh/sourcehut/subdir/srht-dispatch "%u" "%h" "%t" "%k"'';
801
+
# srht-dispatch will setuid/setgid according to [git.sr.ht::dispatch]
802
+
authorizedKeysCommandUser = "root";
804
+
PermitUserEnvironment SRHT_*
807
+
environment.etc."ssh/sourcehut/config.ini".source =
808
+
settingsFormat.generate "sourcehut-dispatch-config.ini"
809
+
(filterAttrs (k: v: k == "git.sr.ht::dispatch")
811
+
environment.etc."ssh/sourcehut/subdir/srht-dispatch" = {
812
+
# sshd_config(5): The program must be owned by root, not writable by group or others
814
+
source = pkgs.writeShellScript "srht-dispatch" ''
816
+
cd /etc/ssh/sourcehut/subdir
817
+
${cfg.python}/bin/gitsrht-dispatch "$@"
820
+
systemd.services.sshd = {
821
+
#path = optional cfg.git.enable [ cfg.git.package ];
823
+
BindReadOnlyPaths =
824
+
# Note that those /usr/bin/* paths are hardcoded in multiple places in *.sr.ht,
825
+
# for instance to get the user from the [git.sr.ht::dispatch] settings.
826
+
# *srht-keys needs to:
827
+
# - access a redis-server in [sr.ht] redis-host,
828
+
# - access the PostgreSQL server in [*.sr.ht] connection-string,
829
+
# - query metasrht-api (through the HTTP API).
830
+
# Using this has the side effect of creating empty files in /usr/bin/
831
+
optionals cfg.builds.enable [
832
+
"${pkgs.writeShellScript "buildsrht-keys-wrapper" ''
834
+
cd /run/sourcehut/buildsrht/subdir
835
+
exec -a "$0" ${pkgs.sourcehut.buildsrht}/bin/buildsrht-keys "$@"
836
+
''}:/usr/bin/buildsrht-keys"
837
+
"${pkgs.sourcehut.buildsrht}/bin/master-shell:/usr/bin/master-shell"
838
+
"${pkgs.sourcehut.buildsrht}/bin/runner-shell:/usr/bin/runner-shell"
840
+
optionals cfg.git.enable [
841
+
# /path/to/gitsrht-keys calls /path/to/gitsrht-shell,
842
+
# or [git.sr.ht] shell= if set.
843
+
"${pkgs.writeShellScript "gitsrht-keys-wrapper" ''
845
+
cd /run/sourcehut/gitsrht/subdir
846
+
exec -a "$0" ${pkgs.sourcehut.gitsrht}/bin/gitsrht-keys "$@"
847
+
''}:/usr/bin/gitsrht-keys"
848
+
"${pkgs.writeShellScript "gitsrht-shell-wrapper" ''
850
+
cd /run/sourcehut/gitsrht/subdir
851
+
exec -a "$0" ${pkgs.sourcehut.gitsrht}/bin/gitsrht-shell "$@"
852
+
''}:/usr/bin/gitsrht-shell"
854
+
optionals cfg.hg.enable [
855
+
# /path/to/hgsrht-keys calls /path/to/hgsrht-shell,
856
+
# or [hg.sr.ht] shell= if set.
857
+
"${pkgs.writeShellScript "hgsrht-keys-wrapper" ''
859
+
cd /run/sourcehut/hgsrht/subdir
860
+
exec -a "$0" ${pkgs.sourcehut.hgsrht}/bin/hgsrht-keys "$@"
861
+
''}:/usr/bin/hgsrht-keys"
862
+
":/usr/bin/hgsrht-shell"
863
+
"${pkgs.writeShellScript "hgsrht-shell-wrapper" ''
865
+
cd /run/sourcehut/hgsrht/subdir
866
+
exec -a "$0" ${pkgs.sourcehut.hgsrht}/bin/hgsrht-shell "$@"
867
+
''}:/usr/bin/hgsrht-shell"
876
+
(import ./service.nix "builds" {
877
+
inherit configIniOfService;
878
+
srvsrht = "buildsrht";
880
+
# TODO: a celery worker on the master and worker are apparently needed
881
+
extraServices.buildsrht-worker = let
882
+
qemuPackage = pkgs.qemu_kvm;
883
+
serviceName = "buildsrht-worker";
884
+
statePath = "/var/lib/sourcehut/${serviceName}";
885
+
in mkIf cfg.builds.enableWorker {
886
+
path = [ pkgs.openssh pkgs.docker ];
889
+
if test -z "$(docker images -q qemu:latest 2>/dev/null)" \
890
+
|| test "$(cat ${statePath}/docker-image-qemu)" != "${qemuPackage.version}"
892
+
# Create and import qemu:latest image for docker
893
+
${pkgs.dockerTools.streamLayeredImage {
896
+
contents = [ qemuPackage ];
898
+
# Mark down current package version
899
+
echo '${qemuPackage.version}' >${statePath}/docker-image-qemu
903
+
ExecStart = "${pkgs.sourcehut.buildsrht}/bin/builds.sr.ht-worker";
904
+
RuntimeDirectory = [ "sourcehut/${serviceName}/subdir" ];
905
+
# builds.sr.ht-worker looks up ../config.ini
906
+
LogsDirectory = [ "sourcehut/${serviceName}" ];
907
+
StateDirectory = [ "sourcehut/${serviceName}" ];
908
+
WorkingDirectory = "-"+"/run/sourcehut/${serviceName}/subdir";
912
+
image_dirs = flatten (
913
+
mapAttrsToList (distro: revs:
914
+
mapAttrsToList (rev: archs:
915
+
mapAttrsToList (arch: image:
916
+
pkgs.runCommand "buildsrht-images" { } ''
917
+
mkdir -p $out/${distro}/${rev}/${arch}
918
+
ln -s ${image}/*.qcow2 $out/${distro}/${rev}/${arch}/root.img.qcow2
922
+
) cfg.builds.images
924
+
image_dir_pre = pkgs.symlinkJoin {
925
+
name = "builds.sr.ht-worker-images-pre";
926
+
paths = image_dirs;
927
+
# FIXME: not working, apparently because ubuntu/latest is a broken link
928
+
# ++ [ "${pkgs.sourcehut.buildsrht}/lib/images" ];
930
+
image_dir = pkgs.runCommand "builds.sr.ht-worker-images" { } ''
931
+
mkdir -p $out/images
932
+
cp -Lr ${image_dir_pre}/* $out/images
936
+
users.users.${cfg.builds.user}.shell = pkgs.bash;
938
+
virtualisation.docker.enable = true;
940
+
services.sourcehut.settings = mkMerge [
941
+
{ # Note that git.sr.ht::dispatch is not a typo,
942
+
# gitsrht-dispatch always use this section
943
+
"git.sr.ht::dispatch"."/usr/bin/buildsrht-keys" =
944
+
mkDefault "${cfg.builds.user}:${cfg.builds.group}";
946
+
(mkIf cfg.builds.enableWorker {
947
+
"builds.sr.ht::worker".shell = "/usr/bin/runner-shell";
948
+
"builds.sr.ht::worker".images = mkDefault "${image_dir}/images";
949
+
"builds.sr.ht::worker".controlcmd = mkDefault "${image_dir}/images/control";
953
+
(mkIf cfg.builds.enableWorker {
955
+
docker.members = [ cfg.builds.user ];
958
+
(mkIf (cfg.builds.enableWorker && cfg.nginx.enable) {
959
+
# Allow nginx access to buildlogs
960
+
users.users.${nginx.user}.extraGroups = [ cfg.builds.group ];
961
+
systemd.services.nginx = {
962
+
serviceConfig.BindReadOnlyPaths = [ "${cfg.settings."builds.sr.ht::worker".buildlogs}:/var/log/nginx/buildsrht/logs" ];
964
+
services.nginx.virtualHosts."logs.${domain}" = mkMerge [ {
965
+
/* FIXME: is a listen needed?
966
+
listen = with builtins;
967
+
# FIXME: not compatible with IPv6
968
+
let address = split ":" cfg.settings."builds.sr.ht::worker".name; in
969
+
[{ addr = elemAt address 0; port = lib.toInt (elemAt address 2); }];
971
+
locations."/logs/".alias = "/var/log/nginx/buildsrht/logs/";
972
+
} cfg.nginx.virtualHost ];
977
+
(import ./service.nix "dispatch" {
978
+
inherit configIniOfService;
982
+
(import ./service.nix "git" (let
984
+
path = [ cfg.git.package ];
985
+
serviceConfig.BindPaths = [ "${cfg.settings."git.sr.ht".repos}:/var/lib/sourcehut/gitsrht/repos" ];
986
+
serviceConfig.BindReadOnlyPaths = [ "${cfg.settings."git.sr.ht".post-update-script}:/var/lib/sourcehut/gitsrht/bin/post-update-script" ];
989
+
inherit configIniOfService;
990
+
mainService = mkMerge [ baseService {
991
+
serviceConfig.StateDirectory = [ "sourcehut/gitsrht" "sourcehut/gitsrht/repos" ];
995
+
extraTimers.gitsrht-periodic = {
996
+
service = baseService;
997
+
timerConfig.OnCalendar = ["20min"];
999
+
extraConfig = mkMerge [
1001
+
# https://stackoverflow.com/questions/22314298/git-push-results-in-fatal-protocol-error-bad-line-length-character-this
1002
+
# Probably could use gitsrht-shell if output is restricted to just parameters...
1003
+
users.users.${cfg.git.user}.shell = pkgs.bash;
1004
+
services.sourcehut.settings = {
1005
+
"git.sr.ht::dispatch"."/usr/bin/gitsrht-keys" =
1006
+
mkDefault "${cfg.git.user}:${cfg.git.group}";
1008
+
systemd.services.sshd = baseService;
1010
+
(mkIf cfg.nginx.enable {
1011
+
services.nginx.virtualHosts."git.${domain}" = {
1012
+
locations."/authorize" = {
1013
+
proxyPass = "http://${cfg.listenAddress}:${toString cfg.git.port}";
1015
+
proxy_pass_request_body off;
1016
+
proxy_set_header Content-Length "";
1017
+
proxy_set_header X-Original-URI $request_uri;
1020
+
locations."~ ^/([^/]+)/([^/]+)/(HEAD|info/refs|objects/info/.*|git-upload-pack).*$" = {
1021
+
root = "/var/lib/sourcehut/gitsrht/repos";
1023
+
GIT_HTTP_EXPORT_ALL = "";
1024
+
GIT_PROJECT_ROOT = "$document_root";
1025
+
PATH_INFO = "$uri";
1026
+
SCRIPT_FILENAME = "${cfg.git.package}/bin/git-http-backend";
1029
+
auth_request /authorize;
1030
+
fastcgi_read_timeout 500s;
1031
+
fastcgi_pass unix:/run/gitsrht-fcgiwrap.sock;
1036
+
systemd.sockets.gitsrht-fcgiwrap = {
1037
+
before = [ "nginx.service" ];
1038
+
wantedBy = [ "sockets.target" "gitsrht.service" ];
1039
+
# This path remains accessible to nginx.service, which has no RootDirectory=
1040
+
socketConfig.ListenStream = "/run/gitsrht-fcgiwrap.sock";
1041
+
socketConfig.SocketUser = nginx.user;
1042
+
socketConfig.SocketMode = "600";
1046
+
extraServices.gitsrht-fcgiwrap = mkIf cfg.nginx.enable {
1048
+
# Socket is passed by gitsrht-fcgiwrap.socket
1049
+
ExecStart = "${pkgs.fcgiwrap}/sbin/fcgiwrap -c ${toString cfg.git.fcgiwrap.preforkProcess}";
1050
+
# No need for config.ini
1051
+
ExecStartPre = mkForce [];
1053
+
DynamicUser = true;
1054
+
BindReadOnlyPaths = [ "${cfg.settings."git.sr.ht".repos}:/var/lib/sourcehut/gitsrht/repos" ];
1055
+
IPAddressDeny = "any";
1056
+
InaccessiblePaths = [ "-+/run/postgresql" "-+/run/redis-sourcehut" ];
1057
+
PrivateNetwork = true;
1058
+
RestrictAddressFamilies = mkForce [ "none" ];
1059
+
SystemCallFilter = mkForce [
1061
+
"~@aio" "~@keyring" "~@memlock" "~@privileged" "~@resources" "~@setuid"
1062
+
# @timer is needed for alarm()
1068
+
(import ./service.nix "hg" (let
1070
+
path = [ cfg.hg.package ];
1071
+
serviceConfig.BindPaths = [ "${cfg.settings."hg.sr.ht".repos}:/var/lib/sourcehut/hgsrht/repos" ];
1072
+
serviceConfig.BindReadOnlyPaths = [ "${cfg.settings."ht.sr.ht".changegroup-script}:/var/lib/sourcehut/hgsrht/bin/changegroup-script" ];
1075
+
inherit configIniOfService;
1076
+
mainService = mkMerge [ baseService {
1077
+
serviceConfig.StateDirectory = [ "sourcehut/hgsrht" "sourcehut/hgsrht/repos" ];
1081
+
extraTimers.hgsrht-periodic = {
1082
+
service = baseService;
1083
+
timerConfig.OnCalendar = ["20min"];
1085
+
extraTimers.hgsrht-clonebundles = mkIf cfg.hg.cloneBundles {
1086
+
service = baseService;
1087
+
timerConfig.OnCalendar = ["daily"];
1088
+
timerConfig.AccuracySec = "1h";
1090
+
extraConfig = mkMerge [
126
-
assertion = with cfgIni.webhooks; private-key != null && stringLength private-key == 44;
127
-
message = "The webhook's private key must be defined and of a 44 byte length.";
1092
+
users.users.${cfg.hg.user}.shell = pkgs.bash;
1093
+
services.sourcehut.settings = {
1094
+
# Note that git.sr.ht::dispatch is not a typo,
1095
+
# gitsrht-dispatch always uses this section.
1096
+
"git.sr.ht::dispatch"."/usr/bin/hgsrht-keys" =
1097
+
mkDefault "${cfg.hg.user}:${cfg.hg.group}";
1099
+
systemd.services.sshd = baseService;
1101
+
(mkIf cfg.nginx.enable {
1102
+
# Allow nginx access to repositories
1103
+
users.users.${nginx.user}.extraGroups = [ cfg.hg.group ];
1104
+
services.nginx.virtualHosts."hg.${domain}" = {
1105
+
locations."/authorize" = {
1106
+
proxyPass = "http://${cfg.listenAddress}:${toString cfg.hg.port}";
1108
+
proxy_pass_request_body off;
1109
+
proxy_set_header Content-Length "";
1110
+
proxy_set_header X-Original-URI $request_uri;
1113
+
# Let clients reach pull bundles. We don't really need to lock this down even for
1114
+
# private repos because the bundles are named after the revision hashes...
1115
+
# so someone would need to know or guess a SHA value to download anything.
1116
+
# TODO: proxyPass to an hg serve service?
1117
+
locations."~ ^/[~^][a-z0-9_]+/[a-zA-Z0-9_.-]+/\\.hg/bundles/.*$" = {
1118
+
root = "/var/lib/nginx/hgsrht/repos";
1120
+
auth_request /authorize;
1125
+
systemd.services.nginx = {
1126
+
serviceConfig.BindReadOnlyPaths = [ "${cfg.settings."hg.sr.ht".repos}:/var/lib/nginx/hgsrht/repos" ];
1132
+
(import ./service.nix "hub" {
1133
+
inherit configIniOfService;
1136
+
services.nginx = mkIf cfg.nginx.enable {
1137
+
virtualHosts."hub.${domain}" = mkMerge [ {
1138
+
serverAliases = [ domain ];
1139
+
} cfg.nginx.virtualHost ];
1144
+
(import ./service.nix "lists" (let
1145
+
srvsrht = "listssrht";
1147
+
inherit configIniOfService;
1150
+
# Receive the mail from Postfix and enqueue them into Redis and PostgreSQL
1151
+
extraServices.listssrht-lmtp = {
1152
+
wants = [ "postfix.service" ];
1153
+
unitConfig.JoinsNamespaceOf = optional cfg.postfix.enable "postfix.service";
1154
+
serviceConfig.ExecStart = "${cfg.python}/bin/listssrht-lmtp";
1155
+
# Avoid crashing: os.chown(sock, os.getuid(), sock_gid)
1156
+
serviceConfig.PrivateUsers = mkForce false;
1158
+
# Dequeue the mails from Redis and dispatch them
1159
+
extraServices.listssrht-process = {
1162
+
cp ${pkgs.writeText "${srvsrht}-webhooks-celeryconfig.py" cfg.lists.process.celeryConfig} \
1163
+
/run/sourcehut/${srvsrht}-webhooks/celeryconfig.py
1165
+
ExecStart = "${cfg.python}/bin/celery --app listssrht.process worker --hostname listssrht-process@%%h " + concatStringsSep " " cfg.lists.process.extraArgs;
1166
+
# Avoid crashing: os.getloadavg()
1167
+
ProcSubset = mkForce "all";
1170
+
extraConfig = mkIf cfg.postfix.enable {
1171
+
users.groups.${postfix.group}.members = [ cfg.lists.user ];
1172
+
services.sourcehut.settings."lists.sr.ht::mail".sock-group = postfix.group;
1173
+
services.postfix = {
1174
+
destination = [ "lists.${domain}" ];
1175
+
# FIXME: an accurate recipient list should be queried
1176
+
# from the lists.sr.ht PostgreSQL database to avoid backscattering.
1177
+
# But usernames are unfortunately not in that database but in meta.sr.ht.
1178
+
# Note that two syntaxes are allowed:
1179
+
# - ~username/list-name@lists.${domain}
1180
+
# - u.username.list-name@lists.${domain}
1181
+
localRecipients = [ "@lists.${domain}" ];
1183
+
lists.${domain} lmtp:unix:${cfg.settings."lists.sr.ht::worker".sock}
1189
+
(import ./service.nix "man" {
1190
+
inherit configIniOfService;
1194
+
(import ./service.nix "meta" {
1195
+
inherit configIniOfService;
1198
+
extraServices.metasrht-api = {
1199
+
serviceConfig.Restart = "always";
1200
+
serviceConfig.RestartSec = "2s";
1201
+
preStart = "set -x\n" + concatStringsSep "\n\n" (attrValues (mapAttrs (k: s:
1202
+
let srvMatch = builtins.match "^([a-z]*)\\.sr\\.ht$" k;
1203
+
srv = head srvMatch;
1205
+
# Configure client(s) as "preauthorized"
1206
+
optionalString (srvMatch != null && cfg.${srv}.enable && ((s.oauth-client-id or null) != null)) ''
1207
+
# Configure ${srv}'s OAuth client as "preauthorized"
1208
+
${postgresql.package}/bin/psql '${cfg.settings."meta.sr.ht".connection-string}' \
1209
+
-c "UPDATE oauthclient SET preauthorized = true WHERE client_id = '${s.oauth-client-id}'"
1212
+
serviceConfig.ExecStart = "${pkgs.sourcehut.metasrht}/bin/metasrht-api -b ${cfg.listenAddress}:${toString (cfg.meta.port + 100)}";
1214
+
extraTimers.metasrht-daily.timerConfig = {
1215
+
OnCalendar = ["daily"];
1216
+
AccuracySec = "1h";
1218
+
extraConfig = mkMerge [
131
-
assertion = hasAttrByPath [ "meta.sr.ht" "origin" ] cfgIni && cfgIni."meta.sr.ht".origin != null;
132
-
message = "meta.sr.ht's origin must be defined.";
1221
+
{ assertion = let s = cfg.settings."meta.sr.ht::billing"; in
1222
+
s.enabled == "yes" -> (s.stripe-public-key != null && s.stripe-secret-key != null);
1223
+
message = "If meta.sr.ht::billing is enabled, the keys must be defined.";
1226
+
environment.systemPackages = optional cfg.meta.enable
1227
+
(pkgs.writeShellScriptBin "metasrht-manageuser" ''
1229
+
if test "$(${pkgs.coreutils}/bin/id -n -u)" != '${cfg.meta.user}'
1230
+
then exec sudo -u '${cfg.meta.user}' "$0" "$@"
1232
+
# In order to load config.ini
1233
+
if cd /run/sourcehut/metasrht
1234
+
then exec ${cfg.python}/bin/metasrht-manageuser "$@"
1236
+
Please run: sudo systemctl start metasrht
1243
+
(mkIf cfg.nginx.enable {
1244
+
services.nginx.virtualHosts."meta.${domain}" = {
1245
+
locations."/query" = {
1246
+
proxyPass = cfg.settings."meta.sr.ht".api-origin;
1248
+
if ($request_method = 'OPTIONS') {
1249
+
add_header 'Access-Control-Allow-Origin' '*';
1250
+
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
1251
+
add_header 'Access-Control-Allow-Headers' 'User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
1252
+
add_header 'Access-Control-Max-Age' 1728000;
1253
+
add_header 'Content-Type' 'text/plain; charset=utf-8';
1254
+
add_header 'Content-Length' 0;
1258
+
add_header 'Access-Control-Allow-Origin' '*';
1259
+
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
1260
+
add_header 'Access-Control-Allow-Headers' 'User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
1261
+
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
136
-
virtualisation.docker.enable = true;
137
-
environment.etc."sr.ht/config.ini".source =
138
-
settingsFormat.generate "sourcehut-config.ini" (mapAttrsRecursive
140
-
path: v: if v == null then "" else v
1269
+
(import ./service.nix "pages" {
1270
+
inherit configIniOfService;
1273
+
srvsrht = "pagessrht";
1274
+
version = pkgs.sourcehut.${srvsrht}.version;
1275
+
stateDir = "/var/lib/sourcehut/${srvsrht}";
1276
+
iniKey = "pages.sr.ht";
1278
+
preStart = mkBefore ''
1280
+
# Use the /run/sourcehut/${srvsrht}/config.ini
1281
+
# installed by a previous ExecStartPre= in baseService
1282
+
cd /run/sourcehut/${srvsrht}
144
-
environment.systemPackages = [ pkgs.sourcehut.coresrht ];
1284
+
if test ! -e ${stateDir}/db; then
1285
+
${postgresql.package}/bin/psql '${cfg.settings.${iniKey}.connection-string}' -f ${pkgs.sourcehut.pagessrht}/share/sql/schema.sql
1286
+
echo ${version} >${stateDir}/db
146
-
# PostgreSQL server
147
-
services.postgresql.enable = mkOverride 999 true;
149
-
services.postfix.enable = mkOverride 999 true;
151
-
services.cron.enable = mkOverride 999 true;
153
-
services.redis.enable = mkOverride 999 true;
154
-
services.redis.bind = mkOverride 999 "127.0.0.1";
1289
+
${optionalString cfg.settings.${iniKey}.migrate-on-upgrade ''
1290
+
# Just try all the migrations because they're not linked to the version
1291
+
for sql in ${pkgs.sourcehut.pagessrht}/share/sql/migrations/*.sql; do
1292
+
${postgresql.package}/bin/psql '${cfg.settings.${iniKey}.connection-string}' -f "$sql" || true
156
-
services.sourcehut.settings = {
157
-
# The name of your network of sr.ht-based sites
158
-
"sr.ht".site-name = mkDefault "sourcehut";
159
-
# The top-level info page for your site
160
-
"sr.ht".site-info = mkDefault "https://sourcehut.org";
161
-
# {{ site-name }}, {{ site-blurb }}
162
-
"sr.ht".site-blurb = mkDefault "the hacker's forge";
163
-
# If this != production, we add a banner to each page
164
-
"sr.ht".environment = mkDefault "development";
165
-
# Contact information for the site owners
166
-
"sr.ht".owner-name = mkDefault "Drew DeVault";
167
-
"sr.ht".owner-email = mkDefault "sir@cmpwn.com";
168
-
# The source code for your fork of sr.ht
169
-
"sr.ht".source-url = mkDefault "https://git.sr.ht/~sircmpwn/srht";
170
-
# A secret key to encrypt session cookies with
171
-
"sr.ht".secret-key = mkDefault null;
172
-
"sr.ht".global-domain = mkDefault null;
1297
+
touch ${stateDir}/webhook
1300
+
ExecStart = mkForce "${pkgs.sourcehut.pagessrht}/bin/pages.sr.ht -b ${cfg.listenAddress}:${toString cfg.pages.port}";
174
-
# Outgoing SMTP settings
175
-
mail.smtp-host = mkDefault null;
176
-
mail.smtp-port = mkDefault null;
177
-
mail.smtp-user = mkDefault null;
178
-
mail.smtp-password = mkDefault null;
179
-
mail.smtp-from = mkDefault null;
180
-
# Application exceptions are emailed to this address
181
-
mail.error-to = mkDefault null;
182
-
mail.error-from = mkDefault null;
183
-
# Your PGP key information (DO NOT mix up pub and priv here)
184
-
# You must remove the password from your secret key, if present.
185
-
# You can do this with gpg --edit-key [key-id], then use the passwd
186
-
# command and do not enter a new password.
187
-
mail.pgp-privkey = mkDefault null;
188
-
mail.pgp-pubkey = mkDefault null;
189
-
mail.pgp-key-id = mkDefault null;
1305
+
(import ./service.nix "paste" {
1306
+
inherit configIniOfService;
191
-
# base64-encoded Ed25519 key for signing webhook payloads. This should be
192
-
# consistent for all *.sr.ht sites, as we'll use this key to verify signatures
193
-
# from other sites in your network.
195
-
# Use the srht-webhook-keygen command to generate a key.
196
-
webhooks.private-key = mkDefault null;
1310
+
(import ./service.nix "todo" {
1311
+
inherit configIniOfService;
1314
+
extraServices.todosrht-lmtp = {
1315
+
wants = [ "postfix.service" ];
1316
+
unitConfig.JoinsNamespaceOf = optional cfg.postfix.enable "postfix.service";
1317
+
serviceConfig.ExecStart = "${cfg.python}/bin/todosrht-lmtp";
1318
+
# Avoid crashing: os.chown(sock, os.getuid(), sock_gid)
1319
+
serviceConfig.PrivateUsers = mkForce false;
1321
+
extraConfig = mkIf cfg.postfix.enable {
1322
+
users.groups.${postfix.group}.members = [ cfg.todo.user ];
1323
+
services.sourcehut.settings."todo.sr.ht::mail".sock-group = postfix.group;
1324
+
services.postfix = {
1325
+
destination = [ "todo.${domain}" ];
1326
+
# FIXME: an accurate recipient list should be queried
1327
+
# from the todo.sr.ht PostgreSQL database to avoid backscattering.
1328
+
# But usernames are unfortunately not in that database but in meta.sr.ht.
1329
+
# Note that two syntaxes are allowed:
1330
+
# - ~username/tracker-name@todo.${domain}
1331
+
# - u.username.tracker-name@todo.${domain}
1332
+
localRecipients = [ "@todo.${domain}" ];
1334
+
todo.${domain} lmtp:unix:${cfg.settings."todo.sr.ht::mail".sock}
1340
+
(mkRenamedOptionModule [ "services" "sourcehut" "originBase" ]
1341
+
[ "services" "sourcehut" "settings" "sr.ht" "global-domain" ])
1342
+
(mkRenamedOptionModule [ "services" "sourcehut" "address" ]
1343
+
[ "services" "sourcehut" "listenAddress" ])
meta.doc = ./sourcehut.xml;
200
-
meta.maintainers = with maintainers; [ tomberek ];
1348
+
meta.maintainers = with maintainers; [ julm tomberek ];