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";
41 oauth-client-id = mkOptionNullOrStr "OAUTH client id for meta.sr.ht.";
42 oauth-client-secret = mkOptionNullOrStr "OAUTH client secret for meta.sr.ht.";
45 # Specialized python containing all the modules
46 python = pkgs.sourcehut.python.withPackages (ps: with ps; [
62 mkOptionNullOrStr = description: mkOption {
64 type = with types; nullOr str;
81 (mkRemovedOptionModule [ "services" "sourcehut" "nginx" "enable" ] ''
82 The sourcehut module supports `nginx` as a local reverse-proxy by default and doesn't
83 support other reverse-proxies officially.
85 However it's possible to use an alternative reverse-proxy by
88 * adjusting the relevant settings for server addresses and ports directly
90 Further details about this can be found in the `Sourcehut`-section of the NixOS-manual.
94 options.services.sourcehut = {
95 enable = mkEnableOption ''
96 sourcehut - git hosting, continuous integration, mailing list, ticket tracking,
97 task dispatching, wiki and account management services
100 services = mkOption {
101 type = types.nonEmptyListOf (types.enum [ "builds" "dispatch" "git" "hub" "hg" "lists" "man" "meta" "paste" "todo" ]);
102 default = [ "man" "meta" "paste" ];
103 example = [ "builds" "dispatch" "git" "hub" "hg" "lists" "man" "meta" "paste" "todo" ];
105 Services to enable on the sourcehut network.
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";
209 options."dispatch.sr.ht::github" = {
210 oauth-client-id = mkOptionNullOrStr "OAUTH client id.";
211 oauth-client-secret = mkOptionNullOrStr "OAUTH client secret.";
213 options."dispatch.sr.ht::gitlab" = {
214 enabled = mkEnableOption "GitLab integration";
215 canonical-upstream = mkOption {
217 description = "Canonical upstream.";
218 default = "gitlab.com";
220 repo-cache = mkOption {
222 description = "Repository cache directory.";
223 default = "./repo-cache";
225 "gitlab.com" = mkOption {
227 description = "GitLab id and secret.";
229 example = "GitLab:application id:secret";
232 options."builds.sr.ht" = commonServiceSettings "builds" // {
234 description = "The redis connection used for the celery worker.";
236 default = "redis://${rcfg.bind}:${toString rcfg.port}/3";
239 description = "The shell used for ssh.";
241 default = "runner-shell";
244 options."git.sr.ht" = commonServiceSettings "git" // {
245 outgoing-domain = mkOption {
246 description = "Outgoing domain.";
248 default = "http://git.${cfg.originBase}";
250 post-update-script = mkOption {
251 description = "A post-update script which is installed in every git repo.";
253 default = "${pkgs.sourcehut.gitsrht}/bin/gitsrht-update-hook";
256 description = "Path to git repositories on disk.";
258 default = "/var/lib/git";
260 webhooks = mkOption {
261 description = "The redis connection used for the webhooks worker.";
263 default = "redis://${rcfg.bind}:${toString rcfg.port}/1";
266 options."hg.sr.ht" = commonServiceSettings "hg" // {
267 changegroup-script = mkOption {
268 description = "A post-update script which is installed in every mercurial repo..";
270 default = "${cfg.python}/bin/hgsrht-hook-changegroup";
273 description = "Path to mercurial repositories on disk.";
275 default = "/var/lib/hg";
277 srhtext = mkOptionNullOrStr ''
278 Path to the srht mercurial extension
279 (defaults to where the hgsrht code is)
281 clone_bundle_threshold = mkOption {
282 description = ".hg/store size (in MB) past which the nightly job generates clone bundles.";
283 type = types.ints.unsigned;
287 description = "Path to hg-ssh (if not in $PATH).";
289 default = "${pkgs.mercurial}/bin/hg-ssh";
291 webhooks = mkOption {
292 description = "The redis connection used for the webhooks worker.";
294 default = "redis://${rcfg.bind}:${toString rcfg.port}/1";
297 options."hub.sr.ht" = commonServiceSettings "hub" // {
299 options."lists.sr.ht" = commonServiceSettings "lists" // {
300 allow-new-lists = mkEnableOption "Allow creation of new lists.";
301 network-key = mkOptionNullOrStr "Network key.";
302 notify-from = mkOption {
303 description = "Outgoing email for notifications generated by users.";
305 default = "lists-notify@${cfg.originBase}";
307 posting-domain = mkOption {
308 description = "Posting domain.";
310 default = "lists.${cfg.originBase}";
313 description = "The redis connection used for the celery worker.";
315 default = "redis://${rcfg.bind}:${toString rcfg.port}/4";
317 webhooks = mkOption {
318 description = "The redis connection used for the webhooks worker.";
320 default = "redis://${rcfg.bind}:${toString rcfg.port}/2";
323 options."lists.sr.ht::worker" = {
324 reject-mimetypes = mkOption {
325 type = with types; listOf str;
326 default = ["text/html"];
328 reject-url = mkOption {
329 description = "Reject URL.";
330 default = "https://man.sr.ht/lists.sr.ht/etiquette.md";
335 Path for the lmtp daemon's unix socket. Direct incoming mail to this socket.
336 Alternatively, specify IP:PORT and an SMTP server will be run instead.
339 default = "/tmp/lists.sr.ht-lmtp.sock";
341 sock-group = mkOption {
343 The lmtp daemon will make the unix socket group-read/write
344 for users in this group.
350 options."man.sr.ht" = commonServiceSettings "man" // {
352 options."meta.sr.ht" = commonServiceSettings "meta" // {
353 api-origin = mkOption {
354 description = "Origin URL for API, 100 more than web.";
356 default = "http://localhost:5100";
358 webhooks = mkOption {
359 description = "The redis connection used for the webhooks worker.";
361 default = "redis://${rcfg.bind}:${toString rcfg.port}/6";
363 welcome-emails = mkEnableOption "sending stock sourcehut welcome emails after signup";
365 options."meta.sr.ht::settings" = {
366 registration = mkEnableOption "public registration";
367 onboarding-redirect = mkOption {
368 description = "Where to redirect new users upon registration.";
370 default = "https://meta.${cfg.originBase}";
372 user-invites = mkOption {
374 How many invites each user is issued upon registration
375 (only applicable if open registration is disabled).
377 type = types.ints.unsigned;
381 options."meta.sr.ht::aliases" = mkOption {
382 description = "Aliases for the client IDs of commonly used OAuth clients.";
383 type = with types; attrsOf int;
385 example = { "git.sr.ht" = 12345; };
387 options."meta.sr.ht::billing" = {
388 enabled = mkEnableOption "the billing system";
389 stripe-public-key = mkOptionNullOrStr "Public key for Stripe. Get your keys at https://dashboard.stripe.com/account/apikeys";
390 stripe-secret-key = mkOptionNullOrStr "Secret key for Stripe. Get your keys at https://dashboard.stripe.com/account/apikeys";
392 options."paste.sr.ht" = commonServiceSettings "paste" // {
393 webhooks = mkOption {
395 default = "redis://${rcfg.bind}:${toString rcfg.port}/5";
398 options."todo.sr.ht" = commonServiceSettings "todo" // {
399 network-key = mkOptionNullOrStr "Network key.";
400 notify-from = mkOption {
401 description = "Outgoing email for notifications generated by users.";
403 default = "todo-notify@${cfg.originBase}";
405 webhooks = mkOption {
406 description = "The redis connection used for the webhooks worker.";
408 default = "redis://${rcfg.bind}:${toString rcfg.port}/7";
411 options."todo.sr.ht::mail" = {
412 posting-domain = mkOption {
413 description = "Posting domain.";
415 default = "todo.${cfg.originBase}";
419 Path for the lmtp daemon's unix socket. Direct incoming mail to this socket.
420 Alternatively, specify IP:PORT and an SMTP server will be run instead.
423 default = "/tmp/todo.sr.ht-lmtp.sock";
425 sock-group = mkOption {
427 The lmtp daemon will make the unix socket group-read/write
428 for users in this group.
437 The configuration for the sourcehut network.
442 config = mkIf cfg.enable {
446 assertion = with cfgIni.webhooks; private-key != null && stringLength private-key == 44;
447 message = "The webhook's private key must be defined and of a 44 byte length.";
451 assertion = hasAttrByPath [ "meta.sr.ht" "origin" ] cfgIni && cfgIni."meta.sr.ht".origin != null;
452 message = "meta.sr.ht's origin must be defined.";
456 environment.etc."sr.ht/config.ini".source =
457 settingsFormat.generate "sourcehut-config.ini"
458 # Disabled services must be removed from the config
459 # to be effectively disabled.
461 let srv = builtins.match "^([a-z]*)\\.sr\\.ht(::.*)?$" k; in
462 srv == null || elem (head srv) cfg.services
465 environment.systemPackages = [ pkgs.sourcehut.coresrht ];
468 services.postgresql.enable = mkOverride 999 true;
470 services.postfix.enable = mkOverride 999 true;
472 services.cron.enable = mkOverride 999 true;
474 services.redis.enable = mkOverride 999 true;
475 services.redis.bind = mkOverride 999 "127.0.0.1";
478 meta.doc = ./sourcehut.xml;
479 meta.maintainers = with maintainers; [ tomberek ];