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.
98 (mkRemovedOptionModule [ "services" "sourcehut" "services" ] ''
99 Please use the `config.services.sourcehut.''${service}.enable' options instead.
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
109 originBase = mkOption {
111 default = with config.networking; hostName + lib.optionalString (domain != null) ".${domain}";
113 Host name used by reverse-proxy and for default settings. Will host services at git."''${originBase}". For example: git.sr.ht
119 default = "127.0.0.1";
127 type = types.package;
130 The python package to use. It should contain references to the *srht modules and also
135 settings = mkOption {
136 type = lib.types.submodule {
137 freeformType = settingsFormat.type;
139 environment = mkOption {
140 description = "Values other than \"production\" adds a banner to each page.";
141 type = types.enum [ "development" "production" ];
142 default = "development";
144 global-domain = mkOptionNullOrStr "Global domain name.";
145 owner-email = mkOption {
146 description = "Owner's email.";
148 default = "contact@example.com";
150 owner-name = mkOption {
151 description = "Owner's name.";
153 default = "John Doe";
155 secret-key = mkOptionNullOrStr "Secret key to encrypt session cookies with.";
156 site-blurb = mkOption {
157 description = "Blurb for your site.";
159 default = "the hacker's forge";
161 site-info = mkOption {
162 description = "The top-level info page for your site.";
164 default = "https://sourcehut.org";
166 site-name = mkOption {
167 description = "The name of your network of sr.ht-based sites.";
169 default = "sourcehut";
171 source-url = mkOption {
172 description = "The source code for your fork of sr.ht.";
174 default = "https://git.sr.ht/~sircmpwn/srht";
178 smtp-host = mkOptionNullOrStr "Outgoing SMTP host.";
179 smtp-port = mkOption {
180 description = "Outgoing SMTP port.";
181 type = with types; nullOr port;
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 ''
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.
196 pgp-pubkey = mkOptionNullOrStr "OpenPGP public key.";
197 pgp-key-id = mkOptionNullOrStr "OpenPGP key identifier.";
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.
208 options."dispatch.sr.ht" = commonServiceSettings "dispatch" // {
210 options."dispatch.sr.ht::github" = {
211 oauth-client-id = mkOptionNullOrStr "OAuth client id.";
212 oauth-client-secret = mkOptionNullOrStr "OAuth client secret.";
214 options."dispatch.sr.ht::gitlab" = {
215 enabled = mkEnableOption "GitLab integration";
216 canonical-upstream = mkOption {
218 description = "Canonical upstream.";
219 default = "gitlab.com";
221 repo-cache = mkOption {
223 description = "Repository cache directory.";
224 default = "./repo-cache";
226 "gitlab.com" = mkOption {
228 description = "GitLab id and secret.";
230 example = "GitLab:application id:secret";
233 options."builds.sr.ht" = commonServiceSettings "builds" // {
235 description = "The redis connection used for the celery worker.";
237 default = "redis://${rcfg.bind}:${toString rcfg.port}/3";
240 description = "The shell used for ssh.";
242 default = "runner-shell";
245 options."git.sr.ht" = commonServiceSettings "git" // {
246 outgoing-domain = mkOption {
247 description = "Outgoing domain.";
249 default = "http://git.${cfg.originBase}";
251 post-update-script = mkOption {
252 description = "A post-update script which is installed in every git repo.";
254 default = "${pkgs.sourcehut.gitsrht}/bin/gitsrht-update-hook";
257 description = "Path to git repositories on disk.";
259 default = "/var/lib/git";
261 webhooks = mkOption {
262 description = "The redis connection used for the webhooks worker.";
264 default = "redis://${rcfg.bind}:${toString rcfg.port}/1";
267 options."hg.sr.ht" = commonServiceSettings "hg" // {
268 changegroup-script = mkOption {
269 description = "A post-update script which is installed in every mercurial repo..";
271 default = "${cfg.python}/bin/hgsrht-hook-changegroup";
274 description = "Path to mercurial repositories on disk.";
276 default = "/var/lib/hg";
278 srhtext = mkOptionNullOrStr ''
279 Path to the srht mercurial extension
280 (defaults to where the hgsrht code is)
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;
288 description = "Path to hg-ssh (if not in $PATH).";
290 default = "${pkgs.mercurial}/bin/hg-ssh";
292 webhooks = mkOption {
293 description = "The redis connection used for the webhooks worker.";
295 default = "redis://${rcfg.bind}:${toString rcfg.port}/1";
298 options."hub.sr.ht" = commonServiceSettings "hub" // {
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.";
306 default = "lists-notify@${cfg.originBase}";
308 posting-domain = mkOption {
309 description = "Posting domain.";
311 default = "lists.${cfg.originBase}";
314 description = "The redis connection used for the celery worker.";
316 default = "redis://${rcfg.bind}:${toString rcfg.port}/4";
318 webhooks = mkOption {
319 description = "The redis connection used for the webhooks worker.";
321 default = "redis://${rcfg.bind}:${toString rcfg.port}/2";
324 options."lists.sr.ht::worker" = {
325 reject-mimetypes = mkOption {
326 type = with types; listOf str;
327 default = ["text/html"];
329 reject-url = mkOption {
330 description = "Reject URL.";
331 default = "https://man.sr.ht/lists.sr.ht/etiquette.md";
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.
340 default = "/tmp/lists.sr.ht-lmtp.sock";
342 sock-group = mkOption {
344 The lmtp daemon will make the unix socket group-read/write
345 for users in this group.
351 options."man.sr.ht" = commonServiceSettings "man" // {
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.";
359 default = "http://localhost:5100";
361 webhooks = mkOption {
362 description = "The redis connection used for the webhooks worker.";
364 default = "redis://${rcfg.bind}:${toString rcfg.port}/6";
366 welcome-emails = mkEnableOption "sending stock sourcehut welcome emails after signup";
368 options."meta.sr.ht::settings" = {
369 registration = mkEnableOption "public registration";
370 onboarding-redirect = mkOption {
371 description = "Where to redirect new users upon registration.";
373 default = "https://meta.${cfg.originBase}";
375 user-invites = mkOption {
377 How many invites each user is issued upon registration
378 (only applicable if open registration is disabled).
380 type = types.ints.unsigned;
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;
388 example = { "git.sr.ht" = 12345; };
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";
395 options."paste.sr.ht" = commonServiceSettings "paste" // {
396 webhooks = mkOption {
398 default = "redis://${rcfg.bind}:${toString rcfg.port}/5";
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.";
406 default = "todo-notify@${cfg.originBase}";
408 webhooks = mkOption {
409 description = "The redis connection used for the webhooks worker.";
411 default = "redis://${rcfg.bind}:${toString rcfg.port}/7";
414 options."todo.sr.ht::mail" = {
415 posting-domain = mkOption {
416 description = "Posting domain.";
418 default = "todo.${cfg.originBase}";
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.
426 default = "/tmp/todo.sr.ht-lmtp.sock";
428 sock-group = mkOption {
430 The lmtp daemon will make the unix socket group-read/write
431 for users in this group.
440 The configuration for the sourcehut network.
445 config = mkIf cfg.enable {
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.";
454 assertion = hasAttrByPath [ "meta.sr.ht" "origin" ] cfgIni && cfgIni."meta.sr.ht".origin != null;
455 message = "meta.sr.ht's origin must be defined.";
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.
464 let srv = builtins.match "^([a-z]*)\\.sr\\.ht(::.*)?$" k; in
465 srv == null || cfg.${head srv}.enable
468 environment.systemPackages = [ pkgs.sourcehut.coresrht ];
471 services.postgresql.enable = mkOverride 999 true;
473 services.postfix.enable = mkOverride 999 true;
475 services.redis.enable = mkOverride 999 true;
476 services.redis.bind = mkOverride 999 "127.0.0.1";
479 meta.doc = ./sourcehut.xml;
480 meta.maintainers = with maintainers; [ tomberek ];