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