]> Git — Sourcephile - sourcephile-nix.git/blob - nixos/modules/services/misc/sourcehut/default.nix
sourcehut: type-check and describe settings
[sourcephile-nix.git] / nixos / modules / services / misc / sourcehut / default.nix
1 { config, pkgs, lib, ... }:
2
3 with lib;
4 let
5 cfg = config.services.sourcehut;
6 rcfg = config.services.redis;
7 cfgIni = cfg.settings;
8 settingsFormat = pkgs.formats.ini {
9 mkKeyValue = k: v:
10 if v == null then ""
11 else generators.mkKeyValueDefault {
12 mkValueString = v:
13 if v == true then "yes"
14 else if v == false then "no"
15 else generators.mkValueStringDefault {} v;
16 } "=" k v;
17 };
18 commonServiceSettings = service: {
19 origin = mkOption {
20 description = "URL ${service}.sr.ht is being served at (protocol://domain)";
21 type = types.str;
22 default = "https://${service}.${cfg.originBase}";
23 };
24 debug-host = mkOption {
25 description = "Address to bind the debug server to.";
26 type = types.str;
27 default = "0.0.0.0";
28 };
29 debug-port = mkOption {
30 description = "Port to bind the debug server to.";
31 type = types.port;
32 default = cfg.${service}.port;
33 };
34 connection-string = mkOption {
35 description = "SQLAlchemy connection string for the database.";
36 type = types.str;
37 default = "postgresql:///${cfg.${service}.database}?user=${cfg.${service}.user}&host=/var/run/postgresql";
38 };
39 migrate-on-upgrade = mkEnableOption "automatic migrations on package upgrade";
40 oauth-client-id = mkOptionNullOrStr "OAUTH client id for meta.sr.ht.";
41 oauth-client-secret = mkOptionNullOrStr "OAUTH client secret for meta.sr.ht.";
42 };
43
44 # Specialized python containing all the modules
45 python = pkgs.sourcehut.python.withPackages (ps: with ps; [
46 gunicorn
47 eventlet
48 # Sourcehut services
49 srht
50 buildsrht
51 dispatchsrht
52 gitsrht
53 hgsrht
54 hubsrht
55 listssrht
56 mansrht
57 metasrht
58 pastesrht
59 todosrht
60 ]);
61 mkOptionNullOrStr = description: mkOption {
62 inherit description;
63 type = with types; nullOr str;
64 default = null;
65 };
66 in
67 {
68 imports =
69 [
70 ./git.nix
71 ./hg.nix
72 ./hub.nix
73 ./todo.nix
74 ./man.nix
75 ./meta.nix
76 ./paste.nix
77 ./builds.nix
78 ./lists.nix
79 ./dispatch.nix
80 (mkRemovedOptionModule [ "services" "sourcehut" "nginx" "enable" ] ''
81 The sourcehut module supports `nginx` as a local reverse-proxy by default and doesn't
82 support other reverse-proxies officially.
83
84 However it's possible to use an alternative reverse-proxy by
85
86 * disabling nginx
87 * adjusting the relevant settings for server addresses and ports directly
88
89 Further details about this can be found in the `Sourcehut`-section of the NixOS-manual.
90 '')
91 ];
92
93 options.services.sourcehut = {
94 enable = mkEnableOption ''
95 sourcehut - git hosting, continuous integration, mailing list, ticket tracking,
96 task dispatching, wiki and account management services
97 '';
98
99 services = mkOption {
100 type = types.nonEmptyListOf (types.enum [ "builds" "dispatch" "git" "hub" "hg" "lists" "man" "meta" "paste" "todo" ]);
101 default = [ "man" "meta" "paste" ];
102 example = [ "builds" "dispatch" "git" "hub" "hg" "lists" "man" "meta" "paste" "todo" ];
103 description = ''
104 Services to enable on the sourcehut network.
105 '';
106 };
107
108 originBase = mkOption {
109 type = types.str;
110 default = with config.networking; hostName + lib.optionalString (domain != null) ".${domain}";
111 description = ''
112 Host name used by reverse-proxy and for default settings. Will host services at git."''${originBase}". For example: git.sr.ht
113 '';
114 };
115
116 address = mkOption {
117 type = types.str;
118 default = "127.0.0.1";
119 description = ''
120 Address to bind to.
121 '';
122 };
123
124 python = mkOption {
125 internal = true;
126 type = types.package;
127 default = python;
128 description = ''
129 The python package to use. It should contain references to the *srht modules and also
130 gunicorn.
131 '';
132 };
133
134 statePath = mkOption {
135 type = types.path;
136 default = "/var/lib/sourcehut";
137 description = ''
138 Root state path for the sourcehut network. If left as the default value
139 this directory will automatically be created before the sourcehut server
140 starts, otherwise the sysadmin is responsible for ensuring the
141 directory exists with appropriate ownership and permissions.
142 '';
143 };
144
145 settings = mkOption {
146 type = lib.types.submodule {
147 freeformType = settingsFormat.type;
148 options."sr.ht" = {
149 environment = mkOption {
150 description = "Values other than \"production\" adds a banner to each page.";
151 type = types.enum [ "development" "production" ];
152 default = "development";
153 };
154 global-domain = mkOptionNullOrStr "Global domain name.";
155 owner-email = mkOption {
156 description = "Owner's email.";
157 type = types.str;
158 default = "contact@example.com";
159 };
160 owner-name = mkOption {
161 description = "Owner's name.";
162 type = types.str;
163 default = "John Doe";
164 };
165 secret-key = mkOptionNullOrStr "Secret key to encrypt session cookies with.";
166 site-blurb = mkOption {
167 description = "Blurb for your site.";
168 type = types.str;
169 default = "the hacker's forge";
170 };
171 site-info = mkOption {
172 description = "The top-level info page for your site.";
173 type = types.str;
174 default = "https://sourcehut.org";
175 };
176 site-name = mkOption {
177 description = "The name of your network of sr.ht-based sites.";
178 type = types.str;
179 default = "sourcehut";
180 };
181 source-url = mkOption {
182 description = "The source code for your fork of sr.ht.";
183 type = types.str;
184 default = "https://git.sr.ht/~sircmpwn/srht";
185 };
186 };
187 options.mail = {
188 smtp-host = mkOptionNullOrStr "Outgoing SMTP host.";
189 smtp-port = mkOption {
190 description = "Outgoing SMTP port.";
191 type = with types; nullOr port;
192 default = null;
193 };
194 smtp-user = mkOptionNullOrStr "Outgoing SMTP user.";
195 smtp-password = mkOptionNullOrStr "Outgoing SMTP password.";
196 smtp-from = mkOptionNullOrStr "Outgoing SMTP FROM.";
197 error-to = mkOptionNullOrStr "Address receiving application exceptions";
198 error-from = mkOptionNullOrStr "Address sending application exceptions";
199 pgp-privkey = mkOptionNullOrStr ''
200 OpenPGP private key.
201
202 Your PGP key information (DO NOT mix up pub and priv here)
203 You must remove the password from your secret key, if present.
204 You can do this with <code>gpg --edit-key [key-id]</code>, then use the <code>passwd</code> command and do not enter a new password.
205 '';
206 pgp-pubkey = mkOptionNullOrStr "OpenPGP public key.";
207 pgp-key-id = mkOptionNullOrStr "OpenPGP key identifier.";
208 };
209 options.webhooks = {
210 private-key = mkOptionNullOrStr ''
211 base64-encoded Ed25519 key for signing webhook payloads.
212 This should be consistent for all *.sr.ht sites,
213 as this key will be used to verify signatures
214 from other sites in your network.
215 Use the <code>srht-webhook-keygen</code> command to generate a key.
216 '';
217 };
218 options."dispatch.sr.ht" = commonServiceSettings "dispatch";
219 options."dispatch.sr.ht::github" = {
220 oauth-client-id = mkOptionNullOrStr "OAUTH client id.";
221 oauth-client-secret = mkOptionNullOrStr "OAUTH client secret.";
222 };
223 options."dispatch.sr.ht::gitlab" = {
224 enabled = mkEnableOption "GitLab integration";
225 canonical-upstream = mkOption {
226 type = types.str;
227 description = "Canonical upstream.";
228 default = "gitlab.com";
229 };
230 repo-cache = mkOption {
231 type = types.str;
232 description = "Repository cache directory.";
233 default = "./repo-cache";
234 };
235 "gitlab.com" = mkOption {
236 type = types.str;
237 description = "GitLab id and secret.";
238 default = "";
239 example = "GitLab:application id:secret";
240 };
241 };
242 options."builds.sr.ht" = commonServiceSettings "builds" // {
243 redis = mkOption {
244 description = "The redis connection used for the celery worker.";
245 type = types.str;
246 default = "redis://${rcfg.bind}:${toString rcfg.port}/3";
247 };
248 shell = mkOption {
249 description = "The shell used for ssh.";
250 type = types.str;
251 default = "runner-shell";
252 };
253 };
254 options."git.sr.ht" = commonServiceSettings "git" // {
255 outgoing-domain = mkOption {
256 description = "Outgoing domain.";
257 type = types.str;
258 default = "http://git.${cfg.originBase}";
259 };
260 post-update-script = mkOption {
261 description = "A post-update script which is installed in every git repo.";
262 type = types.str;
263 default = "${pkgs.sourcehut.gitsrht}/bin/gitsrht-update-hook";
264 };
265 repos = mkOption {
266 description = "Path to git repositories on disk.";
267 type = types.str;
268 default = "/var/lib/git";
269 };
270 webhooks = mkOption {
271 description = "The redis connection used for the webhooks worker.";
272 type = types.str;
273 default = "redis://${rcfg.bind}:${toString rcfg.port}/1";
274 };
275 };
276 options."hg.sr.ht" = commonServiceSettings "hg" // {
277 changegroup-script = mkOption {
278 description = "A post-update script which is installed in every mercurial repo..";
279 type = types.str;
280 default = "${cfg.python}/bin/hgsrht-hook-changegroup";
281 };
282 repos = mkOption {
283 description = "Path to mercurial repositories on disk.";
284 type = types.str;
285 default = "/var/lib/hg";
286 };
287 srhtext = mkOptionNullOrStr ''
288 Path to the srht mercurial extension
289 (defaults to where the hgsrht code is)
290 '';
291 clone_bundle_threshold = mkOption {
292 description = ".hg/store size (in MB) past which the nightly job generates clone bundles.";
293 type = types.ints.unsigned;
294 default = 50;
295 };
296 hg_ssh = mkOption {
297 description = "Path to hg-ssh (if not in $PATH).";
298 type = types.str;
299 default = "${pkgs.mercurial}/bin/hg-ssh";
300 };
301 webhooks = mkOption {
302 description = "The redis connection used for the webhooks worker.";
303 type = types.str;
304 default = "redis://${rcfg.bind}:${toString rcfg.port}/1";
305 };
306 };
307 options."hub.sr.ht" = commonServiceSettings "hub" // {
308 };
309 options."lists.sr.ht" = commonServiceSettings "lists" // {
310 allow-new-lists = mkEnableOption "Allow creation of new lists.";
311 network-key = mkOptionNullOrStr "Network key.";
312 notify-from = mkOption {
313 description = "Outgoing email for notifications generated by users.";
314 type = types.str;
315 default = "lists-notify@${cfg.originBase}";
316 };
317 posting-domain = mkOption {
318 description = "Posting domain.";
319 type = types.str;
320 default = "lists.${cfg.originBase}";
321 };
322 redis = mkOption {
323 description = "The redis connection used for the celery worker.";
324 type = types.str;
325 default = "redis://${rcfg.bind}:${toString rcfg.port}/4";
326 };
327 webhooks = mkOption {
328 description = "The redis connection used for the webhooks worker.";
329 type = types.str;
330 default = "redis://${rcfg.bind}:${toString rcfg.port}/2";
331 };
332 };
333 options."lists.sr.ht::worker" = {
334 reject-mimetypes = mkOption {
335 type = with types; listOf str;
336 default = ["text/html"];
337 };
338 reject-url = mkOption {
339 description = "Reject URL.";
340 default = "https://man.sr.ht/lists.sr.ht/etiquette.md";
341 type = types.str;
342 };
343 sock = mkOption {
344 description = ''
345 Path for the lmtp daemon's unix socket. Direct incoming mail to this socket.
346 Alternatively, specify IP:PORT and an SMTP server will be run instead.
347 '';
348 type = types.str;
349 default = "/tmp/lists.sr.ht-lmtp.sock";
350 };
351 sock-group = mkOption {
352 description = ''
353 The lmtp daemon will make the unix socket group-read/write
354 for users in this group.
355 '';
356 type = types.str;
357 default = "postfix";
358 };
359 };
360 options."man.sr.ht" = commonServiceSettings "man" // {
361 };
362 options."meta.sr.ht" = commonServiceSettings "meta" // {
363 api-origin = mkOption {
364 description = "Origin URL for API, 100 more than web.";
365 type = types.str;
366 default = "http://localhost:5100";
367 };
368 webhooks = mkOption {
369 description = "The redis connection used for the webhooks worker.";
370 type = types.str;
371 default = "redis://${rcfg.bind}:${toString rcfg.port}/6";
372 };
373 welcome-emails = mkEnableOption "sending stock sourcehut welcome emails after signup";
374 };
375 options."meta.sr.ht::settings" = {
376 registration = mkEnableOption "public registration";
377 onboarding-redirect = mkOption {
378 description = "Where to redirect new users upon registration.";
379 type = types.str;
380 default = "https://meta.${cfg.originBase}";
381 };
382 user-invites = mkOption {
383 description = ''
384 How many invites each user is issued upon registration
385 (only applicable if open registration is disabled).
386 '';
387 type = types.ints.unsigned;
388 default = 5;
389 };
390 };
391 options."meta.sr.ht::aliases" = mkOption {
392 description = "Aliases for the client IDs of commonly used OAuth clients.";
393 type = with types; attrsOf int;
394 default = {};
395 example = { "git.sr.ht" = 12345; };
396 };
397 options."meta.sr.ht::billing" = {
398 enabled = mkEnableOption "the billing system";
399 stripe-public-key = mkOptionNullOrStr "Public key for Stripe. Get your keys at https://dashboard.stripe.com/account/apikeys";
400 stripe-secret-key = mkOptionNullOrStr "Secret key for Stripe. Get your keys at https://dashboard.stripe.com/account/apikeys";
401 };
402 options."paste.sr.ht" = commonServiceSettings "paste" // {
403 webhooks = mkOption {
404 type = types.str;
405 default = "redis://${rcfg.bind}:${toString rcfg.port}/5";
406 };
407 };
408 options."todo.sr.ht" = commonServiceSettings "todo" // {
409 network-key = mkOptionNullOrStr "Network key.";
410 notify-from = mkOption {
411 description = "Outgoing email for notifications generated by users.";
412 type = types.str;
413 default = "todo-notify@${cfg.originBase}";
414 };
415 webhooks = mkOption {
416 description = "The redis connection used for the webhooks worker.";
417 type = types.str;
418 default = "redis://${rcfg.bind}:${toString rcfg.port}/7";
419 };
420 };
421 options."todo.sr.ht::mail" = {
422 posting-domain = mkOption {
423 description = "Posting domain.";
424 type = types.str;
425 default = "todo.${cfg.originBase}";
426 };
427 sock = mkOption {
428 description = ''
429 Path for the lmtp daemon's unix socket. Direct incoming mail to this socket.
430 Alternatively, specify IP:PORT and an SMTP server will be run instead.
431 '';
432 type = types.str;
433 default = "/tmp/todo.sr.ht-lmtp.sock";
434 };
435 sock-group = mkOption {
436 description = ''
437 The lmtp daemon will make the unix socket group-read/write
438 for users in this group.
439 '';
440 type = types.str;
441 default = "postfix";
442 };
443 };
444 };
445 default = { };
446 description = ''
447 The configuration for the sourcehut network.
448 '';
449 };
450 };
451
452 config = mkIf cfg.enable {
453 assertions =
454 [
455 {
456 assertion = with cfgIni.webhooks; private-key != null && stringLength private-key == 44;
457 message = "The webhook's private key must be defined and of a 44 byte length.";
458 }
459
460 {
461 assertion = hasAttrByPath [ "meta.sr.ht" "origin" ] cfgIni && cfgIni."meta.sr.ht".origin != null;
462 message = "meta.sr.ht's origin must be defined.";
463 }
464 ];
465
466 environment.etc."sr.ht/config.ini".source =
467 settingsFormat.generate "sourcehut-config.ini"
468 # Disabled services must be removed from the config
469 # to be effectively disabled.
470 (filterAttrs (k: v:
471 let srv = builtins.match "^([a-z]*)\\.sr\\.ht(::.*)?$" k; in
472 srv == null || elem (head srv) cfg.services
473 ) cfg.settings);
474
475 environment.systemPackages = [ pkgs.sourcehut.coresrht ];
476
477 # PostgreSQL server
478 services.postgresql.enable = mkOverride 999 true;
479 # Mail server
480 services.postfix.enable = mkOverride 999 true;
481 # Cron daemon
482 services.cron.enable = mkOverride 999 true;
483 # Redis server
484 services.redis.enable = mkOverride 999 true;
485 services.redis.bind = mkOverride 999 "127.0.0.1";
486
487 };
488 meta.doc = ./sourcehut.xml;
489 meta.maintainers = with maintainers; [ tomberek ];
490 }