1 { config, pkgs, lib, ... }:
5 cfg = config.services.sourcehut;
6 rcfg = config.services.redis;
8 settingsFormat = pkgs.formats.ini {
9 listToValue = concatMapStringsSep "," (generators.mkValueStringDefault {});
12 else generators.mkKeyValueDefault {
14 if v == true then "yes"
15 else if v == false then "no"
16 else generators.mkValueStringDefault {} v;
19 commonServiceSettings = service: {
21 description = "URL ${service}.sr.ht is being served at (protocol://domain)";
23 default = "https://${service}.${cfg.originBase}";
25 debug-host = mkOption {
26 description = "Address to bind the debug server to.";
30 debug-port = mkOption {
31 description = "Port to bind the debug server to.";
33 default = cfg.${service}.port;
35 connection-string = mkOption {
36 description = "SQLAlchemy connection string for the database.";
38 default = "postgresql:///${cfg.${service}.database}?user=${cfg.${service}.user}&host=/var/run/postgresql";
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.";
45 oauth-client-secret = mkOption {
46 description = "${service}.sr.ht's OAuth client secret for meta.sr.ht.";
51 # Specialized python containing all the modules
52 python = pkgs.sourcehut.python.withPackages (ps: with ps; [
68 mkOptionNullOrStr = description: mkOption {
70 type = with types; nullOr str;
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.
91 However it's possible to use an alternative reverse-proxy by
94 * adjusting the relevant settings for server addresses and ports directly
96 Further details about this can be found in the `Sourcehut`-section of the NixOS-manual.
100 options.services.sourcehut = {
101 enable = mkEnableOption ''
102 sourcehut - git hosting, continuous integration, mailing list, ticket tracking,
103 task dispatching, wiki and account management services
106 services = mkOption {
107 type = types.nonEmptyListOf (types.enum [ "builds" "dispatch" "git" "hub" "hg" "lists" "man" "meta" "paste" "todo" ]);
108 default = [ "man" "meta" "paste" ];
109 example = [ "builds" "dispatch" "git" "hub" "hg" "lists" "man" "meta" "paste" "todo" ];
111 Services to enable on the sourcehut network.
115 originBase = mkOption {
117 default = with config.networking; hostName + lib.optionalString (domain != null) ".${domain}";
119 Host name used by reverse-proxy and for default settings. Will host services at git."''${originBase}". For example: git.sr.ht
125 default = "127.0.0.1";
133 type = types.package;
136 The python package to use. It should contain references to the *srht modules and also
141 settings = mkOption {
142 type = lib.types.submodule {
143 freeformType = settingsFormat.type;
145 environment = mkOption {
146 description = "Values other than \"production\" adds a banner to each page.";
147 type = types.enum [ "development" "production" ];
148 default = "development";
150 global-domain = mkOptionNullOrStr "Global domain name.";
151 owner-email = mkOption {
152 description = "Owner's email.";
154 default = "contact@example.com";
156 owner-name = mkOption {
157 description = "Owner's name.";
159 default = "John Doe";
161 secret-key = mkOptionNullOrStr "Secret key to encrypt session cookies with.";
162 site-blurb = mkOption {
163 description = "Blurb for your site.";
165 default = "the hacker's forge";
167 site-info = mkOption {
168 description = "The top-level info page for your site.";
170 default = "https://sourcehut.org";
172 site-name = mkOption {
173 description = "The name of your network of sr.ht-based sites.";
175 default = "sourcehut";
177 source-url = mkOption {
178 description = "The source code for your fork of sr.ht.";
180 default = "https://git.sr.ht/~sircmpwn/srht";
184 smtp-host = mkOptionNullOrStr "Outgoing SMTP host.";
185 smtp-port = mkOption {
186 description = "Outgoing SMTP port.";
187 type = with types; nullOr port;
190 smtp-user = mkOptionNullOrStr "Outgoing SMTP user.";
191 smtp-password = mkOptionNullOrStr "Outgoing SMTP password.";
192 smtp-from = mkOptionNullOrStr "Outgoing SMTP FROM.";
193 error-to = mkOptionNullOrStr "Address receiving application exceptions";
194 error-from = mkOptionNullOrStr "Address sending application exceptions";
195 pgp-privkey = mkOptionNullOrStr ''
198 Your PGP key information (DO NOT mix up pub and priv here)
199 You must remove the password from your secret key, if present.
200 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.
202 pgp-pubkey = mkOptionNullOrStr "OpenPGP public key.";
203 pgp-key-id = mkOptionNullOrStr "OpenPGP key identifier.";
206 private-key = mkOptionNullOrStr ''
207 base64-encoded Ed25519 key for signing webhook payloads.
208 This should be consistent for all *.sr.ht sites,
209 as this key will be used to verify signatures
210 from other sites in your network.
211 Use the <code>srht-webhook-keygen</code> command to generate a key.
214 options."dispatch.sr.ht" = commonServiceSettings "dispatch" // {
216 options."dispatch.sr.ht::github" = {
217 oauth-client-id = mkOptionNullOrStr "OAuth client id.";
218 oauth-client-secret = mkOptionNullOrStr "OAuth client secret.";
220 options."dispatch.sr.ht::gitlab" = {
221 enabled = mkEnableOption "GitLab integration";
222 canonical-upstream = mkOption {
224 description = "Canonical upstream.";
225 default = "gitlab.com";
227 repo-cache = mkOption {
229 description = "Repository cache directory.";
230 default = "./repo-cache";
232 "gitlab.com" = mkOption {
234 description = "GitLab id and secret.";
236 example = "GitLab:application id:secret";
239 options."builds.sr.ht" = commonServiceSettings "builds" // {
241 description = "The redis connection used for the celery worker.";
243 default = "redis://${rcfg.bind}:${toString rcfg.port}/3";
246 description = "The shell used for ssh.";
248 default = "runner-shell";
251 options."git.sr.ht" = commonServiceSettings "git" // {
252 outgoing-domain = mkOption {
253 description = "Outgoing domain.";
255 default = "http://git.${cfg.originBase}";
257 post-update-script = mkOption {
258 description = "A post-update script which is installed in every git repo.";
260 default = "${pkgs.sourcehut.gitsrht}/bin/gitsrht-update-hook";
263 description = "Path to git repositories on disk.";
265 default = "/var/lib/git";
267 webhooks = mkOption {
268 description = "The redis connection used for the webhooks worker.";
270 default = "redis://${rcfg.bind}:${toString rcfg.port}/1";
273 options."hg.sr.ht" = commonServiceSettings "hg" // {
274 changegroup-script = mkOption {
275 description = "A post-update script which is installed in every mercurial repo..";
277 default = "${cfg.python}/bin/hgsrht-hook-changegroup";
280 description = "Path to mercurial repositories on disk.";
282 default = "/var/lib/hg";
284 srhtext = mkOptionNullOrStr ''
285 Path to the srht mercurial extension
286 (defaults to where the hgsrht code is)
288 clone_bundle_threshold = mkOption {
289 description = ".hg/store size (in MB) past which the nightly job generates clone bundles.";
290 type = types.ints.unsigned;
294 description = "Path to hg-ssh (if not in $PATH).";
296 default = "${pkgs.mercurial}/bin/hg-ssh";
298 webhooks = mkOption {
299 description = "The redis connection used for the webhooks worker.";
301 default = "redis://${rcfg.bind}:${toString rcfg.port}/1";
304 options."hub.sr.ht" = commonServiceSettings "hub" // {
306 options."lists.sr.ht" = commonServiceSettings "lists" // {
307 allow-new-lists = mkEnableOption "Allow creation of new lists.";
308 network-key = mkOptionNullOrStr "Network key.";
309 notify-from = mkOption {
310 description = "Outgoing email for notifications generated by users.";
312 default = "lists-notify@${cfg.originBase}";
314 posting-domain = mkOption {
315 description = "Posting domain.";
317 default = "lists.${cfg.originBase}";
320 description = "The redis connection used for the celery worker.";
322 default = "redis://${rcfg.bind}:${toString rcfg.port}/4";
324 webhooks = mkOption {
325 description = "The redis connection used for the webhooks worker.";
327 default = "redis://${rcfg.bind}:${toString rcfg.port}/2";
330 options."lists.sr.ht::worker" = {
331 reject-mimetypes = mkOption {
332 type = with types; listOf str;
333 default = ["text/html"];
335 reject-url = mkOption {
336 description = "Reject URL.";
337 default = "https://man.sr.ht/lists.sr.ht/etiquette.md";
342 Path for the lmtp daemon's unix socket. Direct incoming mail to this socket.
343 Alternatively, specify IP:PORT and an SMTP server will be run instead.
346 default = "/tmp/lists.sr.ht-lmtp.sock";
348 sock-group = mkOption {
350 The lmtp daemon will make the unix socket group-read/write
351 for users in this group.
357 options."man.sr.ht" = commonServiceSettings "man" // {
359 options."meta.sr.ht" = commonServiceSettings "meta" // {
360 oauth-client-id = mkOptionNullOrStr "OAuth client id.";
361 oauth-client-secret = mkOptionNullOrStr "OAuth client secret.";
362 api-origin = mkOption {
363 description = "Origin URL for API, 100 more than web.";
365 default = "http://localhost:5100";
367 webhooks = mkOption {
368 description = "The redis connection used for the webhooks worker.";
370 default = "redis://${rcfg.bind}:${toString rcfg.port}/6";
372 welcome-emails = mkEnableOption "sending stock sourcehut welcome emails after signup";
374 options."meta.sr.ht::settings" = {
375 registration = mkEnableOption "public registration";
376 onboarding-redirect = mkOption {
377 description = "Where to redirect new users upon registration.";
379 default = "https://meta.${cfg.originBase}";
381 user-invites = mkOption {
383 How many invites each user is issued upon registration
384 (only applicable if open registration is disabled).
386 type = types.ints.unsigned;
390 options."meta.sr.ht::aliases" = mkOption {
391 description = "Aliases for the client IDs of commonly used OAuth clients.";
392 type = with types; attrsOf int;
394 example = { "git.sr.ht" = 12345; };
396 options."meta.sr.ht::billing" = {
397 enabled = mkEnableOption "the billing system";
398 stripe-public-key = mkOptionNullOrStr "Public key for Stripe. Get your keys at https://dashboard.stripe.com/account/apikeys";
399 stripe-secret-key = mkOptionNullOrStr "Secret key for Stripe. Get your keys at https://dashboard.stripe.com/account/apikeys";
401 options."paste.sr.ht" = commonServiceSettings "paste" // {
402 webhooks = mkOption {
404 default = "redis://${rcfg.bind}:${toString rcfg.port}/5";
407 options."todo.sr.ht" = commonServiceSettings "todo" // {
408 network-key = mkOptionNullOrStr "Network key.";
409 notify-from = mkOption {
410 description = "Outgoing email for notifications generated by users.";
412 default = "todo-notify@${cfg.originBase}";
414 webhooks = mkOption {
415 description = "The redis connection used for the webhooks worker.";
417 default = "redis://${rcfg.bind}:${toString rcfg.port}/7";
420 options."todo.sr.ht::mail" = {
421 posting-domain = mkOption {
422 description = "Posting domain.";
424 default = "todo.${cfg.originBase}";
428 Path for the lmtp daemon's unix socket. Direct incoming mail to this socket.
429 Alternatively, specify IP:PORT and an SMTP server will be run instead.
432 default = "/tmp/todo.sr.ht-lmtp.sock";
434 sock-group = mkOption {
436 The lmtp daemon will make the unix socket group-read/write
437 for users in this group.
446 The configuration for the sourcehut network.
451 config = mkIf cfg.enable {
455 assertion = with cfgIni.webhooks; private-key != null && stringLength private-key == 44;
456 message = "The webhook's private key must be defined and of a 44 byte length.";
460 assertion = hasAttrByPath [ "meta.sr.ht" "origin" ] cfgIni && cfgIni."meta.sr.ht".origin != null;
461 message = "meta.sr.ht's origin must be defined.";
465 environment.etc."sr.ht/config.ini".source =
466 settingsFormat.generate "sourcehut-config.ini"
467 # Disabled services must be removed from the config
468 # to be effectively disabled.
470 let srv = builtins.match "^([a-z]*)\\.sr\\.ht(::.*)?$" k; in
471 srv == null || elem (head srv) cfg.services
474 environment.systemPackages = [ pkgs.sourcehut.coresrht ];
477 services.postgresql.enable = mkOverride 999 true;
479 services.postfix.enable = mkOverride 999 true;
481 services.cron.enable = mkOverride 999 true;
483 services.redis.enable = mkOverride 999 true;
484 services.redis.bind = mkOverride 999 "127.0.0.1";
487 meta.doc = ./sourcehut.xml;
488 meta.maintainers = with maintainers; [ tomberek ];