diff --git a/nixos/lib/make-options-doc/options-to-docbook.xsl b/nixos/lib/make-options-doc/options-to-docbook.xsl
index b9ac2645051..b286f7b5e2c 100644
--- a/nixos/lib/make-options-doc/options-to-docbook.xsl
+++ b/nixos/lib/make-options-doc/options-to-docbook.xsl
@@ -20,7 +20,7 @@
Configuration Options
-
+
diff --git a/nixos/modules/services/misc/sourcehut/default.nix b/nixos/modules/services/misc/sourcehut/default.nix
index c84a75b0ca0..1bd21c278e0 100644
--- a/nixos/modules/services/misc/sourcehut/default.nix
+++ b/nixos/modules/services/misc/sourcehut/default.nix
@@ -1,14 +1,90 @@
{ config, pkgs, lib, ... }:
-
with lib;
let
+ inherit (config.services) nginx postfix postgresql redis;
+ inherit (config.users) users groups;
cfg = config.services.sourcehut;
- cfgIni = cfg.settings;
- settingsFormat = pkgs.formats.ini { };
+ domain = cfg.settings."sr.ht".global-domain;
+ settingsFormat = pkgs.formats.ini {
+ listToValue = concatMapStringsSep "," (generators.mkValueStringDefault {});
+ mkKeyValue = k: v:
+ if v == null then ""
+ else generators.mkKeyValueDefault {
+ mkValueString = v:
+ if v == true then "yes"
+ else if v == false then "no"
+ else generators.mkValueStringDefault {} v;
+ } "=" k v;
+ };
+ configIniOfService = srv: settingsFormat.generate "sourcehut-${srv}-config.ini"
+ # Each service needs access to only a subset of sections (and secrets).
+ (filterAttrs (k: v: v != null)
+ (mapAttrs (section: v:
+ let srvMatch = builtins.match "^([a-z]*)\\.sr\\.ht(::.*)?$" section; in
+ if srvMatch == null # Include sections shared by all services
+ || head srvMatch == srv # Include sections for the service being configured
+ then v
+ # Enable Web links and integrations between services.
+ else if tail srvMatch == [ null ] && elem (head srvMatch) cfg.services
+ then {
+ inherit (v) origin;
+ # mansrht crashes without it
+ oauth-client-id = v.oauth-client-id or null;
+ }
+ # Drop sub-sections of other services
+ else null)
+ (recursiveUpdate cfg.settings {
+ # Those paths are mounted using BindPaths= or BindReadOnlyPaths=
+ # for services needing access to them.
+ "builds.sr.ht::worker".buildlogs = "/var/log/sourcehut/buildsrht-worker";
+ "git.sr.ht".post-update-script = "/usr/bin/gitsrht-update-hook";
+ "git.sr.ht".repos = "/var/lib/sourcehut/gitsrht/repos";
+ "hg.sr.ht".changegroup-script = "/usr/bin/hgsrht-hook-changegroup";
+ "hg.sr.ht".repos = "/var/lib/sourcehut/hgsrht/repos";
+ # Making this a per service option despite being in a global section,
+ # so that it uses the redis-server used by the service.
+ "sr.ht".redis-host = cfg.${srv}.redis.host;
+ })));
+ commonServiceSettings = srv: {
+ origin = mkOption {
+ description = "URL ${srv}.sr.ht is being served at (protocol://domain)";
+ type = types.str;
+ default = "https://${srv}.${domain}";
+ defaultText = "https://${srv}.example.com";
+ };
+ debug-host = mkOption {
+ description = "Address to bind the debug server to.";
+ type = with types; nullOr str;
+ default = null;
+ };
+ debug-port = mkOption {
+ description = "Port to bind the debug server to.";
+ type = with types; nullOr str;
+ default = null;
+ };
+ connection-string = mkOption {
+ description = "SQLAlchemy connection string for the database.";
+ type = types.str;
+ default = "postgresql:///localhost?user=${srv}srht&host=/run/postgresql";
+ };
+ migrate-on-upgrade = mkEnableOption "automatic migrations on package upgrade" // { default = true; };
+ oauth-client-id = mkOption {
+ description = "${srv}.sr.ht's OAuth client id for meta.sr.ht.";
+ type = types.str;
+ };
+ oauth-client-secret = mkOption {
+ description = "${srv}.sr.ht's OAuth client secret for meta.sr.ht.";
+ type = types.path;
+ apply = s: "<" + toString s;
+ };
+ };
# Specialized python containing all the modules
python = pkgs.sourcehut.python.withPackages (ps: with ps; [
gunicorn
+ eventlet
+ # For monitoring Celery: sudo -u listssrht celery --app listssrht.process -b redis+socket:///run/redis-sourcehut/redis.sock?virtual_host=5 flower
+ flower
# Sourcehut services
srht
buildsrht
@@ -19,72 +95,37 @@ let
listssrht
mansrht
metasrht
+ # Not a python package
+ #pagessrht
pastesrht
todosrht
]);
+ mkOptionNullOrStr = description: mkOption {
+ inherit description;
+ type = with types; nullOr str;
+ default = null;
+ };
in
{
- imports =
- [
- ./git.nix
- ./hg.nix
- ./hub.nix
- ./todo.nix
- ./man.nix
- ./meta.nix
- ./paste.nix
- ./builds.nix
- ./lists.nix
- ./dispatch.nix
- (mkRemovedOptionModule [ "services" "sourcehut" "nginx" "enable" ] ''
- The sourcehut module supports `nginx` as a local reverse-proxy by default and doesn't
- support other reverse-proxies officially.
-
- However it's possible to use an alternative reverse-proxy by
-
- * disabling nginx
- * adjusting the relevant settings for server addresses and ports directly
-
- Further details about this can be found in the `Sourcehut`-section of the NixOS-manual.
- '')
- ];
-
options.services.sourcehut = {
- enable = mkOption {
- type = types.bool;
- default = false;
- description = ''
- Enable sourcehut - git hosting, continuous integration, mailing list, ticket tracking,
- task dispatching, wiki and account management services
- '';
- };
+ enable = mkEnableOption ''
+ sourcehut - git hosting, continuous integration, mailing list, ticket tracking,
+ task dispatching, wiki and account management services
+ '';
services = mkOption {
- type = types.nonEmptyListOf (types.enum [ "builds" "dispatch" "git" "hub" "hg" "lists" "man" "meta" "paste" "todo" ]);
- default = [ "man" "meta" "paste" ];
- example = [ "builds" "dispatch" "git" "hub" "hg" "lists" "man" "meta" "paste" "todo" ];
+ type = with types; listOf (enum
+ [ "builds" "dispatch" "git" "hg" "hub" "lists" "man" "meta" "pages" "paste" "todo" ]);
+ defaultText = "locally enabled services";
description = ''
- Services to enable on the sourcehut network.
+ Services that may be displayed as links in the title bar of the Web interface.
'';
};
- originBase = mkOption {
+ listenAddress = mkOption {
type = types.str;
- default = with config.networking; hostName + lib.optionalString (domain != null) ".${domain}";
- defaultText = literalExpression ''
- with config.networking; hostName + optionalString (domain != null) ".''${domain}"
- '';
- description = ''
- Host name used by reverse-proxy and for default settings. Will host services at git."''${originBase}". For example: git.sr.ht
- '';
- };
-
- address = mkOption {
- type = types.str;
- default = "127.0.0.1";
- description = ''
- Address to bind to.
- '';
+ default = "localhost";
+ description = "Address to bind to.";
};
python = mkOption {
@@ -97,105 +138,1247 @@ in
'';
};
- statePath = mkOption {
- type = types.path;
- default = "/var/lib/sourcehut";
- description = ''
- Root state path for the sourcehut network. If left as the default value
- this directory will automatically be created before the sourcehut server
- starts, otherwise the sysadmin is responsible for ensuring the
- directory exists with appropriate ownership and permissions.
- '';
+ minio = {
+ enable = mkEnableOption ''local minio integration'';
+ };
+
+ nginx = {
+ enable = mkEnableOption ''local nginx integration'';
+ virtualHost = mkOption {
+ type = types.attrs;
+ default = {};
+ description = "Virtual-host configuration merged with all Sourcehut's virtual-hosts.";
+ };
+ };
+
+ postfix = {
+ enable = mkEnableOption ''local postfix integration'';
+ };
+
+ postgresql = {
+ enable = mkEnableOption ''local postgresql integration'';
+ };
+
+ redis = {
+ enable = mkEnableOption ''local redis integration in a dedicated redis-server'';
};
settings = mkOption {
type = lib.types.submodule {
freeformType = settingsFormat.type;
+ options."sr.ht" = {
+ global-domain = mkOption {
+ description = "Global domain name.";
+ type = types.str;
+ example = "example.com";
+ };
+ environment = mkOption {
+ description = "Values other than \"production\" adds a banner to each page.";
+ type = types.enum [ "development" "production" ];
+ default = "development";
+ };
+ network-key = mkOption {
+ description = ''
+ An absolute file path (which should be outside the Nix-store)
+ to a secret key to encrypt internal messages with. Use srht-keygen network
to
+ generate this key. It must be consistent between all services and nodes.
+ '';
+ type = types.path;
+ apply = s: "<" + toString s;
+ };
+ owner-email = mkOption {
+ description = "Owner's email.";
+ type = types.str;
+ default = "contact@example.com";
+ };
+ owner-name = mkOption {
+ description = "Owner's name.";
+ type = types.str;
+ default = "John Doe";
+ };
+ site-blurb = mkOption {
+ description = "Blurb for your site.";
+ type = types.str;
+ default = "the hacker's forge";
+ };
+ site-info = mkOption {
+ description = "The top-level info page for your site.";
+ type = types.str;
+ default = "https://sourcehut.org";
+ };
+ service-key = mkOption {
+ description = ''
+ An absolute file path (which should be outside the Nix-store)
+ to a key used for encrypting session cookies. Use srht-keygen service
to
+ generate the service key. This must be shared between each node of the same
+ service (e.g. git1.sr.ht and git2.sr.ht), but different services may use
+ different keys. If you configure all of your services with the same
+ config.ini, you may use the same service-key for all of them.
+ '';
+ type = types.path;
+ apply = s: "<" + toString s;
+ };
+ site-name = mkOption {
+ description = "The name of your network of sr.ht-based sites.";
+ type = types.str;
+ default = "sourcehut";
+ };
+ source-url = mkOption {
+ description = "The source code for your fork of sr.ht.";
+ type = types.str;
+ default = "https://git.sr.ht/~sircmpwn/srht";
+ };
+ };
+ options.mail = {
+ smtp-host = mkOptionNullOrStr "Outgoing SMTP host.";
+ smtp-port = mkOption {
+ description = "Outgoing SMTP port.";
+ type = with types; nullOr port;
+ default = null;
+ };
+ smtp-user = mkOptionNullOrStr "Outgoing SMTP user.";
+ smtp-password = mkOptionNullOrStr "Outgoing SMTP password.";
+ smtp-from = mkOptionNullOrStr "Outgoing SMTP FROM.";
+ error-to = mkOptionNullOrStr "Address receiving application exceptions";
+ error-from = mkOptionNullOrStr "Address sending application exceptions";
+ pgp-privkey = mkOptionNullOrStr ''
+ An absolute file path (which should be outside the Nix-store)
+ to an OpenPGP private key.
+
+ Your PGP key information (DO NOT mix up pub and priv here)
+ You must remove the password from your secret key, if present.
+ You can do this with gpg --edit-key [key-id]
,
+ then use the passwd
command and do not enter a new password.
+ '';
+ pgp-pubkey = mkOptionNullOrStr "OpenPGP public key.";
+ pgp-key-id = mkOptionNullOrStr "OpenPGP key identifier.";
+ };
+ options.objects = {
+ s3-upstream = mkOption {
+ description = "Configure the S3-compatible object storage service.";
+ type = with types; nullOr str;
+ default = null;
+ };
+ s3-access-key = mkOption {
+ description = "Access key to the S3-compatible object storage service";
+ type = with types; nullOr str;
+ default = null;
+ };
+ s3-secret-key = mkOption {
+ description = ''
+ An absolute file path (which should be outside the Nix-store)
+ to the secret key of the S3-compatible object storage service.
+ '';
+ type = with types; nullOr path;
+ default = null;
+ apply = mapNullable (s: "<" + toString s);
+ };
+ };
+ options.webhooks = {
+ private-key = mkOption {
+ description = ''
+ An absolute file path (which should be outside the Nix-store)
+ to a base64-encoded Ed25519 key for signing webhook payloads.
+ This should be consistent for all *.sr.ht sites,
+ as this key will be used to verify signatures
+ from other sites in your network.
+ Use the srht-keygen webhook
command to generate a key.
+ '';
+ type = types.path;
+ apply = s: "<" + toString s;
+ };
+ };
+
+ options."dispatch.sr.ht" = commonServiceSettings "dispatch" // {
+ };
+ options."dispatch.sr.ht::github" = {
+ oauth-client-id = mkOptionNullOrStr "OAuth client id.";
+ oauth-client-secret = mkOptionNullOrStr "OAuth client secret.";
+ };
+ options."dispatch.sr.ht::gitlab" = {
+ enabled = mkEnableOption "GitLab integration";
+ canonical-upstream = mkOption {
+ type = types.str;
+ description = "Canonical upstream.";
+ default = "gitlab.com";
+ };
+ repo-cache = mkOption {
+ type = types.str;
+ description = "Repository cache directory.";
+ default = "./repo-cache";
+ };
+ "gitlab.com" = mkOption {
+ type = with types; nullOr str;
+ description = "GitLab id and secret.";
+ default = null;
+ example = "GitLab:application id:secret";
+ };
+ };
+
+ options."builds.sr.ht" = commonServiceSettings "builds" // {
+ allow-free = mkEnableOption "nonpaying users to submit builds";
+ redis = mkOption {
+ description = "The Redis connection used for the Celery worker.";
+ type = types.str;
+ default = "redis+socket:///run/redis-sourcehut-buildsrht/redis.sock?virtual_host=2";
+ };
+ shell = mkOption {
+ description = ''
+ Scripts used to launch on SSH connection.
+ /usr/bin/master-shell on master,
+ /usr/bin/runner-shell on runner.
+ If master and worker are on the same system
+ set to /usr/bin/runner-shell.
+ '';
+ type = types.enum ["/usr/bin/master-shell" "/usr/bin/runner-shell"];
+ default = "/usr/bin/master-shell";
+ };
+ };
+ options."builds.sr.ht::worker" = {
+ bind-address = mkOption {
+ description = ''
+ HTTP bind address for serving local build information/monitoring.
+ '';
+ type = types.str;
+ default = "localhost:8080";
+ };
+ buildlogs = mkOption {
+ description = "Path to write build logs.";
+ type = types.str;
+ default = "/var/log/sourcehut/buildsrht-worker";
+ };
+ name = mkOption {
+ description = ''
+ Listening address and listening port
+ of the build runner (with HTTP port if not 80).
+ '';
+ type = types.str;
+ default = "localhost:5020";
+ };
+ timeout = mkOption {
+ description = ''
+ Max build duration.
+ See .
+ '';
+ type = types.str;
+ default = "3m";
+ };
+ };
+
+ options."git.sr.ht" = commonServiceSettings "git" // {
+ outgoing-domain = mkOption {
+ description = "Outgoing domain.";
+ type = types.str;
+ default = "https://git.localhost.localdomain";
+ };
+ post-update-script = mkOption {
+ description = ''
+ A post-update script which is installed in every git repo.
+ This setting is propagated to newer and existing repositories.
+ '';
+ type = types.path;
+ default = "${pkgs.sourcehut.gitsrht}/bin/gitsrht-update-hook";
+ defaultText = "\${pkgs.sourcehut.gitsrht}/bin/gitsrht-update-hook";
+ };
+ repos = mkOption {
+ description = ''
+ Path to git repositories on disk.
+ If changing the default, you must ensure that
+ the gitsrht's user as read and write access to it.
+ '';
+ type = types.str;
+ default = "/var/lib/sourcehut/gitsrht/repos";
+ };
+ webhooks = mkOption {
+ description = "The Redis connection used for the webhooks worker.";
+ type = types.str;
+ default = "redis+socket:///run/redis-sourcehut-gitsrht/redis.sock?virtual_host=1";
+ };
+ };
+ options."git.sr.ht::api" = {
+ internal-ipnet = mkOption {
+ description = ''
+ Set of IP subnets which are permitted to utilize internal API
+ authentication. This should be limited to the subnets
+ from which your *.sr.ht services are running.
+ See .
+ '';
+ type = with types; listOf str;
+ default = [ "127.0.0.0/8" "::1/128" ];
+ };
+ };
+
+ options."hg.sr.ht" = commonServiceSettings "hg" // {
+ changegroup-script = mkOption {
+ description = ''
+ A changegroup script which is installed in every mercurial repo.
+ This setting is propagated to newer and existing repositories.
+ '';
+ type = types.str;
+ default = "${cfg.python}/bin/hgsrht-hook-changegroup";
+ defaultText = "\${cfg.python}/bin/hgsrht-hook-changegroup";
+ };
+ repos = mkOption {
+ description = ''
+ Path to mercurial repositories on disk.
+ If changing the default, you must ensure that
+ the hgsrht's user as read and write access to it.
+ '';
+ type = types.str;
+ default = "/var/lib/sourcehut/hgsrht/repos";
+ };
+ srhtext = mkOptionNullOrStr ''
+ Path to the srht mercurial extension
+ (defaults to where the hgsrht code is)
+ '';
+ clone_bundle_threshold = mkOption {
+ description = ".hg/store size (in MB) past which the nightly job generates clone bundles.";
+ type = types.ints.unsigned;
+ default = 50;
+ };
+ hg_ssh = mkOption {
+ description = "Path to hg-ssh (if not in $PATH).";
+ type = types.str;
+ default = "${pkgs.mercurial}/bin/hg-ssh";
+ defaultText = "\${pkgs.mercurial}/bin/hg-ssh";
+ };
+ webhooks = mkOption {
+ description = "The Redis connection used for the webhooks worker.";
+ type = types.str;
+ default = "redis+socket:///run/redis-sourcehut-hgsrht/redis.sock?virtual_host=1";
+ };
+ };
+
+ options."hub.sr.ht" = commonServiceSettings "hub" // {
+ };
+
+ options."lists.sr.ht" = commonServiceSettings "lists" // {
+ allow-new-lists = mkEnableOption "Allow creation of new lists.";
+ notify-from = mkOption {
+ description = "Outgoing email for notifications generated by users.";
+ type = types.str;
+ default = "lists-notify@localhost.localdomain";
+ };
+ posting-domain = mkOption {
+ description = "Posting domain.";
+ type = types.str;
+ default = "lists.localhost.localdomain";
+ };
+ redis = mkOption {
+ description = "The Redis connection used for the Celery worker.";
+ type = types.str;
+ default = "redis+socket:///run/redis-sourcehut-listssrht/redis.sock?virtual_host=2";
+ };
+ webhooks = mkOption {
+ description = "The Redis connection used for the webhooks worker.";
+ type = types.str;
+ default = "redis+socket:///run/redis-sourcehut-listssrht/redis.sock?virtual_host=1";
+ };
+ };
+ options."lists.sr.ht::worker" = {
+ reject-mimetypes = mkOption {
+ description = ''
+ Comma-delimited list of Content-Types to reject. Messages with Content-Types
+ included in this list are rejected. Multipart messages are always supported,
+ and each part is checked against this list.
+
+ Uses fnmatch for wildcard expansion.
+ '';
+ type = with types; listOf str;
+ default = ["text/html"];
+ };
+ reject-url = mkOption {
+ description = "Reject URL.";
+ type = types.str;
+ default = "https://man.sr.ht/lists.sr.ht/etiquette.md";
+ };
+ sock = mkOption {
+ description = ''
+ Path for the lmtp daemon's unix socket. Direct incoming mail to this socket.
+ Alternatively, specify IP:PORT and an SMTP server will be run instead.
+ '';
+ type = types.str;
+ default = "/tmp/lists.sr.ht-lmtp.sock";
+ };
+ sock-group = mkOption {
+ description = ''
+ The lmtp daemon will make the unix socket group-read/write
+ for users in this group.
+ '';
+ type = types.str;
+ default = "postfix";
+ };
+ };
+
+ options."man.sr.ht" = commonServiceSettings "man" // {
+ };
+
+ options."meta.sr.ht" =
+ removeAttrs (commonServiceSettings "meta")
+ ["oauth-client-id" "oauth-client-secret"] // {
+ api-origin = mkOption {
+ description = "Origin URL for API, 100 more than web.";
+ type = types.str;
+ default = "http://${cfg.listenAddress}:${toString (cfg.meta.port + 100)}";
+ defaultText = ''http://:''${toString ( + 100)}'';
+ };
+ webhooks = mkOption {
+ description = "The Redis connection used for the webhooks worker.";
+ type = types.str;
+ default = "redis+socket:///run/redis-sourcehut-metasrht/redis.sock?virtual_host=1";
+ };
+ welcome-emails = mkEnableOption "sending stock sourcehut welcome emails after signup";
+ };
+ options."meta.sr.ht::api" = {
+ internal-ipnet = mkOption {
+ description = ''
+ Set of IP subnets which are permitted to utilize internal API
+ authentication. This should be limited to the subnets
+ from which your *.sr.ht services are running.
+ See .
+ '';
+ type = with types; listOf str;
+ default = [ "127.0.0.0/8" "::1/128" ];
+ };
+ };
+ options."meta.sr.ht::aliases" = mkOption {
+ description = "Aliases for the client IDs of commonly used OAuth clients.";
+ type = with types; attrsOf int;
+ default = {};
+ example = { "git.sr.ht" = 12345; };
+ };
+ options."meta.sr.ht::billing" = {
+ enabled = mkEnableOption "the billing system";
+ stripe-public-key = mkOptionNullOrStr "Public key for Stripe. Get your keys at https://dashboard.stripe.com/account/apikeys";
+ stripe-secret-key = mkOptionNullOrStr ''
+ An absolute file path (which should be outside the Nix-store)
+ to a secret key for Stripe. Get your keys at https://dashboard.stripe.com/account/apikeys
+ '' // {
+ apply = mapNullable (s: "<" + toString s);
+ };
+ };
+ options."meta.sr.ht::settings" = {
+ registration = mkEnableOption "public registration";
+ onboarding-redirect = mkOption {
+ description = "Where to redirect new users upon registration.";
+ type = types.str;
+ default = "https://meta.localhost.localdomain";
+ };
+ user-invites = mkOption {
+ description = ''
+ How many invites each user is issued upon registration
+ (only applicable if open registration is disabled).
+ '';
+ type = types.ints.unsigned;
+ default = 5;
+ };
+ };
+
+ options."pages.sr.ht" = commonServiceSettings "pages" // {
+ gemini-certs = mkOption {
+ description = ''
+ An absolute file path (which should be outside the Nix-store)
+ to Gemini certificates.
+ '';
+ type = with types; nullOr path;
+ default = null;
+ };
+ max-site-size = mkOption {
+ description = "Maximum size of any given site (post-gunzip), in MiB.";
+ type = types.int;
+ default = 1024;
+ };
+ user-domain = mkOption {
+ description = ''
+ Configures the user domain, if enabled.
+ All users are given <username>.this.domain.
+ '';
+ type = with types; nullOr str;
+ default = null;
+ };
+ };
+ options."pages.sr.ht::api" = {
+ internal-ipnet = mkOption {
+ description = ''
+ Set of IP subnets which are permitted to utilize internal API
+ authentication. This should be limited to the subnets
+ from which your *.sr.ht services are running.
+ See .
+ '';
+ type = with types; listOf str;
+ default = [ "127.0.0.0/8" "::1/128" ];
+ };
+ };
+
+ options."paste.sr.ht" = commonServiceSettings "paste" // {
+ };
+
+ options."todo.sr.ht" = commonServiceSettings "todo" // {
+ notify-from = mkOption {
+ description = "Outgoing email for notifications generated by users.";
+ type = types.str;
+ default = "todo-notify@localhost.localdomain";
+ };
+ webhooks = mkOption {
+ description = "The Redis connection used for the webhooks worker.";
+ type = types.str;
+ default = "redis+socket:///run/redis-sourcehut-todosrht/redis.sock?virtual_host=1";
+ };
+ };
+ options."todo.sr.ht::mail" = {
+ posting-domain = mkOption {
+ description = "Posting domain.";
+ type = types.str;
+ default = "todo.localhost.localdomain";
+ };
+ sock = mkOption {
+ description = ''
+ Path for the lmtp daemon's unix socket. Direct incoming mail to this socket.
+ Alternatively, specify IP:PORT and an SMTP server will be run instead.
+ '';
+ type = types.str;
+ default = "/tmp/todo.sr.ht-lmtp.sock";
+ };
+ sock-group = mkOption {
+ description = ''
+ The lmtp daemon will make the unix socket group-read/write
+ for users in this group.
+ '';
+ type = types.str;
+ default = "postfix";
+ };
+ };
};
default = { };
description = ''
The configuration for the sourcehut network.
'';
};
- };
- config = mkIf cfg.enable {
- assertions =
- [
- {
- assertion = with cfgIni.webhooks; private-key != null && stringLength private-key == 44;
- message = "The webhook's private key must be defined and of a 44 byte length.";
- }
+ builds = {
+ enableWorker = mkEnableOption ''
+ worker for builds.sr.ht
- {
- assertion = hasAttrByPath [ "meta.sr.ht" "origin" ] cfgIni && cfgIni."meta.sr.ht".origin != null;
- message = "meta.sr.ht's origin must be defined.";
- }
- ];
+
+ For smaller deployments, job runners can be installed alongside the master server
+ but even if you only build your own software, integration with other services
+ may cause you to run untrusted builds
+ (e.g. automatic testing of patches via listssrht).
+ See .
+
+ '';
- virtualisation.docker.enable = true;
- environment.etc."sr.ht/config.ini".source =
- settingsFormat.generate "sourcehut-config.ini" (mapAttrsRecursive
- (
- path: v: if v == null then "" else v
- )
- cfg.settings);
+ images = mkOption {
+ type = with types; attrsOf (attrsOf (attrsOf package));
+ default = { };
+ example = lib.literalExpression ''(let
+ # Pinning unstable to allow usage with flakes and limit rebuilds.
+ pkgs_unstable = builtins.fetchGit {
+ url = "https://github.com/NixOS/nixpkgs";
+ rev = "ff96a0fa5635770390b184ae74debea75c3fd534";
+ ref = "nixos-unstable";
+ };
+ image_from_nixpkgs = (import ("${pkgs.sourcehut.buildsrht}/lib/images/nixos/image.nix") {
+ pkgs = (import pkgs_unstable {});
+ });
+ in
+ {
+ nixos.unstable.x86_64 = image_from_nixpkgs;
+ }
+ )'';
+ description = ''
+ Images for builds.sr.ht. Each package should be distro.release.arch and point to a /nix/store/package/root.img.qcow2.
+ '';
+ };
+ };
- environment.systemPackages = [ pkgs.sourcehut.coresrht ];
+ git = {
+ package = mkOption {
+ type = types.package;
+ default = pkgs.git;
+ example = literalExpression "pkgs.gitFull";
+ description = ''
+ Git package for git.sr.ht. This can help silence collisions.
+ '';
+ };
+ fcgiwrap.preforkProcess = mkOption {
+ description = "Number of fcgiwrap processes to prefork.";
+ type = types.int;
+ default = 4;
+ };
+ };
- # PostgreSQL server
- services.postgresql.enable = mkOverride 999 true;
- # Mail server
- services.postfix.enable = mkOverride 999 true;
- # Cron daemon
- services.cron.enable = mkOverride 999 true;
- # Redis server
- services.redis.enable = mkOverride 999 true;
- services.redis.bind = mkOverride 999 "127.0.0.1";
+ hg = {
+ package = mkOption {
+ type = types.package;
+ default = pkgs.mercurial;
+ description = ''
+ Mercurial package for hg.sr.ht. This can help silence collisions.
+ '';
+ };
+ cloneBundles = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Generate clonebundles (which require more disk space but dramatically speed up cloning large repositories).
+ '';
+ };
+ };
- services.sourcehut.settings = {
- # The name of your network of sr.ht-based sites
- "sr.ht".site-name = mkDefault "sourcehut";
- # The top-level info page for your site
- "sr.ht".site-info = mkDefault "https://sourcehut.org";
- # {{ site-name }}, {{ site-blurb }}
- "sr.ht".site-blurb = mkDefault "the hacker's forge";
- # If this != production, we add a banner to each page
- "sr.ht".environment = mkDefault "development";
- # Contact information for the site owners
- "sr.ht".owner-name = mkDefault "Drew DeVault";
- "sr.ht".owner-email = mkDefault "sir@cmpwn.com";
- # The source code for your fork of sr.ht
- "sr.ht".source-url = mkDefault "https://git.sr.ht/~sircmpwn/srht";
- # A secret key to encrypt session cookies with
- "sr.ht".secret-key = mkDefault null;
- "sr.ht".global-domain = mkDefault null;
-
- # Outgoing SMTP settings
- mail.smtp-host = mkDefault null;
- mail.smtp-port = mkDefault null;
- mail.smtp-user = mkDefault null;
- mail.smtp-password = mkDefault null;
- mail.smtp-from = mkDefault null;
- # Application exceptions are emailed to this address
- mail.error-to = mkDefault null;
- mail.error-from = mkDefault null;
- # Your PGP key information (DO NOT mix up pub and priv here)
- # You must remove the password from your secret key, if present.
- # You can do this with gpg --edit-key [key-id], then use the passwd
- # command and do not enter a new password.
- mail.pgp-privkey = mkDefault null;
- mail.pgp-pubkey = mkDefault null;
- mail.pgp-key-id = mkDefault null;
-
- # base64-encoded Ed25519 key for signing webhook payloads. This should be
- # consistent for all *.sr.ht sites, as we'll use this key to verify signatures
- # from other sites in your network.
- #
- # Use the srht-webhook-keygen command to generate a key.
- webhooks.private-key = mkDefault null;
+ lists = {
+ process = {
+ extraArgs = mkOption {
+ type = with types; listOf str;
+ default = [ "--loglevel DEBUG" "--pool eventlet" "--without-heartbeat" ];
+ description = "Extra arguments passed to the Celery responsible for processing mails.";
+ };
+ celeryConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Content of the celeryconfig.py used by the Celery of listssrht-process.";
+ };
+ };
};
};
+
+ config = mkIf cfg.enable (mkMerge [
+ {
+ environment.systemPackages = [ pkgs.sourcehut.coresrht ];
+
+ services.sourcehut.settings = {
+ "git.sr.ht".outgoing-domain = mkDefault "https://git.${domain}";
+ "lists.sr.ht".notify-from = mkDefault "lists-notify@${domain}";
+ "lists.sr.ht".posting-domain = mkDefault "lists.${domain}";
+ "meta.sr.ht::settings".onboarding-redirect = mkDefault "https://meta.${domain}";
+ "todo.sr.ht".notify-from = mkDefault "todo-notify@${domain}";
+ "todo.sr.ht::mail".posting-domain = mkDefault "todo.${domain}";
+ };
+ }
+ (mkIf cfg.postgresql.enable {
+ assertions = [
+ { assertion = postgresql.enable;
+ message = "postgresql must be enabled and configured";
+ }
+ ];
+ })
+ (mkIf cfg.postfix.enable {
+ assertions = [
+ { assertion = postfix.enable;
+ message = "postfix must be enabled and configured";
+ }
+ ];
+ # Needed for sharing the LMTP sockets with JoinsNamespaceOf=
+ systemd.services.postfix.serviceConfig.PrivateTmp = true;
+ })
+ (mkIf cfg.redis.enable {
+ services.redis.vmOverCommit = mkDefault true;
+ })
+ (mkIf cfg.nginx.enable {
+ assertions = [
+ { assertion = nginx.enable;
+ message = "nginx must be enabled and configured";
+ }
+ ];
+ # For proxyPass= in virtual-hosts for Sourcehut services.
+ services.nginx.recommendedProxySettings = mkDefault true;
+ })
+ (mkIf (cfg.builds.enable || cfg.git.enable || cfg.hg.enable) {
+ services.openssh = {
+ # Note that sshd will continue to honor AuthorizedKeysFile.
+ # Note that you may want automatically rotate
+ # or link to /dev/null the following log files:
+ # - /var/log/gitsrht-dispatch
+ # - /var/log/{build,git,hg}srht-keys
+ # - /var/log/{git,hg}srht-shell
+ # - /var/log/gitsrht-update-hook
+ authorizedKeysCommand = ''/etc/ssh/sourcehut/subdir/srht-dispatch "%u" "%h" "%t" "%k"'';
+ # srht-dispatch will setuid/setgid according to [git.sr.ht::dispatch]
+ authorizedKeysCommandUser = "root";
+ extraConfig = ''
+ PermitUserEnvironment SRHT_*
+ '';
+ };
+ environment.etc."ssh/sourcehut/config.ini".source =
+ settingsFormat.generate "sourcehut-dispatch-config.ini"
+ (filterAttrs (k: v: k == "git.sr.ht::dispatch")
+ cfg.settings);
+ environment.etc."ssh/sourcehut/subdir/srht-dispatch" = {
+ # sshd_config(5): The program must be owned by root, not writable by group or others
+ mode = "0755";
+ source = pkgs.writeShellScript "srht-dispatch" ''
+ set -e
+ cd /etc/ssh/sourcehut/subdir
+ ${cfg.python}/bin/gitsrht-dispatch "$@"
+ '';
+ };
+ systemd.services.sshd = {
+ #path = optional cfg.git.enable [ cfg.git.package ];
+ serviceConfig = {
+ BindReadOnlyPaths =
+ # Note that those /usr/bin/* paths are hardcoded in multiple places in *.sr.ht,
+ # for instance to get the user from the [git.sr.ht::dispatch] settings.
+ # *srht-keys needs to:
+ # - access a redis-server in [sr.ht] redis-host,
+ # - access the PostgreSQL server in [*.sr.ht] connection-string,
+ # - query metasrht-api (through the HTTP API).
+ # Using this has the side effect of creating empty files in /usr/bin/
+ optionals cfg.builds.enable [
+ "${pkgs.writeShellScript "buildsrht-keys-wrapper" ''
+ set -e
+ cd /run/sourcehut/buildsrht/subdir
+ set -x
+ exec -a "$0" ${pkgs.sourcehut.buildsrht}/bin/buildsrht-keys "$@"
+ ''}:/usr/bin/buildsrht-keys"
+ "${pkgs.sourcehut.buildsrht}/bin/master-shell:/usr/bin/master-shell"
+ "${pkgs.sourcehut.buildsrht}/bin/runner-shell:/usr/bin/runner-shell"
+ ] ++
+ optionals cfg.git.enable [
+ # /path/to/gitsrht-keys calls /path/to/gitsrht-shell,
+ # or [git.sr.ht] shell= if set.
+ "${pkgs.writeShellScript "gitsrht-keys-wrapper" ''
+ set -e
+ cd /run/sourcehut/gitsrht/subdir
+ set -x
+ exec -a "$0" ${pkgs.sourcehut.gitsrht}/bin/gitsrht-keys "$@"
+ ''}:/usr/bin/gitsrht-keys"
+ "${pkgs.writeShellScript "gitsrht-shell-wrapper" ''
+ set -e
+ cd /run/sourcehut/gitsrht/subdir
+ set -x
+ exec -a "$0" ${pkgs.sourcehut.gitsrht}/bin/gitsrht-shell "$@"
+ ''}:/usr/bin/gitsrht-shell"
+ "${pkgs.writeShellScript "gitsrht-update-hook" ''
+ set -e
+ test -e "''${PWD%/*}"/config.ini ||
+ # Git hooks are run relative to their repository's directory,
+ # but gitsrht-update-hook looks up ../config.ini
+ ln -s /run/sourcehut/gitsrht/config.ini "''${PWD%/*}"/config.ini
+ # hooks/post-update calls /usr/bin/gitsrht-update-hook as hooks/stage-3
+ # but this wrapper being a bash script, it overrides $0 with /usr/bin/gitsrht-update-hook
+ # hence this hack to put hooks/stage-3 back into gitsrht-update-hook's $0
+ if test "''${STAGE3:+set}"
+ then
+ set -x
+ exec -a hooks/stage-3 ${pkgs.sourcehut.gitsrht}/bin/gitsrht-update-hook "$@"
+ else
+ export STAGE3=set
+ set -x
+ exec -a "$0" ${pkgs.sourcehut.gitsrht}/bin/gitsrht-update-hook "$@"
+ fi
+ ''}:/usr/bin/gitsrht-update-hook"
+ ] ++
+ optionals cfg.hg.enable [
+ # /path/to/hgsrht-keys calls /path/to/hgsrht-shell,
+ # or [hg.sr.ht] shell= if set.
+ "${pkgs.writeShellScript "hgsrht-keys-wrapper" ''
+ set -e
+ cd /run/sourcehut/hgsrht/subdir
+ set -x
+ exec -a "$0" ${pkgs.sourcehut.hgsrht}/bin/hgsrht-keys "$@"
+ ''}:/usr/bin/hgsrht-keys"
+ "${pkgs.writeShellScript "hgsrht-shell-wrapper" ''
+ set -e
+ cd /run/sourcehut/hgsrht/subdir
+ set -x
+ exec -a "$0" ${pkgs.sourcehut.hgsrht}/bin/hgsrht-shell "$@"
+ ''}:/usr/bin/hgsrht-shell"
+ # Mercurial's changegroup hooks are run relative to their repository's directory,
+ # but hgsrht-hook-changegroup looks up ./config.ini
+ "${pkgs.writeShellScript "hgsrht-hook-changegroup" ''
+ set -e
+ test -e "''$PWD"/config.ini ||
+ ln -s /run/sourcehut/hgsrht/config.ini "''$PWD"/config.ini
+ set -x
+ exec -a "$0" ${cfg.python}/bin/hgsrht-hook-changegroup "$@"
+ ''}:/usr/bin/hgsrht-hook-changegroup"
+ ];
+ };
+ };
+ })
+ ]);
+
+ imports = [
+
+ (import ./service.nix "builds" {
+ inherit configIniOfService;
+ srvsrht = "buildsrht";
+ port = 5002;
+ # TODO: a celery worker on the master and worker are apparently needed
+ extraServices.buildsrht-worker = let
+ qemuPackage = pkgs.qemu_kvm;
+ serviceName = "buildsrht-worker";
+ statePath = "/var/lib/sourcehut/${serviceName}";
+ in mkIf cfg.builds.enableWorker {
+ path = [ pkgs.openssh pkgs.docker ];
+ preStart = ''
+ set -x
+ if test -z "$(docker images -q qemu:latest 2>/dev/null)" \
+ || test "$(cat ${statePath}/docker-image-qemu)" != "${qemuPackage.version}"
+ then
+ # Create and import qemu:latest image for docker
+ ${pkgs.dockerTools.streamLayeredImage {
+ name = "qemu";
+ tag = "latest";
+ contents = [ qemuPackage ];
+ }} | docker load
+ # Mark down current package version
+ echo '${qemuPackage.version}' >${statePath}/docker-image-qemu
+ fi
+ '';
+ serviceConfig = {
+ ExecStart = "${pkgs.sourcehut.buildsrht}/bin/builds.sr.ht-worker";
+ BindPaths = [ cfg.settings."builds.sr.ht::worker".buildlogs ];
+ LogsDirectory = [ "sourcehut/${serviceName}" ];
+ RuntimeDirectory = [ "sourcehut/${serviceName}/subdir" ];
+ StateDirectory = [ "sourcehut/${serviceName}" ];
+ TimeoutStartSec = "1800s";
+ # builds.sr.ht-worker looks up ../config.ini
+ WorkingDirectory = "-"+"/run/sourcehut/${serviceName}/subdir";
+ };
+ };
+ extraConfig = let
+ image_dirs = flatten (
+ mapAttrsToList (distro: revs:
+ mapAttrsToList (rev: archs:
+ mapAttrsToList (arch: image:
+ pkgs.runCommand "buildsrht-images" { } ''
+ mkdir -p $out/${distro}/${rev}/${arch}
+ ln -s ${image}/*.qcow2 $out/${distro}/${rev}/${arch}/root.img.qcow2
+ ''
+ ) archs
+ ) revs
+ ) cfg.builds.images
+ );
+ image_dir_pre = pkgs.symlinkJoin {
+ name = "builds.sr.ht-worker-images-pre";
+ paths = image_dirs;
+ # FIXME: not working, apparently because ubuntu/latest is a broken link
+ # ++ [ "${pkgs.sourcehut.buildsrht}/lib/images" ];
+ };
+ image_dir = pkgs.runCommand "builds.sr.ht-worker-images" { } ''
+ mkdir -p $out/images
+ cp -Lr ${image_dir_pre}/* $out/images
+ '';
+ in mkMerge [
+ {
+ users.users.${cfg.builds.user}.shell = pkgs.bash;
+
+ virtualisation.docker.enable = true;
+
+ services.sourcehut.settings = mkMerge [
+ { # Note that git.sr.ht::dispatch is not a typo,
+ # gitsrht-dispatch always use this section
+ "git.sr.ht::dispatch"."/usr/bin/buildsrht-keys" =
+ mkDefault "${cfg.builds.user}:${cfg.builds.group}";
+ }
+ (mkIf cfg.builds.enableWorker {
+ "builds.sr.ht::worker".shell = "/usr/bin/runner-shell";
+ "builds.sr.ht::worker".images = mkDefault "${image_dir}/images";
+ "builds.sr.ht::worker".controlcmd = mkDefault "${image_dir}/images/control";
+ })
+ ];
+ }
+ (mkIf cfg.builds.enableWorker {
+ users.groups = {
+ docker.members = [ cfg.builds.user ];
+ };
+ })
+ (mkIf (cfg.builds.enableWorker && cfg.nginx.enable) {
+ # Allow nginx access to buildlogs
+ users.users.${nginx.user}.extraGroups = [ cfg.builds.group ];
+ systemd.services.nginx = {
+ serviceConfig.BindReadOnlyPaths = [ cfg.settings."builds.sr.ht::worker".buildlogs ];
+ };
+ services.nginx.virtualHosts."logs.${domain}" = mkMerge [ {
+ /* FIXME: is a listen needed?
+ listen = with builtins;
+ # FIXME: not compatible with IPv6
+ let address = split ":" cfg.settings."builds.sr.ht::worker".name; in
+ [{ addr = elemAt address 0; port = lib.toInt (elemAt address 2); }];
+ */
+ locations."/logs/".alias = cfg.settings."builds.sr.ht::worker".buildlogs + "/";
+ } cfg.nginx.virtualHost ];
+ })
+ ];
+ })
+
+ (import ./service.nix "dispatch" {
+ inherit configIniOfService;
+ port = 5005;
+ })
+
+ (import ./service.nix "git" (let
+ baseService = {
+ path = [ cfg.git.package ];
+ serviceConfig.BindPaths = [ "${cfg.settings."git.sr.ht".repos}:/var/lib/sourcehut/gitsrht/repos" ];
+ };
+ in {
+ inherit configIniOfService;
+ mainService = mkMerge [ baseService {
+ serviceConfig.StateDirectory = [ "sourcehut/gitsrht" "sourcehut/gitsrht/repos" ];
+ preStart = mkIf (!versionAtLeast config.system.stateVersion "22.05") (mkBefore ''
+ # Fix Git hooks of repositories pre-dating https://github.com/NixOS/nixpkgs/pull/133984
+ (
+ set +f
+ shopt -s nullglob
+ for h in /var/lib/sourcehut/gitsrht/repos/~*/*/hooks/{pre-receive,update,post-update}
+ do ln -fnsv /usr/bin/gitsrht-update-hook "$h"; done
+ )
+ '');
+ } ];
+ port = 5001;
+ webhooks = true;
+ extraTimers.gitsrht-periodic = {
+ service = baseService;
+ timerConfig.OnCalendar = ["*:0/20"];
+ };
+ extraConfig = mkMerge [
+ {
+ # https://stackoverflow.com/questions/22314298/git-push-results-in-fatal-protocol-error-bad-line-length-character-this
+ # Probably could use gitsrht-shell if output is restricted to just parameters...
+ users.users.${cfg.git.user}.shell = pkgs.bash;
+ services.sourcehut.settings = {
+ "git.sr.ht::dispatch"."/usr/bin/gitsrht-keys" =
+ mkDefault "${cfg.git.user}:${cfg.git.group}";
+ };
+ systemd.services.sshd = baseService;
+ }
+ (mkIf cfg.nginx.enable {
+ services.nginx.virtualHosts."git.${domain}" = {
+ locations."/authorize" = {
+ proxyPass = "http://${cfg.listenAddress}:${toString cfg.git.port}";
+ extraConfig = ''
+ proxy_pass_request_body off;
+ proxy_set_header Content-Length "";
+ proxy_set_header X-Original-URI $request_uri;
+ '';
+ };
+ locations."~ ^/([^/]+)/([^/]+)/(HEAD|info/refs|objects/info/.*|git-upload-pack).*$" = {
+ root = "/var/lib/sourcehut/gitsrht/repos";
+ fastcgiParams = {
+ GIT_HTTP_EXPORT_ALL = "";
+ GIT_PROJECT_ROOT = "$document_root";
+ PATH_INFO = "$uri";
+ SCRIPT_FILENAME = "${cfg.git.package}/bin/git-http-backend";
+ };
+ extraConfig = ''
+ auth_request /authorize;
+ fastcgi_read_timeout 500s;
+ fastcgi_pass unix:/run/gitsrht-fcgiwrap.sock;
+ gzip off;
+ '';
+ };
+ };
+ systemd.sockets.gitsrht-fcgiwrap = {
+ before = [ "nginx.service" ];
+ wantedBy = [ "sockets.target" "gitsrht.service" ];
+ # This path remains accessible to nginx.service, which has no RootDirectory=
+ socketConfig.ListenStream = "/run/gitsrht-fcgiwrap.sock";
+ socketConfig.SocketUser = nginx.user;
+ socketConfig.SocketMode = "600";
+ };
+ })
+ ];
+ extraServices.gitsrht-fcgiwrap = mkIf cfg.nginx.enable {
+ serviceConfig = {
+ # Socket is passed by gitsrht-fcgiwrap.socket
+ ExecStart = "${pkgs.fcgiwrap}/sbin/fcgiwrap -c ${toString cfg.git.fcgiwrap.preforkProcess}";
+ # No need for config.ini
+ ExecStartPre = mkForce [];
+ User = null;
+ DynamicUser = true;
+ BindReadOnlyPaths = [ "${cfg.settings."git.sr.ht".repos}:/var/lib/sourcehut/gitsrht/repos" ];
+ IPAddressDeny = "any";
+ InaccessiblePaths = [ "-+/run/postgresql" "-+/run/redis-sourcehut" ];
+ PrivateNetwork = true;
+ RestrictAddressFamilies = mkForce [ "none" ];
+ SystemCallFilter = mkForce [
+ "@system-service"
+ "~@aio" "~@keyring" "~@memlock" "~@privileged" "~@resources" "~@setuid"
+ # @timer is needed for alarm()
+ ];
+ };
+ };
+ }))
+
+ (import ./service.nix "hg" (let
+ baseService = {
+ path = [ cfg.hg.package ];
+ serviceConfig.BindPaths = [ "${cfg.settings."hg.sr.ht".repos}:/var/lib/sourcehut/hgsrht/repos" ];
+ };
+ in {
+ inherit configIniOfService;
+ mainService = mkMerge [ baseService {
+ serviceConfig.StateDirectory = [ "sourcehut/hgsrht" "sourcehut/hgsrht/repos" ];
+ } ];
+ port = 5010;
+ webhooks = true;
+ extraTimers.hgsrht-periodic = {
+ service = baseService;
+ timerConfig.OnCalendar = ["*:0/20"];
+ };
+ extraTimers.hgsrht-clonebundles = mkIf cfg.hg.cloneBundles {
+ service = baseService;
+ timerConfig.OnCalendar = ["daily"];
+ timerConfig.AccuracySec = "1h";
+ };
+ extraConfig = mkMerge [
+ {
+ users.users.${cfg.hg.user}.shell = pkgs.bash;
+ services.sourcehut.settings = {
+ # Note that git.sr.ht::dispatch is not a typo,
+ # gitsrht-dispatch always uses this section.
+ "git.sr.ht::dispatch"."/usr/bin/hgsrht-keys" =
+ mkDefault "${cfg.hg.user}:${cfg.hg.group}";
+ };
+ systemd.services.sshd = baseService;
+ }
+ (mkIf cfg.nginx.enable {
+ # Allow nginx access to repositories
+ users.users.${nginx.user}.extraGroups = [ cfg.hg.group ];
+ services.nginx.virtualHosts."hg.${domain}" = {
+ locations."/authorize" = {
+ proxyPass = "http://${cfg.listenAddress}:${toString cfg.hg.port}";
+ extraConfig = ''
+ proxy_pass_request_body off;
+ proxy_set_header Content-Length "";
+ proxy_set_header X-Original-URI $request_uri;
+ '';
+ };
+ # Let clients reach pull bundles. We don't really need to lock this down even for
+ # private repos because the bundles are named after the revision hashes...
+ # so someone would need to know or guess a SHA value to download anything.
+ # TODO: proxyPass to an hg serve service?
+ locations."~ ^/[~^][a-z0-9_]+/[a-zA-Z0-9_.-]+/\\.hg/bundles/.*$" = {
+ root = "/var/lib/nginx/hgsrht/repos";
+ extraConfig = ''
+ auth_request /authorize;
+ gzip off;
+ '';
+ };
+ };
+ systemd.services.nginx = {
+ serviceConfig.BindReadOnlyPaths = [ "${cfg.settings."hg.sr.ht".repos}:/var/lib/nginx/hgsrht/repos" ];
+ };
+ })
+ ];
+ }))
+
+ (import ./service.nix "hub" {
+ inherit configIniOfService;
+ port = 5014;
+ extraConfig = {
+ services.nginx = mkIf cfg.nginx.enable {
+ virtualHosts."hub.${domain}" = mkMerge [ {
+ serverAliases = [ domain ];
+ } cfg.nginx.virtualHost ];
+ };
+ };
+ })
+
+ (import ./service.nix "lists" (let
+ srvsrht = "listssrht";
+ in {
+ inherit configIniOfService;
+ port = 5006;
+ webhooks = true;
+ # Receive the mail from Postfix and enqueue them into Redis and PostgreSQL
+ extraServices.listssrht-lmtp = {
+ wants = [ "postfix.service" ];
+ unitConfig.JoinsNamespaceOf = optional cfg.postfix.enable "postfix.service";
+ serviceConfig.ExecStart = "${cfg.python}/bin/listssrht-lmtp";
+ # Avoid crashing: os.chown(sock, os.getuid(), sock_gid)
+ serviceConfig.PrivateUsers = mkForce false;
+ };
+ # Dequeue the mails from Redis and dispatch them
+ extraServices.listssrht-process = {
+ serviceConfig = {
+ preStart = ''
+ cp ${pkgs.writeText "${srvsrht}-webhooks-celeryconfig.py" cfg.lists.process.celeryConfig} \
+ /run/sourcehut/${srvsrht}-webhooks/celeryconfig.py
+ '';
+ ExecStart = "${cfg.python}/bin/celery --app listssrht.process worker --hostname listssrht-process@%%h " + concatStringsSep " " cfg.lists.process.extraArgs;
+ # Avoid crashing: os.getloadavg()
+ ProcSubset = mkForce "all";
+ };
+ };
+ extraConfig = mkIf cfg.postfix.enable {
+ users.groups.${postfix.group}.members = [ cfg.lists.user ];
+ services.sourcehut.settings."lists.sr.ht::mail".sock-group = postfix.group;
+ services.postfix = {
+ destination = [ "lists.${domain}" ];
+ # FIXME: an accurate recipient list should be queried
+ # from the lists.sr.ht PostgreSQL database to avoid backscattering.
+ # But usernames are unfortunately not in that database but in meta.sr.ht.
+ # Note that two syntaxes are allowed:
+ # - ~username/list-name@lists.${domain}
+ # - u.username.list-name@lists.${domain}
+ localRecipients = [ "@lists.${domain}" ];
+ transport = ''
+ lists.${domain} lmtp:unix:${cfg.settings."lists.sr.ht::worker".sock}
+ '';
+ };
+ };
+ }))
+
+ (import ./service.nix "man" {
+ inherit configIniOfService;
+ port = 5004;
+ })
+
+ (import ./service.nix "meta" {
+ inherit configIniOfService;
+ port = 5000;
+ webhooks = true;
+ extraServices.metasrht-api = {
+ serviceConfig.Restart = "always";
+ serviceConfig.RestartSec = "2s";
+ preStart = "set -x\n" + concatStringsSep "\n\n" (attrValues (mapAttrs (k: s:
+ let srvMatch = builtins.match "^([a-z]*)\\.sr\\.ht$" k;
+ srv = head srvMatch;
+ in
+ # Configure client(s) as "preauthorized"
+ optionalString (srvMatch != null && cfg.${srv}.enable && ((s.oauth-client-id or null) != null)) ''
+ # Configure ${srv}'s OAuth client as "preauthorized"
+ ${postgresql.package}/bin/psql '${cfg.settings."meta.sr.ht".connection-string}' \
+ -c "UPDATE oauthclient SET preauthorized = true WHERE client_id = '${s.oauth-client-id}'"
+ ''
+ ) cfg.settings));
+ serviceConfig.ExecStart = "${pkgs.sourcehut.metasrht}/bin/metasrht-api -b ${cfg.listenAddress}:${toString (cfg.meta.port + 100)}";
+ };
+ extraTimers.metasrht-daily.timerConfig = {
+ OnCalendar = ["daily"];
+ AccuracySec = "1h";
+ };
+ extraConfig = mkMerge [
+ {
+ assertions = [
+ { assertion = let s = cfg.settings."meta.sr.ht::billing"; in
+ s.enabled == "yes" -> (s.stripe-public-key != null && s.stripe-secret-key != null);
+ message = "If meta.sr.ht::billing is enabled, the keys must be defined.";
+ }
+ ];
+ environment.systemPackages = optional cfg.meta.enable
+ (pkgs.writeShellScriptBin "metasrht-manageuser" ''
+ set -eux
+ if test "$(${pkgs.coreutils}/bin/id -n -u)" != '${cfg.meta.user}'
+ then exec sudo -u '${cfg.meta.user}' "$0" "$@"
+ else
+ # In order to load config.ini
+ if cd /run/sourcehut/metasrht
+ then exec ${cfg.python}/bin/metasrht-manageuser "$@"
+ else cat <${stateDir}/db
+ fi
+
+ ${optionalString cfg.settings.${iniKey}.migrate-on-upgrade ''
+ # Just try all the migrations because they're not linked to the version
+ for sql in ${pkgs.sourcehut.pagessrht}/share/sql/migrations/*.sql; do
+ ${postgresql.package}/bin/psql '${cfg.settings.${iniKey}.connection-string}' -f "$sql" || true
+ done
+ ''}
+
+ # Disable webhook
+ touch ${stateDir}/webhook
+ '';
+ serviceConfig = {
+ ExecStart = mkForce "${pkgs.sourcehut.pagessrht}/bin/pages.sr.ht -b ${cfg.listenAddress}:${toString cfg.pages.port}";
+ };
+ };
+ })
+
+ (import ./service.nix "paste" {
+ inherit configIniOfService;
+ port = 5011;
+ })
+
+ (import ./service.nix "todo" {
+ inherit configIniOfService;
+ port = 5003;
+ webhooks = true;
+ extraServices.todosrht-lmtp = {
+ wants = [ "postfix.service" ];
+ unitConfig.JoinsNamespaceOf = optional cfg.postfix.enable "postfix.service";
+ serviceConfig.ExecStart = "${cfg.python}/bin/todosrht-lmtp";
+ # Avoid crashing: os.chown(sock, os.getuid(), sock_gid)
+ serviceConfig.PrivateUsers = mkForce false;
+ };
+ extraConfig = mkIf cfg.postfix.enable {
+ users.groups.${postfix.group}.members = [ cfg.todo.user ];
+ services.sourcehut.settings."todo.sr.ht::mail".sock-group = postfix.group;
+ services.postfix = {
+ destination = [ "todo.${domain}" ];
+ # FIXME: an accurate recipient list should be queried
+ # from the todo.sr.ht PostgreSQL database to avoid backscattering.
+ # But usernames are unfortunately not in that database but in meta.sr.ht.
+ # Note that two syntaxes are allowed:
+ # - ~username/tracker-name@todo.${domain}
+ # - u.username.tracker-name@todo.${domain}
+ localRecipients = [ "@todo.${domain}" ];
+ transport = ''
+ todo.${domain} lmtp:unix:${cfg.settings."todo.sr.ht::mail".sock}
+ '';
+ };
+ };
+ })
+
+ (mkRenamedOptionModule [ "services" "sourcehut" "originBase" ]
+ [ "services" "sourcehut" "settings" "sr.ht" "global-domain" ])
+ (mkRenamedOptionModule [ "services" "sourcehut" "address" ]
+ [ "services" "sourcehut" "listenAddress" ])
+
+ ];
+
meta.doc = ./sourcehut.xml;
- meta.maintainers = with maintainers; [ tomberek ];
+ meta.maintainers = with maintainers; [ julm tomberek ];
}
diff --git a/nixos/modules/services/misc/sourcehut/service.nix b/nixos/modules/services/misc/sourcehut/service.nix
index 65b4ad020f9..f1706ad0a6a 100644
--- a/nixos/modules/services/misc/sourcehut/service.nix
+++ b/nixos/modules/services/misc/sourcehut/service.nix
@@ -1,66 +1,375 @@
-{ config, pkgs, lib }:
-serviceCfg: serviceDrv: iniKey: attrs:
+srv:
+{ configIniOfService
+, srvsrht ? "${srv}srht" # Because "buildsrht" does not follow that pattern (missing an "s").
+, iniKey ? "${srv}.sr.ht"
+, webhooks ? false
+, extraTimers ? {}
+, mainService ? {}
+, extraServices ? {}
+, extraConfig ? {}
+, port
+}:
+{ config, lib, pkgs, ... }:
+
+with lib;
let
+ inherit (config.services) postgresql;
+ redis = config.services.redis.servers."sourcehut-${srvsrht}";
+ inherit (config.users) users;
cfg = config.services.sourcehut;
- cfgIni = cfg.settings."${iniKey}";
- pgSuperUser = config.services.postgresql.superUser;
-
- setupDB = pkgs.writeScript "${serviceDrv.pname}-gen-db" ''
- #! ${cfg.python}/bin/python
- from ${serviceDrv.pname}.app import db
- db.create()
- '';
+ configIni = configIniOfService srv;
+ srvCfg = cfg.${srv};
+ baseService = serviceName: { allowStripe ? false }: extraService: let
+ runDir = "/run/sourcehut/${serviceName}";
+ rootDir = "/run/sourcehut/chroots/${serviceName}";
+ in
+ mkMerge [ extraService {
+ after = [ "network.target" ] ++
+ optional cfg.postgresql.enable "postgresql.service" ++
+ optional cfg.redis.enable "redis-sourcehut-${srvsrht}.service";
+ requires =
+ optional cfg.postgresql.enable "postgresql.service" ++
+ optional cfg.redis.enable "redis-sourcehut-${srvsrht}.service";
+ path = [ pkgs.gawk ];
+ environment.HOME = runDir;
+ serviceConfig = {
+ User = mkDefault srvCfg.user;
+ Group = mkDefault srvCfg.group;
+ RuntimeDirectory = [
+ "sourcehut/${serviceName}"
+ # Used by *srht-keys which reads ../config.ini
+ "sourcehut/${serviceName}/subdir"
+ "sourcehut/chroots/${serviceName}"
+ ];
+ RuntimeDirectoryMode = "2750";
+ # No need for the chroot path once inside the chroot
+ InaccessiblePaths = [ "-+${rootDir}" ];
+ # g+rx is for group members (eg. fcgiwrap or nginx)
+ # to read Git/Mercurial repositories, buildlogs, etc.
+ # o+x is for intermediate directories created by BindPaths= and like,
+ # as they're owned by root:root.
+ UMask = "0026";
+ RootDirectory = rootDir;
+ RootDirectoryStartOnly = true;
+ PrivateTmp = true;
+ MountAPIVFS = true;
+ # config.ini is looked up in there, before /etc/srht/config.ini
+ # Note that it fails to be set in ExecStartPre=
+ WorkingDirectory = mkDefault ("-"+runDir);
+ BindReadOnlyPaths = [
+ builtins.storeDir
+ "/etc"
+ "/run/booted-system"
+ "/run/current-system"
+ "/run/systemd"
+ ] ++
+ optional cfg.postgresql.enable "/run/postgresql" ++
+ optional cfg.redis.enable "/run/redis-sourcehut-${srvsrht}";
+ # LoadCredential= are unfortunately not available in ExecStartPre=
+ # Hence this one is run as root (the +) with RootDirectoryStartOnly=
+ # to reach credentials wherever they are.
+ # Note that each systemd service gets its own ${runDir}/config.ini file.
+ ExecStartPre = mkBefore [("+"+pkgs.writeShellScript "${serviceName}-credentials" ''
+ set -x
+ # Replace values begining with a '<' by the content of the file whose name is after.
+ gawk '{ if (match($0,/^([^=]+=)<(.+)/,m)) { getline f < m[2]; print m[1] f } else print $0 }' ${configIni} |
+ ${optionalString (!allowStripe) "gawk '!/^stripe-secret-key=/' |"}
+ install -o ${srvCfg.user} -g root -m 400 /dev/stdin ${runDir}/config.ini
+ '')];
+ # The following options are only for optimizing:
+ # systemd-analyze security
+ AmbientCapabilities = "";
+ CapabilityBoundingSet = "";
+ # ProtectClock= adds DeviceAllow=char-rtc r
+ DeviceAllow = "";
+ LockPersonality = true;
+ MemoryDenyWriteExecute = true;
+ NoNewPrivileges = true;
+ PrivateDevices = true;
+ PrivateMounts = true;
+ PrivateNetwork = mkDefault false;
+ PrivateUsers = true;
+ ProcSubset = "pid";
+ ProtectClock = true;
+ ProtectControlGroups = true;
+ ProtectHome = true;
+ ProtectHostname = true;
+ ProtectKernelLogs = true;
+ ProtectKernelModules = true;
+ ProtectKernelTunables = true;
+ ProtectProc = "invisible";
+ ProtectSystem = "strict";
+ RemoveIPC = true;
+ RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
+ RestrictNamespaces = true;
+ RestrictRealtime = true;
+ RestrictSUIDSGID = true;
+ #SocketBindAllow = [ "tcp:${toString srvCfg.port}" "tcp:${toString srvCfg.prometheusPort}" ];
+ #SocketBindDeny = "any";
+ SystemCallFilter = [
+ "@system-service"
+ "~@aio" "~@keyring" "~@memlock" "~@privileged" "~@resources" "~@timer"
+ "@chown" "@setuid"
+ ];
+ SystemCallArchitectures = "native";
+ };
+ } ];
in
-with serviceCfg; with lib; recursiveUpdate
{
- environment.HOME = statePath;
- path = [ config.services.postgresql.package ] ++ (attrs.path or [ ]);
- restartTriggers = [ config.environment.etc."sr.ht/config.ini".source ];
- serviceConfig = {
- Type = "simple";
- User = user;
- Group = user;
- Restart = "always";
- WorkingDirectory = statePath;
- } // (if (cfg.statePath == "/var/lib/sourcehut/${serviceDrv.pname}") then {
- StateDirectory = [ "sourcehut/${serviceDrv.pname}" ];
- } else {})
- ;
+ options.services.sourcehut.${srv} = {
+ enable = mkEnableOption "${srv} service";
- preStart = ''
- if ! test -e ${statePath}/db; then
- # Setup the initial database
- ${setupDB}
+ user = mkOption {
+ type = types.str;
+ default = srvsrht;
+ description = ''
+ User for ${srv}.sr.ht.
+ '';
+ };
- # Set the initial state of the database for future database upgrades
- if test -e ${cfg.python}/bin/${serviceDrv.pname}-migrate; then
- # Run alembic stamp head once to tell alembic the schema is up-to-date
- ${cfg.python}/bin/${serviceDrv.pname}-migrate stamp head
- fi
+ group = mkOption {
+ type = types.str;
+ default = srvsrht;
+ description = ''
+ Group for ${srv}.sr.ht.
+ Membership grants access to the Git/Mercurial repositories by default,
+ but not to the config.ini file (where secrets are).
+ '';
+ };
- printf "%s" "${serviceDrv.version}" > ${statePath}/db
- fi
+ port = mkOption {
+ type = types.port;
+ default = port;
+ description = ''
+ Port on which the "${srv}" backend should listen.
+ '';
+ };
- # Update copy of each users' profile to the latest
- # See https://lists.sr.ht/~sircmpwn/sr.ht-admins/<20190302181207.GA13778%40cirno.my.domain>
- if ! test -e ${statePath}/webhook; then
- # Update ${iniKey}'s users' profile copy to the latest
- ${cfg.python}/bin/srht-update-profiles ${iniKey}
+ redis = {
+ host = mkOption {
+ type = types.str;
+ default = "unix:/run/redis-sourcehut-${srvsrht}/redis.sock?db=0";
+ example = "redis://shared.wireguard:6379/0";
+ description = ''
+ The redis host URL. This is used for caching and temporary storage, and must
+ be shared between nodes (e.g. git1.sr.ht and git2.sr.ht), but need not be
+ shared between services. It may be shared between services, however, with no
+ ill effect, if this better suits your infrastructure.
+ '';
+ };
+ };
- touch ${statePath}/webhook
- fi
+ postgresql = {
+ database = mkOption {
+ type = types.str;
+ default = "${srv}.sr.ht";
+ description = ''
+ PostgreSQL database name for the ${srv}.sr.ht service,
+ used if is true.
+ '';
+ };
+ };
- ${optionalString (builtins.hasAttr "migrate-on-upgrade" cfgIni && cfgIni.migrate-on-upgrade == "yes") ''
- if [ "$(cat ${statePath}/db)" != "${serviceDrv.version}" ]; then
- # Manage schema migrations using alembic
- ${cfg.python}/bin/${serviceDrv.pname}-migrate -a upgrade head
+ gunicorn = {
+ extraArgs = mkOption {
+ type = with types; listOf str;
+ default = ["--timeout 120" "--workers 1" "--log-level=info"];
+ description = "Extra arguments passed to Gunicorn.";
+ };
+ };
+ } // optionalAttrs webhooks {
+ webhooks = {
+ extraArgs = mkOption {
+ type = with types; listOf str;
+ default = ["--loglevel DEBUG" "--pool eventlet" "--without-heartbeat"];
+ description = "Extra arguments passed to the Celery responsible for webhooks.";
+ };
+ celeryConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Content of the celeryconfig.py used by the Celery responsible for webhooks.";
+ };
+ };
+ };
- # Mark down current package version
- printf "%s" "${serviceDrv.version}" > ${statePath}/db
- fi
- ''}
+ config = lib.mkIf (cfg.enable && srvCfg.enable) (mkMerge [ extraConfig {
+ users = {
+ users = {
+ "${srvCfg.user}" = {
+ isSystemUser = true;
+ group = mkDefault srvCfg.group;
+ description = mkDefault "sourcehut user for ${srv}.sr.ht";
+ };
+ };
+ groups = {
+ "${srvCfg.group}" = { };
+ } // optionalAttrs (cfg.postgresql.enable
+ && hasSuffix "0" (postgresql.settings.unix_socket_permissions or "")) {
+ "postgres".members = [ srvCfg.user ];
+ } // optionalAttrs (cfg.redis.enable
+ && hasSuffix "0" (redis.settings.unixsocketperm or "")) {
+ "redis-sourcehut-${srvsrht}".members = [ srvCfg.user ];
+ };
+ };
- ${attrs.preStart or ""}
- '';
+ services.nginx = mkIf cfg.nginx.enable {
+ virtualHosts."${srv}.${cfg.settings."sr.ht".global-domain}" = mkMerge [ {
+ forceSSL = mkDefault true;
+ locations."/".proxyPass = "http://${cfg.listenAddress}:${toString srvCfg.port}";
+ locations."/static" = {
+ root = "${pkgs.sourcehut.${srvsrht}}/${pkgs.sourcehut.python.sitePackages}/${srvsrht}";
+ extraConfig = mkDefault ''
+ expires 30d;
+ '';
+ };
+ } cfg.nginx.virtualHost ];
+ };
+
+ services.postgresql = mkIf cfg.postgresql.enable {
+ authentication = ''
+ local ${srvCfg.postgresql.database} ${srvCfg.user} trust
+ '';
+ ensureDatabases = [ srvCfg.postgresql.database ];
+ ensureUsers = map (name: {
+ inherit name;
+ ensurePermissions = { "DATABASE \"${srvCfg.postgresql.database}\"" = "ALL PRIVILEGES"; };
+ }) [srvCfg.user];
+ };
+
+ services.sourcehut.services = mkDefault (filter (s: cfg.${s}.enable)
+ [ "builds" "dispatch" "git" "hg" "hub" "lists" "man" "meta" "pages" "paste" "todo" ]);
+
+ services.sourcehut.settings = mkMerge [
+ {
+ "${srv}.sr.ht".origin = mkDefault "https://${srv}.${cfg.settings."sr.ht".global-domain}";
+ }
+
+ (mkIf cfg.postgresql.enable {
+ "${srv}.sr.ht".connection-string = mkDefault "postgresql:///${srvCfg.postgresql.database}?user=${srvCfg.user}&host=/run/postgresql";
+ })
+ ];
+
+ services.redis.servers."sourcehut-${srvsrht}" = mkIf cfg.redis.enable {
+ enable = true;
+ databases = 3;
+ syslog = true;
+ # TODO: set a more informed value
+ save = mkDefault [ [1800 10] [300 100] ];
+ settings = {
+ # TODO: set a more informed value
+ maxmemory = "128MB";
+ maxmemory-policy = "volatile-ttl";
+ };
+ };
+
+ systemd.services = mkMerge [
+ {
+ "${srvsrht}" = baseService srvsrht { allowStripe = srv == "meta"; } (mkMerge [
+ {
+ description = "sourcehut ${srv}.sr.ht website service";
+ before = optional cfg.nginx.enable "nginx.service";
+ wants = optional cfg.nginx.enable "nginx.service";
+ wantedBy = [ "multi-user.target" ];
+ path = optional cfg.postgresql.enable postgresql.package;
+ # Beware: change in credentials' content will not trigger restart.
+ restartTriggers = [ configIni ];
+ serviceConfig = {
+ Type = "simple";
+ Restart = mkDefault "always";
+ #RestartSec = mkDefault "2min";
+ StateDirectory = [ "sourcehut/${srvsrht}" ];
+ StateDirectoryMode = "2750";
+ ExecStart = "${cfg.python}/bin/gunicorn ${srvsrht}.app:app --name ${srvsrht} --bind ${cfg.listenAddress}:${toString srvCfg.port} " + concatStringsSep " " srvCfg.gunicorn.extraArgs;
+ };
+ preStart = let
+ version = pkgs.sourcehut.${srvsrht}.version;
+ stateDir = "/var/lib/sourcehut/${srvsrht}";
+ in mkBefore ''
+ set -x
+ # Use the /run/sourcehut/${srvsrht}/config.ini
+ # installed by a previous ExecStartPre= in baseService
+ cd /run/sourcehut/${srvsrht}
+
+ if test ! -e ${stateDir}/db; then
+ # Setup the initial database.
+ # Note that it stamps the alembic head afterward
+ ${cfg.python}/bin/${srvsrht}-initdb
+ echo ${version} >${stateDir}/db
+ fi
+
+ ${optionalString cfg.settings.${iniKey}.migrate-on-upgrade ''
+ if [ "$(cat ${stateDir}/db)" != "${version}" ]; then
+ # Manage schema migrations using alembic
+ ${cfg.python}/bin/${srvsrht}-migrate -a upgrade head
+ echo ${version} >${stateDir}/db
+ fi
+ ''}
+
+ # Update copy of each users' profile to the latest
+ # See https://lists.sr.ht/~sircmpwn/sr.ht-admins/<20190302181207.GA13778%40cirno.my.domain>
+ if test ! -e ${stateDir}/webhook; then
+ # Update ${iniKey}'s users' profile copy to the latest
+ ${cfg.python}/bin/srht-update-profiles ${iniKey}
+ touch ${stateDir}/webhook
+ fi
+ '';
+ } mainService ]);
+ }
+
+ (mkIf webhooks {
+ "${srvsrht}-webhooks" = baseService "${srvsrht}-webhooks" {}
+ {
+ description = "sourcehut ${srv}.sr.ht webhooks service";
+ after = [ "${srvsrht}.service" ];
+ wantedBy = [ "${srvsrht}.service" ];
+ partOf = [ "${srvsrht}.service" ];
+ preStart = ''
+ cp ${pkgs.writeText "${srvsrht}-webhooks-celeryconfig.py" srvCfg.webhooks.celeryConfig} \
+ /run/sourcehut/${srvsrht}-webhooks/celeryconfig.py
+ '';
+ serviceConfig = {
+ Type = "simple";
+ Restart = "always";
+ ExecStart = "${cfg.python}/bin/celery --app ${srvsrht}.webhooks worker --hostname ${srvsrht}-webhooks@%%h " + concatStringsSep " " srvCfg.webhooks.extraArgs;
+ # Avoid crashing: os.getloadavg()
+ ProcSubset = mkForce "all";
+ };
+ };
+ })
+
+ (mapAttrs (timerName: timer: (baseService timerName {} (mkMerge [
+ {
+ description = "sourcehut ${timerName} service";
+ after = [ "network.target" "${srvsrht}.service" ];
+ serviceConfig = {
+ Type = "oneshot";
+ ExecStart = "${cfg.python}/bin/${timerName}";
+ };
+ }
+ (timer.service or {})
+ ]))) extraTimers)
+
+ (mapAttrs (serviceName: extraService: baseService serviceName {} (mkMerge [
+ {
+ description = "sourcehut ${serviceName} service";
+ # So that extraServices have the PostgreSQL database initialized.
+ after = [ "${srvsrht}.service" ];
+ wantedBy = [ "${srvsrht}.service" ];
+ partOf = [ "${srvsrht}.service" ];
+ serviceConfig = {
+ Type = "simple";
+ Restart = mkDefault "always";
+ };
+ }
+ extraService
+ ])) extraServices)
+ ];
+
+ systemd.timers = mapAttrs (timerName: timer:
+ {
+ description = "sourcehut timer for ${timerName}";
+ wantedBy = [ "timers.target" ];
+ inherit (timer) timerConfig;
+ }) extraTimers;
+ } ]);
}
- (builtins.removeAttrs attrs [ "path" "preStart" ])
diff --git a/nixos/modules/services/misc/sourcehut/sourcehut.xml b/nixos/modules/services/misc/sourcehut/sourcehut.xml
index ab9a8c6cb4b..41094f65a94 100644
--- a/nixos/modules/services/misc/sourcehut/sourcehut.xml
+++ b/nixos/modules/services/misc/sourcehut/sourcehut.xml
@@ -14,13 +14,12 @@
Basic usage
Sourcehut is a Python and Go based set of applications.
- services.sourcehut
- by default will use
+ This NixOS module also provides basic configuration integrating Sourcehut into locally running
services.nginx,
- services.redis,
- services.cron,
+ services.redis.servers.sourcehut,
+ services.postfix
and
- services.postgresql.
+ services.postgresql services.
@@ -42,18 +41,23 @@ in {
services.sourcehut = {
enable = true;
- originBase = fqdn;
- services = [ "meta" "man" "git" ];
+ git.enable = true;
+ man.enable = true;
+ meta.enable = true;
+ nginx.enable = true;
+ postfix.enable = true;
+ postgresql.enable = true;
+ redis.enable = true;
settings = {
"sr.ht" = {
environment = "production";
global-domain = fqdn;
origin = "https://${fqdn}";
# Produce keys with srht-keygen from sourcehut.coresrht.
- network-key = "SECRET";
- service-key = "SECRET";
+ network-key = "/run/keys/path/to/network-key";
+ service-key = "/run/keys/path/to/service-key";
};
- webhooks.private-key= "SECRET";
+ webhooks.private-key= "/run/keys/path/to/webhook-key";
};
};
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 96ea8fc9280..09cd26a7669 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -436,6 +436,7 @@ in
solanum = handleTest ./solanum.nix {};
solr = handleTest ./solr.nix {};
sonarr = handleTest ./sonarr.nix {};
+ sourcehut = handleTest ./sourcehut.nix {};
spacecookie = handleTest ./spacecookie.nix {};
spark = handleTestOn ["x86_64-linux"] ./spark {};
sslh = handleTest ./sslh.nix {};
diff --git a/nixos/tests/sourcehut.nix b/nixos/tests/sourcehut.nix
index b56a14ebf85..d1536c59322 100644
--- a/nixos/tests/sourcehut.nix
+++ b/nixos/tests/sourcehut.nix
@@ -1,29 +1,197 @@
-import ./make-test-python.nix ({ pkgs, ... }:
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+let
+ domain = "sourcehut.localdomain";
+ # Note that wildcard certificates just under the TLD (eg. *.com)
+ # would be rejected by clients like curl.
+ tls-cert = pkgs.runCommand "selfSignedCerts" { buildInputs = [ pkgs.openssl ]; } ''
+ openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -nodes -days 36500 \
+ -subj '/CN=${domain}' -extensions v3_req \
+ -addext 'subjectAltName = DNS:*.${domain}'
+ install -D -t $out key.pem cert.pem
+ '';
+
+ images = {
+ nixos.unstable.x86_64 =
+ let
+ systemConfig = { pkgs, ... }: {
+ # passwordless ssh server
+ services.openssh = {
+ enable = true;
+ permitRootLogin = "yes";
+ extraConfig = "PermitEmptyPasswords yes";
+ };
+
+ users = {
+ mutableUsers = false;
+ # build user
+ extraUsers."build" = {
+ isNormalUser = true;
+ uid = 1000;
+ extraGroups = [ "wheel" ];
+ password = "";
+ };
+ users.root.password = "";
+ };
+
+ security.sudo.wheelNeedsPassword = false;
+ nix.trustedUsers = [ "root" "build" ];
+ documentation.nixos.enable = false;
+
+ # builds.sr.ht-image-specific network settings
+ networking = {
+ hostName = "build";
+ dhcpcd.enable = false;
+ defaultGateway.address = "10.0.2.2";
+ usePredictableInterfaceNames = false;
+ interfaces."eth0".ipv4.addresses = [{
+ address = "10.0.2.15";
+ prefixLength = 25;
+ }];
+ enableIPv6 = false;
+ nameservers = [
+ # OpenNIC anycast
+ "185.121.177.177"
+ "169.239.202.202"
+ # Google
+ "8.8.8.8"
+ ];
+ firewall.allowedTCPPorts = [ 22 ];
+ };
+
+ environment.systemPackages = [
+ pkgs.gitMinimal
+ #pkgs.mercurial
+ pkgs.curl
+ pkgs.gnupg
+ ];
+ };
+ qemuConfig = { pkgs, ... }: {
+ imports = [ systemConfig ];
+ fileSystems."/".device = "/dev/disk/by-label/nixos";
+ boot.initrd.availableKernelModules = [
+ "ahci"
+ "ehci_pci"
+ "sd_mod"
+ "usb_storage"
+ "usbhid"
+ "virtio_balloon"
+ "virtio_blk"
+ "virtio_pci"
+ "virtio_ring"
+ "xhci_pci"
+ ];
+ boot.loader = {
+ grub = {
+ version = 2;
+ device = "/dev/vda";
+ };
+ timeout = 0;
+ };
+ };
+ config = (import (pkgs.path + "/nixos/lib/eval-config.nix") {
+ inherit pkgs; modules = [ qemuConfig ];
+ system = "x86_64-linux";
+ }).config;
+ in
+ import (pkgs.path + "/nixos/lib/make-disk-image.nix") {
+ inherit pkgs lib config;
+ diskSize = 16000;
+ format = "qcow2-compressed";
+ contents = [
+ { source = pkgs.writeText "gitconfig" ''
+ [user]
+ name = builds.sr.ht
+ email = build@sr.ht
+ '';
+ target = "/home/build/.gitconfig";
+ user = "build";
+ group = "users";
+ mode = "644";
+ }
+ ];
+ };
+ };
+
+in
{
name = "sourcehut";
meta.maintainers = [ pkgs.lib.maintainers.tomberek ];
- machine = { config, pkgs, ... }: {
- virtualisation.memorySize = 2048;
- networking.firewall.allowedTCPPorts = [ 80 ];
+ machine = { config, pkgs, nodes, ... }: {
+ # buildsrht needs space
+ virtualisation.diskSize = 4 * 1024;
+ virtualisation.memorySize = 2 * 1024;
+ networking.domain = domain;
+ networking.extraHosts = ''
+ ${config.networking.primaryIPAddress} meta.${domain}
+ ${config.networking.primaryIPAddress} builds.${domain}
+ '';
services.sourcehut = {
enable = true;
- services = [ "meta" ];
- originBase = "sourcehut";
- settings."sr.ht".service-key = "8888888888888888888888888888888888888888888888888888888888888888";
- settings."sr.ht".network-key = "0000000000000000000000000000000000000000000=";
- settings.webhooks.private-key = "0000000000000000000000000000000000000000000=";
+ services = [ "meta" "builds" ];
+ nginx.enable = true;
+ nginx.virtualHost = {
+ forceSSL = true;
+ sslCertificate = "${tls-cert}/cert.pem";
+ sslCertificateKey = "${tls-cert}/key.pem";
+ };
+ postgresql.enable = true;
+ redis.enable = true;
+
+ meta.enable = true;
+ builds = {
+ enable = true;
+ # FIXME: see why it does not seem to activate fully.
+ #enableWorker = true;
+ inherit images;
+ };
+ settings."sr.ht" = {
+ global-domain = config.networking.domain;
+ service-key = pkgs.writeText "service-key" "8b327279b77e32a3620e2fc9aabce491cc46e7d821fd6713b2a2e650ce114d01";
+ network-key = pkgs.writeText "network-key" "cEEmc30BRBGkgQZcHFksiG7hjc6_dK1XR2Oo5Jb9_nQ=";
+ };
+ settings."builds.sr.ht" = {
+ oauth-client-secret = pkgs.writeText "buildsrht-oauth-client-secret" "2260e9c4d9b8dcedcef642860e0504bc";
+ oauth-client-id = "299db9f9c2013170";
+ };
+ settings.webhooks.private-key = pkgs.writeText "webhook-key" "Ra3IjxgFiwG9jxgp4WALQIZw/BMYt30xWiOsqD0J7EA=";
+ };
+
+ networking.firewall.allowedTCPPorts = [ 443 ];
+ security.pki.certificateFiles = [ "${tls-cert}/cert.pem" ];
+ services.nginx = {
+ enable = true;
+ recommendedGzipSettings = true;
+ recommendedOptimisation = true;
+ recommendedTlsSettings = true;
+ recommendedProxySettings = true;
+ };
+
+ services.postgresql = {
+ enable = true;
+ enableTCPIP = false;
+ settings.unix_socket_permissions = "0770";
};
};
testScript = ''
start_all()
machine.wait_for_unit("multi-user.target")
+
+ # Testing metasrht
+ machine.wait_for_unit("metasrht-api.service")
machine.wait_for_unit("metasrht.service")
machine.wait_for_open_port(5000)
- machine.succeed("curl -sL http://localhost:5000 | grep meta.sourcehut")
+ machine.succeed("curl -sL http://localhost:5000 | grep meta.${domain}")
+ machine.succeed("curl -sL http://meta.${domain} | grep meta.${domain}")
+
+ # Testing buildsrht
+ machine.wait_for_unit("buildsrht.service")
+ machine.wait_for_open_port(5002)
+ machine.succeed("curl -sL http://localhost:5002 | grep builds.${domain}")
+ #machine.wait_for_unit("buildsrht-worker.service")
'';
})
diff --git a/pkgs/applications/version-management/sourcehut/builds.nix b/pkgs/applications/version-management/sourcehut/builds.nix
index c8163caf8ea..2e771de73a5 100644
--- a/pkgs/applications/version-management/sourcehut/builds.nix
+++ b/pkgs/applications/version-management/sourcehut/builds.nix
@@ -11,26 +11,31 @@
, python
}:
let
- version = "0.66.7";
-
- buildWorker = src: buildGoModule {
- inherit src version;
- pname = "builds-sr-ht-worker";
-
- vendorSha256 = "sha256-giOaldV46aBqXyFH/cQVsbUr6Rb4VMhbBO86o48tRZY=";
- };
-in
-buildPythonPackage rec {
- inherit version;
- pname = "buildsrht";
+ version = "0.74.17";
src = fetchFromSourcehut {
owner = "~sircmpwn";
repo = "builds.sr.ht";
rev = version;
- sha256 = "sha256-2MLs/DOXHjEYarXDVUcPZe3o0fmZbzVxn528SE72lhM=";
+ sha256 = "sha256-6Yc33lkhozpnx8e6yukUfo+/Qw5mwpJQQKuYbC7uqcU=";
};
+ buildWorker = src: buildGoModule {
+ inherit src version;
+ pname = "builds-sr-ht-worker";
+
+ vendorSha256 = "sha256-Pf1M9a43eK4jr6QMi6kRHA8DodXQU0pqq9ua5VC3ER0=";
+ };
+in
+buildPythonPackage rec {
+ inherit src version;
+ pname = "buildsrht";
+
+ patches = [
+ # Revert change breaking Unix socket support for Redis
+ patches/redis-socket/build/0001-Revert-Add-build-submission-and-queue-monitoring.patch
+ ];
+
nativeBuildInputs = srht.nativeBuildInputs;
propagatedBuildInputs = [
@@ -56,10 +61,12 @@ buildPythonPackage rec {
cp ${buildWorker "${src}/worker"}/bin/worker $out/bin/builds.sr.ht-worker
'';
+ pythonImportsCheck = [ "buildsrht" ];
+
meta = with lib; {
homepage = "https://git.sr.ht/~sircmpwn/builds.sr.ht";
description = "Continuous integration service for the sr.ht network";
- license = licenses.agpl3;
+ license = licenses.agpl3Only;
maintainers = with maintainers; [ eadwu ];
};
}
diff --git a/pkgs/applications/version-management/sourcehut/core.nix b/pkgs/applications/version-management/sourcehut/core.nix
index 7c3a516ed9d..3242b85e378 100644
--- a/pkgs/applications/version-management/sourcehut/core.nix
+++ b/pkgs/applications/version-management/sourcehut/core.nix
@@ -25,17 +25,16 @@
, sassc
, nodejs
, redis
-, writeText
}:
buildPythonPackage rec {
pname = "srht";
- version = "0.67.4";
+ version = "0.68.13";
src = fetchgit {
url = "https://git.sr.ht/~sircmpwn/core.sr.ht";
rev = version;
- sha256 = "sha256-XvzFfcBK5Mq8p7xEBAF/eupUE1kkUBh5k+ByM/WA9bc=";
+ sha256 = "sha256-LPyEfpNlmod18Fj16xpihKOrsU/hQUfAeOmWMmUeVPQ=";
fetchSubmodules = true;
};
@@ -46,6 +45,7 @@ buildPythonPackage rec {
};
patches = [
+ # Disable check for npm
./disable-npm-install.patch
];
@@ -87,6 +87,7 @@ buildPythonPackage rec {
'';
dontUseSetuptoolsCheck = true;
+ pythonImportsCheck = [ "srht" ];
meta = with lib; {
homepage = "https://git.sr.ht/~sircmpwn/srht";
diff --git a/pkgs/applications/version-management/sourcehut/default.nix b/pkgs/applications/version-management/sourcehut/default.nix
index 401a1437b7d..00810f208cc 100644
--- a/pkgs/applications/version-management/sourcehut/default.nix
+++ b/pkgs/applications/version-management/sourcehut/default.nix
@@ -22,6 +22,7 @@ let
listssrht = self.callPackage ./lists.nix { };
mansrht = self.callPackage ./man.nix { };
metasrht = self.callPackage ./meta.nix { };
+ pagessrht = self.callPackage ./pages.nix { };
pastesrht = self.callPackage ./paste.nix { };
todosrht = self.callPackage ./todo.nix { };
@@ -40,6 +41,7 @@ with python.pkgs; recurseIntoAttrs {
listssrht = toPythonApplication listssrht;
mansrht = toPythonApplication mansrht;
metasrht = toPythonApplication metasrht;
+ pagessrht = pagessrht;
pastesrht = toPythonApplication pastesrht;
todosrht = toPythonApplication todosrht;
}
diff --git a/pkgs/applications/version-management/sourcehut/dispatch.nix b/pkgs/applications/version-management/sourcehut/dispatch.nix
index 637c6f9c1df..18e105aea4b 100644
--- a/pkgs/applications/version-management/sourcehut/dispatch.nix
+++ b/pkgs/applications/version-management/sourcehut/dispatch.nix
@@ -9,13 +9,13 @@
buildPythonPackage rec {
pname = "dispatchsrht";
- version = "0.15.8";
+ version = "0.15.34";
src = fetchFromSourcehut {
owner = "~sircmpwn";
repo = "dispatch.sr.ht";
rev = version;
- sha256 = "sha256-zWCGPjIgMKHXHJUs9aciV7IFgo0rpahon6KXHDwcfss=";
+ sha256 = "sha256-bZ4ZKohMozZIyP0TUgxETOECib4XGUv29+Mg8ZsoMf8=";
};
nativeBuildInputs = srht.nativeBuildInputs;
@@ -31,10 +31,12 @@ buildPythonPackage rec {
export SRHT_PATH=${srht}/${python.sitePackages}/srht
'';
+ pythonImportsCheck = [ "dispatchsrht" ];
+
meta = with lib; {
homepage = "https://dispatch.sr.ht/~sircmpwn/dispatch.sr.ht";
description = "Task dispatcher and service integration tool for the sr.ht network";
- license = licenses.agpl3;
+ license = licenses.agpl3Only;
maintainers = with maintainers; [ eadwu ];
};
}
diff --git a/pkgs/applications/version-management/sourcehut/git.nix b/pkgs/applications/version-management/sourcehut/git.nix
index e44fb9cd6c6..e33bb659501 100644
--- a/pkgs/applications/version-management/sourcehut/git.nix
+++ b/pkgs/applications/version-management/sourcehut/git.nix
@@ -8,13 +8,13 @@
, scmsrht
}:
let
- version = "0.72.8";
+ version = "0.76.4";
src = fetchFromSourcehut {
owner = "~sircmpwn";
repo = "git.sr.ht";
rev = version;
- sha256 = "sha256-AB2uzajO5PtcpJfbOOTfuDFM6is5K39v3AZJ1hShRNc=";
+ sha256 = "sha256-diUkQpB/ivg8JTaoTcSyKr9Q9LZiMo6qVInBDPceklc=";
};
buildShell = src: buildGoModule {
@@ -32,13 +32,13 @@ let
buildKeys = src: buildGoModule {
inherit src version;
pname = "gitsrht-keys";
- vendorSha256 = "1d94cqy7x0q0agwg515xxsbl70b3qrzxbzsyjhn1pbyj532brn7f";
+ vendorSha256 = "sha256-9pojS69HCKVHUceyOpGtv9ewcxFD4WsOVsEzkmWJkF4=";
};
buildUpdateHook = src: buildGoModule {
inherit src version;
pname = "gitsrht-update-hook";
- vendorSha256 = "0fwzqpjv8x5y3w3bfjd0x0cvqjjak23m0zj88hf32jpw49xmjkih";
+ vendorSha256 = "sha256-sBlG7EFqdDm7CkAHVX50Mf4N3sl1rPNmWExG/bfbfGA=";
};
updateHook = buildUpdateHook "${src}/gitsrht-update-hook";
@@ -72,10 +72,12 @@ buildPythonPackage rec {
inherit updateHook;
};
+ pythonImportsCheck = [ "gitsrht" ];
+
meta = with lib; {
homepage = "https://git.sr.ht/~sircmpwn/git.sr.ht";
description = "Git repository hosting service for the sr.ht network";
- license = licenses.agpl3;
+ license = licenses.agpl3Only;
maintainers = with maintainers; [ eadwu ];
};
}
diff --git a/pkgs/applications/version-management/sourcehut/hg.nix b/pkgs/applications/version-management/sourcehut/hg.nix
index cddb76cabf2..6716f643a7e 100644
--- a/pkgs/applications/version-management/sourcehut/hg.nix
+++ b/pkgs/applications/version-management/sourcehut/hg.nix
@@ -10,12 +10,12 @@
buildPythonPackage rec {
pname = "hgsrht";
- version = "0.27.4";
+ version = "0.29.3";
src = fetchhg {
url = "https://hg.sr.ht/~sircmpwn/hg.sr.ht";
rev = version;
- sha256 = "1c0qfi0gmbfngvds6917fy9ii2iglawn429757rh7b4bvzn7n6mr";
+ sha256 = "y8gKaamwD5lsYqO1SkxMcn3E2TWidHAo2slvEU+8ovg=";
};
nativeBuildInputs = srht.nativeBuildInputs;
@@ -32,10 +32,12 @@ buildPythonPackage rec {
export SRHT_PATH=${srht}/${python.sitePackages}/srht
'';
+ pythonImportsCheck = [ "hgsrht" ];
+
meta = with lib; {
homepage = "https://git.sr.ht/~sircmpwn/hg.sr.ht";
description = "Mercurial repository hosting service for the sr.ht network";
- license = licenses.agpl3;
+ license = licenses.agpl3Only;
maintainers = with maintainers; [ eadwu ];
};
}
diff --git a/pkgs/applications/version-management/sourcehut/hub.nix b/pkgs/applications/version-management/sourcehut/hub.nix
index 17cb3fe4b61..511ec359fc4 100644
--- a/pkgs/applications/version-management/sourcehut/hub.nix
+++ b/pkgs/applications/version-management/sourcehut/hub.nix
@@ -6,13 +6,13 @@
buildPythonPackage rec {
pname = "hubsrht";
- version = "0.13.1";
+ version = "0.14.4";
src = fetchFromSourcehut {
owner = "~sircmpwn";
repo = "hub.sr.ht";
rev = version;
- sha256 = "sha256-Kqzy4mh5Nn1emzHBco/LVuXro/tW3NX+OYqdEwBSQ/U=";
+ sha256 = "sha256-7HF+jykWGqzPWA0YtJZQZU7pnID1yexcqLkEf2HpnSs=";
};
nativeBuildInputs = srht.nativeBuildInputs;
@@ -26,11 +26,12 @@ buildPythonPackage rec {
'';
dontUseSetuptoolsCheck = true;
+ pythonImportsCheck = [ "hubsrht" ];
meta = with lib; {
homepage = "https://git.sr.ht/~sircmpwn/hub.sr.ht";
description = "Project hub service for the sr.ht network";
- license = licenses.agpl3;
+ license = licenses.agpl3Only;
maintainers = with maintainers; [ eadwu ];
};
}
diff --git a/pkgs/applications/version-management/sourcehut/lists.nix b/pkgs/applications/version-management/sourcehut/lists.nix
index b419b49f7b5..3490796dda0 100644
--- a/pkgs/applications/version-management/sourcehut/lists.nix
+++ b/pkgs/applications/version-management/sourcehut/lists.nix
@@ -12,13 +12,13 @@
buildPythonPackage rec {
pname = "listssrht";
- version = "0.48.19";
+ version = "0.51.0";
src = fetchFromSourcehut {
owner = "~sircmpwn";
repo = "lists.sr.ht";
rev = version;
- sha256 = "sha256-bsakEMyvWaxiE4/SGcAP4mlGG9jkdHfFxpt9H+TJn/8=";
+ sha256 = "sha256-iywZ6G5E4AJevg/Q1LoB7JMJxBcsAnbhiND++mFy/bw=";
};
nativeBuildInputs = srht.nativeBuildInputs;
@@ -37,10 +37,12 @@ buildPythonPackage rec {
export SRHT_PATH=${srht}/${python.sitePackages}/srht
'';
+ pythonImportsCheck = [ "listssrht" ];
+
meta = with lib; {
homepage = "https://git.sr.ht/~sircmpwn/lists.sr.ht";
description = "Mailing list service for the sr.ht network";
- license = licenses.agpl3;
+ license = licenses.agpl3Only;
maintainers = with maintainers; [ eadwu ];
};
}
diff --git a/pkgs/applications/version-management/sourcehut/man.nix b/pkgs/applications/version-management/sourcehut/man.nix
index bd331f000a7..2d4d152e3aa 100644
--- a/pkgs/applications/version-management/sourcehut/man.nix
+++ b/pkgs/applications/version-management/sourcehut/man.nix
@@ -8,13 +8,13 @@
buildPythonPackage rec {
pname = "mansrht";
- version = "0.15.12";
+ version = "0.15.22";
src = fetchFromSourcehut {
owner = "~sircmpwn";
repo = "man.sr.ht";
rev = version;
- sha256 = "sha256-MqH/8K9XRvEg6P7GHE6XXtWnhDP3wT8iGoNaFtYQbio=";
+ sha256 = "sha256-curouf+eNCKprDI23blGs4AzJMry6zlCLDt/+0j5c8A=";
};
nativeBuildInputs = srht.nativeBuildInputs;
@@ -29,10 +29,12 @@ buildPythonPackage rec {
export SRHT_PATH=${srht}/${python.sitePackages}/srht
'';
+ pythonImportsCheck = [ "mansrht" ];
+
meta = with lib; {
homepage = "https://git.sr.ht/~sircmpwn/man.sr.ht";
description = "Wiki service for the sr.ht network";
- license = licenses.agpl3;
+ license = licenses.agpl3Only;
maintainers = with maintainers; [ eadwu ];
};
}
diff --git a/pkgs/applications/version-management/sourcehut/meta.nix b/pkgs/applications/version-management/sourcehut/meta.nix
index 86d293973d7..74a00692b6c 100644
--- a/pkgs/applications/version-management/sourcehut/meta.nix
+++ b/pkgs/applications/version-management/sourcehut/meta.nix
@@ -18,19 +18,19 @@
, python
}:
let
- version = "0.53.14";
+ version = "0.57.2";
src = fetchFromSourcehut {
owner = "~sircmpwn";
repo = "meta.sr.ht";
rev = version;
- sha256 = "sha256-/+r/XLDkcSTW647xPMh5bcJmR2xZNNH74AJ5jemna2k=";
+ sha256 = "sha256-+ksfAOuch/fLkFLYU52Ug0Hf0EoERy+oCwa9g+GKuAA=";
};
buildApi = src: buildGoModule {
inherit src version;
pname = "metasrht-api";
- vendorSha256 = "sha256-eZyDrr2VcNMxI++18qUy7LA1Q1YDlWCoRtl00L8lfR4=";
+ vendorSha256 = "sha256-vo+YbMyo/Eal7hbFxP9hwIW2cePJcGFszoDRCCzFYdM=";
};
in
@@ -38,6 +38,11 @@ buildPythonPackage rec {
pname = "metasrht";
inherit version src;
+ patches = [
+ # Revert change breaking Unix socket support for Redis
+ patches/redis-socket/meta/0001-Revert-Add-webhook-queue-monitoring.patch
+ ];
+
nativeBuildInputs = srht.nativeBuildInputs;
propagatedBuildInputs = [
@@ -66,10 +71,12 @@ buildPythonPackage rec {
cp ${buildApi "${src}/api/"}/bin/api $out/bin/metasrht-api
'';
+ pythonImportsCheck = [ "metasrht" ];
+
meta = with lib; {
homepage = "https://git.sr.ht/~sircmpwn/meta.sr.ht";
description = "Account management service for the sr.ht network";
- license = licenses.agpl3;
+ license = licenses.agpl3Only;
maintainers = with maintainers; [ eadwu ];
};
}
diff --git a/pkgs/applications/version-management/sourcehut/pages.nix b/pkgs/applications/version-management/sourcehut/pages.nix
new file mode 100644
index 00000000000..9b6011d9990
--- /dev/null
+++ b/pkgs/applications/version-management/sourcehut/pages.nix
@@ -0,0 +1,30 @@
+{ lib
+, fetchFromSourcehut
+, buildGoModule
+}:
+
+buildGoModule rec {
+ pname = "pagessrht";
+ version = "0.5.2";
+
+ src = fetchFromSourcehut {
+ owner = "~sircmpwn";
+ repo = "pages.sr.ht";
+ rev = version;
+ sha256 = "sha256-yEM122uhF0MNkMlNXyvBSfkLogRQETeuBl2K66kivac=";
+ };
+
+ vendorSha256 = "sha256-udr+1y5ApQCSPhs3yQTTi9QfzRbz0A9COYuFMjQGa74=";
+
+ postInstall = ''
+ mkdir -p $out/share/sql/
+ cp -r -t $out/share/sql/ schema.sql migrations
+ '';
+
+ meta = with lib; {
+ homepage = "https://git.sr.ht/~sircmpwn/pages.sr.ht";
+ description = "Web hosting service for the sr.ht network";
+ license = licenses.agpl3Only;
+ maintainers = with maintainers; [ eadwu ];
+ };
+}
diff --git a/pkgs/applications/version-management/sourcehut/paste.nix b/pkgs/applications/version-management/sourcehut/paste.nix
index 0d8c9135493..c411f8e8c95 100644
--- a/pkgs/applications/version-management/sourcehut/paste.nix
+++ b/pkgs/applications/version-management/sourcehut/paste.nix
@@ -8,13 +8,13 @@
buildPythonPackage rec {
pname = "pastesrht";
- version = "0.12.1";
+ version = "0.13.6";
src = fetchFromSourcehut {
owner = "~sircmpwn";
repo = "paste.sr.ht";
rev = version;
- sha256 = "sha256-QQhd2LeH9BLmlHilhsv+9fZ+RPNmEMSmOpFA3dsMBFc=";
+ sha256 = "sha256-Khcqk86iD9nxiKXN3+8mSLNoDau2qXNFOrLdkVu+rH8=";
};
nativeBuildInputs = srht.nativeBuildInputs;
@@ -29,10 +29,12 @@ buildPythonPackage rec {
export SRHT_PATH=${srht}/${python.sitePackages}/srht
'';
+ pythonImportsCheck = [ "pastesrht" ];
+
meta = with lib; {
homepage = "https://git.sr.ht/~sircmpwn/paste.sr.ht";
description = "Ad-hoc text file hosting service for the sr.ht network";
- license = licenses.agpl3;
+ license = licenses.agpl3Only;
maintainers = with maintainers; [ eadwu ];
};
}
diff --git a/pkgs/applications/version-management/sourcehut/patches/redis-socket/build/0001-Revert-Add-build-submission-and-queue-monitoring.patch b/pkgs/applications/version-management/sourcehut/patches/redis-socket/build/0001-Revert-Add-build-submission-and-queue-monitoring.patch
new file mode 100644
index 00000000000..ae8e95ddc0c
--- /dev/null
+++ b/pkgs/applications/version-management/sourcehut/patches/redis-socket/build/0001-Revert-Add-build-submission-and-queue-monitoring.patch
@@ -0,0 +1,69 @@
+From 069b03f85847ed4a9223183b62ee53f420838911 Mon Sep 17 00:00:00 2001
+From: Julien Moutinho
+Date: Thu, 16 Dec 2021 04:54:24 +0100
+Subject: [PATCH builds.sr.ht] Revert "Add build submission and queue
+ monitoring"
+
+This reverts commit 690f1aa16c77e418dc40109cd5e8fdf4a7ed947a.
+
+This has broken Unix socket support for Redis
+See https://lists.sr.ht/~sircmpwn/sr.ht-dev/%3C20211208082636.65665-1-me%40ignaskiela.eu%3E#%3C20211216033723.wefibfulfjhqnhem@sourcephile.fr%3E
+---
+ buildsrht/app.py | 3 ---
+ buildsrht/runner.py | 9 +--------
+ 2 files changed, 1 insertion(+), 11 deletions(-)
+
+diff --git a/buildsrht/app.py b/buildsrht/app.py
+index e5321a2..7c9977c 100644
+--- a/buildsrht/app.py
++++ b/buildsrht/app.py
+@@ -36,9 +36,6 @@ class BuildApp(SrhtFlask):
+ self.register_blueprint(secrets)
+ self.register_blueprint(gql_blueprint)
+
+- from buildsrht.runner import builds_queue_metrics_collector
+- self.metrics_registry.register(builds_queue_metrics_collector)
+-
+ @self.context_processor
+ def inject():
+ return {
+diff --git a/buildsrht/runner.py b/buildsrht/runner.py
+index 7773452..0389c8e 100644
+--- a/buildsrht/runner.py
++++ b/buildsrht/runner.py
+@@ -5,13 +5,10 @@ from srht.config import cfg
+ from srht.database import db
+ from srht.email import send_email
+ from srht.oauth import UserType
+-from srht.metrics import RedisQueueCollector
+-from prometheus_client import Counter
+
+ allow_free = cfg("builds.sr.ht", "allow-free", default="no") == "yes"
+
+-builds_broker = cfg("builds.sr.ht", "redis")
+-runner = Celery('builds', broker=builds_broker, config_source={
++runner = Celery('builds', broker=cfg("builds.sr.ht", "redis"), config_source={
+ "CELERY_TASK_SERIALIZER": "json",
+ "CELERY_ACCEPT_CONTENT": ["json"],
+ "CELERY_RESULT_SERIALIZER": "json",
+@@ -19,9 +16,6 @@ runner = Celery('builds', broker=builds_broker, config_source={
+ "CELERY_TASK_PROTOCOL": 1
+ })
+
+-builds_queue_metrics_collector = RedisQueueCollector(builds_broker, "buildsrht_builds", "Number of builds currently in queue")
+-builds_submitted = Counter("buildsrht_builds_submited", "Number of builds submitted")
+-
+ def queue_build(job, manifest):
+ from buildsrht.types import JobStatus
+ job.status = JobStatus.queued
+@@ -34,7 +28,6 @@ def queue_build(job, manifest):
+ cfg("sr.ht", "owner-email"),
+ "Cryptocurrency mining attempt on builds.sr.ht")
+ else:
+- builds_submitted.inc()
+ run_build.delay(job.id, manifest.to_dict())
+
+ def requires_payment(user):
+--
+2.34.0
+
diff --git a/pkgs/applications/version-management/sourcehut/patches/redis-socket/meta/0001-Revert-Add-webhook-queue-monitoring.patch b/pkgs/applications/version-management/sourcehut/patches/redis-socket/meta/0001-Revert-Add-webhook-queue-monitoring.patch
new file mode 100644
index 00000000000..9ec37670b06
--- /dev/null
+++ b/pkgs/applications/version-management/sourcehut/patches/redis-socket/meta/0001-Revert-Add-webhook-queue-monitoring.patch
@@ -0,0 +1,48 @@
+From d88bee195797c6c294320617ff14798da94cd0f3 Mon Sep 17 00:00:00 2001
+From: Julien Moutinho
+Date: Thu, 16 Dec 2021 04:52:08 +0100
+Subject: [PATCH meta.sr.ht] Revert "Add webhook queue monitoring"
+
+This reverts commit 9931df3c23094af5179df9ef019ca732b8125dac.
+
+This has broken Unix socket support for Redis.
+See https://lists.sr.ht/~sircmpwn/sr.ht-dev/%3C20211208082636.65665-1-me%40ignaskiela.eu%3E#%3C20211216033723.wefibfulfjhqnhem@sourcephile.fr%3E
+---
+ metasrht/app.py | 3 ---
+ metasrht/webhooks.py | 5 +----
+ 2 files changed, 1 insertion(+), 7 deletions(-)
+
+diff --git a/metasrht/app.py b/metasrht/app.py
+index b190875..89c59bc 100644
+--- a/metasrht/app.py
++++ b/metasrht/app.py
+@@ -49,9 +49,6 @@ class MetaApp(SrhtFlask):
+ from metasrht.blueprints.billing import billing
+ self.register_blueprint(billing)
+
+- from metasrht.webhooks import webhook_metrics_collector
+- self.metrics_registry.register(webhook_metrics_collector)
+-
+ @self.context_processor
+ def inject():
+ return {
+diff --git a/metasrht/webhooks.py b/metasrht/webhooks.py
+index 3e1149e..3f0ba01 100644
+--- a/metasrht/webhooks.py
++++ b/metasrht/webhooks.py
+@@ -7,11 +7,8 @@ if not hasattr(db, "session"):
+ db.init()
+ from srht.webhook import Event
+ from srht.webhook.celery import CeleryWebhook, make_worker
+-from srht.metrics import RedisQueueCollector
+
+-webhook_broker = cfg("meta.sr.ht", "webhooks", "redis://")
+-worker = make_worker(broker=webhook_broker)
+-webhook_metrics_collector = RedisQueueCollector(webhook_broker, "srht_webhooks", "Webhook queue length")
++worker = make_worker(broker=cfg("meta.sr.ht", "webhooks", "redis://"))
+
+ class UserWebhook(CeleryWebhook):
+ events = [
+--
+2.34.0
+
diff --git a/pkgs/applications/version-management/sourcehut/scm.nix b/pkgs/applications/version-management/sourcehut/scm.nix
index 1f385265360..9fbaa8c9245 100644
--- a/pkgs/applications/version-management/sourcehut/scm.nix
+++ b/pkgs/applications/version-management/sourcehut/scm.nix
@@ -5,18 +5,17 @@
, redis
, pyyaml
, buildsrht
-, writeText
}:
buildPythonPackage rec {
pname = "scmsrht";
- version = "0.22.9";
+ version = "0.22.16"; # Untagged version
src = fetchFromSourcehut {
owner = "~sircmpwn";
repo = "scm.sr.ht";
rev = version;
- sha256 = "sha256-327G6C8FW+iZx+167D7TQsFtV6FGc8MpMVo9L/cUUqU=";
+ sha256 = "sha256-A4Q7wUc4ag7KRWOkdYXCsbzuFHyJJsM15OjrCoVt9UQ=";
};
nativeBuildInputs = srht.nativeBuildInputs;
@@ -33,11 +32,12 @@ buildPythonPackage rec {
'';
dontUseSetuptoolsCheck = true;
+ pythonImportsCheck = [ "scmsrht" ];
meta = with lib; {
- homepage = "https://git.sr.ht/~sircmpwn/git.sr.ht";
+ homepage = "https://git.sr.ht/~sircmpwn/scm.sr.ht";
description = "Shared support code for sr.ht source control services.";
- license = licenses.agpl3;
+ license = licenses.agpl3Only;
maintainers = with maintainers; [ eadwu ];
};
}
diff --git a/pkgs/applications/version-management/sourcehut/todo.nix b/pkgs/applications/version-management/sourcehut/todo.nix
index 85e1f5637b6..da31ba24073 100644
--- a/pkgs/applications/version-management/sourcehut/todo.nix
+++ b/pkgs/applications/version-management/sourcehut/todo.nix
@@ -12,13 +12,13 @@
buildPythonPackage rec {
pname = "todosrht";
- version = "0.64.14";
+ version = "0.66.1";
src = fetchFromSourcehut {
owner = "~sircmpwn";
repo = "todo.sr.ht";
rev = version;
- sha256 = "sha256-huIAhn6h1F5w5ST4/yBwr82kAzyYwhLu+gpRuOQgnsE=";
+ sha256 = "sha256-P0xaQpK7O9zipGSIa5jL1O0L/fKt51EMNGt7XndYQ+g=";
};
nativeBuildInputs = srht.nativeBuildInputs;
@@ -42,11 +42,12 @@ buildPythonPackage rec {
];
dontUseSetuptoolsCheck = true;
+ pythonImportsCheck = [ "todosrht" ];
meta = with lib; {
homepage = "https://todo.sr.ht/~sircmpwn/todo.sr.ht";
description = "Ticket tracking service for the sr.ht network";
- license = licenses.agpl3;
+ license = licenses.agpl3Only;
maintainers = with maintainers; [ eadwu ];
};
}
diff --git a/pkgs/applications/version-management/sourcehut/update.sh b/pkgs/applications/version-management/sourcehut/update.sh
index 156d4cc35e4..97509397aef 100755
--- a/pkgs/applications/version-management/sourcehut/update.sh
+++ b/pkgs/applications/version-management/sourcehut/update.sh
@@ -1,8 +1,11 @@
#! /usr/bin/env nix-shell
#! nix-shell -i bash -p git mercurial common-updater-scripts
+set -eux -o pipefail
-cd "$(dirname "${BASH_SOURCE[0]}")"
+cd "$(dirname "${BASH_SOURCE[0]}")" || exit 1
root=../../../..
+tmp=$(mktemp -d)
+trap 'rm -rf "$tmp"' EXIT
default() {
(cd "$root" && nix-instantiate --eval --strict -A "sourcehut.python.pkgs.$1.meta.position" | sed -re 's/^"(.*):[0-9]+"$/\1/')
@@ -13,42 +16,61 @@ version() {
}
src_url() {
- (cd "$root" && nix-instantiate --eval --strict -A "sourcehut.python.pkgs.$1.src.drvAttrs.url" | tr -d '"')
+ nix-instantiate --eval --strict --expr " with import $root {}; let src = sourcehut.python.pkgs.$1.drvAttrs.src; in src.url or src.meta.homepage" | tr -d '"'
}
get_latest_version() {
src="$(src_url "$1")"
- tmp=$(mktemp -d)
-
+ rm -rf "$tmp"
if [ "$1" = "hgsrht" ]; then
- hg clone "$src" "$tmp" &> /dev/null
+ hg clone "$src" "$tmp" >/dev/null
printf "%s" "$(cd "$tmp" && hg log --limit 1 --template '{latesttag}')"
else
- git clone "$src" "$tmp"
- printf "%s" "$(cd "$tmp" && git describe $(git rev-list --tags --max-count=1))"
+ git clone "$src" "$tmp" >/dev/null
+ printf "%s" "$(cd "$tmp" && git describe "$(git rev-list --tags --max-count=1)")"
fi
}
update_version() {
default_nix="$(default "$1")"
- version_old="$(version "$1")"
+ oldVersion="$(version "$1")"
version="$(get_latest_version "$1")"
(cd "$root" && update-source-version "sourcehut.python.pkgs.$1" "$version")
- git add "$default_nix"
- git commit -m "$1: $version_old -> $version"
+ # Update vendorSha256 of Go modules
+ retry=true
+ while "$retry"; do
+ retry=false;
+ exec < <(exec nix -L build -f "$root" sourcehut.python.pkgs."$1" 2>&1)
+ while IFS=' :' read -r origin hash; do
+ case "$origin" in
+ (expected|specified) oldHash="$hash";;
+ (got) sed -i "s|$oldHash|$hash|" "$default_nix"; retry=true; break;;
+ (*) printf >&2 "%s\n" "$origin${hash:+:$hash}"
+ esac
+ done
+ done
+
+ if [ "$oldVersion" != "$version" ]; then
+ git add "$default_nix"
+ git commit -m "sourcehut.$1: $oldVersion -> $version"
+ fi
}
-services=( "srht" "buildsrht" "dispatchsrht" "gitsrht" "hgsrht" "hubsrht" "listssrht" "mansrht"
- "metasrht" "pastesrht" "todosrht" "scmsrht" )
-
-# Whether or not a specific service is requested
-if [ -n "$1" ]; then
- version="$(get_latest_version "$1")"
- (cd "$root" && update-source-version "sourcehut.python.pkgs.$1" "$version")
+if [ $# -gt 0 ]; then
+ services=("$@")
else
- for service in "${services[@]}"; do
- update_version "$service"
- done
+ # Beware that some packages must be updated before others,
+ # eg. buildsrht must be updated before gitsrht,
+ # otherwise this script would enter an infinite loop
+ # because the reported $oldHash to be changed
+ # may not actually be in $default_nix
+ # but in the file of one of its dependencies.
+ services=( "srht" "scmsrht" "buildsrht" "dispatchsrht" "gitsrht" "hgsrht" "hubsrht" "listssrht" "mansrht"
+ "metasrht" "pagessrht" "pastesrht" "todosrht" )
fi
+
+for service in "${services[@]}"; do
+ update_version "$service"
+done
diff --git a/pkgs/development/go-modules/generic/default.nix b/pkgs/development/go-modules/generic/default.nix
index 3b645f9ce8b..f00ca1984ec 100644
--- a/pkgs/development/go-modules/generic/default.nix
+++ b/pkgs/development/go-modules/generic/default.nix
@@ -71,6 +71,7 @@ let
inherit (go) GOOS GOARCH;
patches = args.patches or [];
+ patchFlags = args.patchFlags or [];
preBuild = args.preBuild or "";
sourceRoot = args.sourceRoot or "";