diff --git a/nixos/lib/make-options-doc/options-to-docbook.xsl b/nixos/lib/make-options-doc/options-to-docbook.xsl index 18d19fddaca..304698a51ad 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/databases/redis.nix b/nixos/modules/services/databases/redis.nix index 8873f6d00e0..e14bb89c75c 100644 --- a/nixos/modules/services/databases/redis.nix +++ b/nixos/modules/services/databases/redis.nix @@ -5,17 +5,18 @@ with lib; let cfg = config.services.redis; - ulimitNofile = cfg.maxclients + 32; - mkValueString = value: if value == true then "yes" else if value == false then "no" else generators.mkValueStringDefault { } value; - redisConfig = pkgs.writeText "redis.conf" (generators.toKeyValue { + redisConfig = settings: pkgs.writeText "redis.conf" (generators.toKeyValue { listsAsDuplicateKeys = true; mkKeyValue = generators.mkKeyValueDefault { inherit mkValueString; } " "; - } cfg.settings); + } settings); + + redisName = name: "redis" + optionalString (name != "") ("-"+name); + enabledServers = filterAttrs (name: conf: conf.enable) config.services.redis.servers; in { imports = [ @@ -25,6 +26,27 @@ in { (mkRemovedOptionModule [ "services" "redis" "appendOnlyFilename" ] "This option was never used.") (mkRemovedOptionModule [ "services" "redis" "pidFile" ] "This option was removed.") (mkRemovedOptionModule [ "services" "redis" "extraConfig" ] "Use services.redis.settings instead.") + (mkRenamedOptionModule [ "services" "redis" "enable"] [ "services" "redis" "servers" "" "enable" ]) + (mkRenamedOptionModule [ "services" "redis" "port"] [ "services" "redis" "servers" "" "port" ]) + (mkRenamedOptionModule [ "services" "redis" "openFirewall"] [ "services" "redis" "servers" "" "openFirewall" ]) + (mkRenamedOptionModule [ "services" "redis" "bind"] [ "services" "redis" "servers" "" "bind" ]) + (mkRenamedOptionModule [ "services" "redis" "unixSocket"] [ "services" "redis" "servers" "" "unixSocket" ]) + (mkRenamedOptionModule [ "services" "redis" "unixSocketPerm"] [ "services" "redis" "servers" "" "unixSocketPerm" ]) + (mkRenamedOptionModule [ "services" "redis" "logLevel"] [ "services" "redis" "servers" "" "logLevel" ]) + (mkRenamedOptionModule [ "services" "redis" "logfile"] [ "services" "redis" "servers" "" "logfile" ]) + (mkRenamedOptionModule [ "services" "redis" "syslog"] [ "services" "redis" "servers" "" "syslog" ]) + (mkRenamedOptionModule [ "services" "redis" "databases"] [ "services" "redis" "servers" "" "databases" ]) + (mkRenamedOptionModule [ "services" "redis" "maxclients"] [ "services" "redis" "servers" "" "maxclients" ]) + (mkRenamedOptionModule [ "services" "redis" "save"] [ "services" "redis" "servers" "" "save" ]) + (mkRenamedOptionModule [ "services" "redis" "slaveOf"] [ "services" "redis" "servers" "" "slaveOf" ]) + (mkRenamedOptionModule [ "services" "redis" "masterAuth"] [ "services" "redis" "servers" "" "masterAuth" ]) + (mkRenamedOptionModule [ "services" "redis" "requirePass"] [ "services" "redis" "servers" "" "requirePass" ]) + (mkRenamedOptionModule [ "services" "redis" "requirePassFile"] [ "services" "redis" "servers" "" "requirePassFile" ]) + (mkRenamedOptionModule [ "services" "redis" "appendOnly"] [ "services" "redis" "servers" "" "appendOnly" ]) + (mkRenamedOptionModule [ "services" "redis" "appendFsync"] [ "services" "redis" "servers" "" "appendFsync" ]) + (mkRenamedOptionModule [ "services" "redis" "slowLogLogSlowerThan"] [ "services" "redis" "servers" "" "slowLogLogSlowerThan" ]) + (mkRenamedOptionModule [ "services" "redis" "slowLogMaxLen"] [ "services" "redis" "servers" "" "slowLogMaxLen" ]) + (mkRenamedOptionModule [ "services" "redis" "settings"] [ "services" "redis" "servers" "" "settings" ]) ]; ###### interface @@ -32,18 +54,6 @@ in { options = { services.redis = { - - enable = mkOption { - type = types.bool; - default = false; - description = '' - Whether to enable the Redis server. Note that the NixOS module for - Redis disables kernel support for Transparent Huge Pages (THP), - because this features causes major performance problems for Redis, - e.g. (https://redis.io/topics/latency). - ''; - }; - package = mkOption { type = types.package; default = pkgs.redis; @@ -51,177 +61,227 @@ in { description = "Which Redis derivation to use."; }; - port = mkOption { - type = types.port; - default = 6379; - description = "The port for Redis to listen to."; - }; + vmOverCommit = mkEnableOption '' + setting of vm.overcommit_memory to 1 + (Suggested for Background Saving: http://redis.io/topics/faq) + ''; - vmOverCommit = mkOption { - type = types.bool; - default = false; - description = '' - Set vm.overcommit_memory to 1 (Suggested for Background Saving: http://redis.io/topics/faq) - ''; - }; - - openFirewall = mkOption { - type = types.bool; - default = false; - description = '' - Whether to open ports in the firewall for the server. - ''; - }; - - bind = mkOption { - type = with types; nullOr str; - default = "127.0.0.1"; - description = '' - The IP interface to bind to. - null means "all interfaces". - ''; - example = "192.0.2.1"; - }; - - unixSocket = mkOption { - type = with types; nullOr path; - default = null; - description = "The path to the socket to bind to."; - example = "/run/redis/redis.sock"; - }; - - unixSocketPerm = mkOption { - type = types.int; - default = 750; - description = "Change permissions for the socket"; - example = 700; - }; - - logLevel = mkOption { - type = types.str; - default = "notice"; # debug, verbose, notice, warning - example = "debug"; - description = "Specify the server verbosity level, options: debug, verbose, notice, warning."; - }; - - logfile = mkOption { - type = types.str; - default = "/dev/null"; - description = "Specify the log file name. Also 'stdout' can be used to force Redis to log on the standard output."; - example = "/var/log/redis.log"; - }; - - syslog = mkOption { - type = types.bool; - default = true; - description = "Enable logging to the system logger."; - }; - - databases = mkOption { - type = types.int; - default = 16; - description = "Set the number of databases."; - }; - - maxclients = mkOption { - type = types.int; - default = 10000; - description = "Set the max number of connected clients at the same time."; - }; - - save = mkOption { - type = with types; listOf (listOf int); - default = [ [900 1] [300 10] [60 10000] ]; - description = "The schedule in which data is persisted to disk, represented as a list of lists where the first element represent the amount of seconds and the second the number of changes."; - example = [ [900 1] [300 10] [60 10000] ]; - }; - - slaveOf = mkOption { - type = with types; nullOr (submodule ({ ... }: { + servers = mkOption { + type = with types; attrsOf (submodule ({config, name, ...}@args: { options = { - ip = mkOption { - type = str; - description = "IP of the Redis master"; - example = "192.168.1.100"; + enable = mkEnableOption '' + Redis server. + + Note that the NixOS module for Redis disables kernel support + for Transparent Huge Pages (THP), + because this features causes major performance problems for Redis, + e.g. (https://redis.io/topics/latency). + ''; + + user = mkOption { + type = types.str; + default = redisName name; + defaultText = "\"redis\" or \"redis-\${name}\" if name != \"\""; + description = "The username and groupname for redis-server."; }; port = mkOption { - type = port; - description = "port of the Redis master"; + type = types.port; default = 6379; + description = "The port for Redis to listen to."; + }; + + openFirewall = mkOption { + type = types.bool; + default = false; + description = '' + Whether to open ports in the firewall for the server. + ''; + }; + + bind = mkOption { + type = with types; nullOr str; + default = if name == "" then "127.0.0.1" else null; + defaultText = "127.0.0.1 or null if name != \"\""; + description = '' + The IP interface to bind to. + null means "all interfaces". + ''; + example = "192.0.2.1"; + }; + + unixSocket = mkOption { + type = with types; nullOr path; + default = "/run/${redisName name}/redis.sock"; + defaultText = "\"/run/redis/redis.sock\" or \"/run/redis-\${name}/redis.sock\" if name != \"\""; + description = "The path to the socket to bind to."; + }; + + unixSocketPerm = mkOption { + type = types.int; + default = 660; + description = "Change permissions for the socket"; + example = 600; + }; + + logLevel = mkOption { + type = types.str; + default = "notice"; # debug, verbose, notice, warning + example = "debug"; + description = "Specify the server verbosity level, options: debug, verbose, notice, warning."; + }; + + logfile = mkOption { + type = types.str; + default = "/dev/null"; + description = "Specify the log file name. Also 'stdout' can be used to force Redis to log on the standard output."; + example = "/var/log/redis.log"; + }; + + syslog = mkOption { + type = types.bool; + default = true; + description = "Enable logging to the system logger."; + }; + + databases = mkOption { + type = types.int; + default = 16; + description = "Set the number of databases."; + }; + + maxclients = mkOption { + type = types.int; + default = 10000; + description = "Set the max number of connected clients at the same time."; + }; + + save = mkOption { + type = with types; listOf (listOf int); + default = [ [900 1] [300 10] [60 10000] ]; + description = "The schedule in which data is persisted to disk, represented as a list of lists where the first element represent the amount of seconds and the second the number of changes."; + example = [ [900 1] [300 10] [60 10000] ]; + }; + + slaveOf = mkOption { + type = with types; nullOr (submodule ({ ... }: { + options = { + ip = mkOption { + type = str; + description = "IP of the Redis master"; + example = "192.168.1.100"; + }; + + port = mkOption { + type = port; + description = "port of the Redis master"; + default = 6379; + }; + }; + })); + + default = null; + description = "IP and port to which this redis instance acts as a slave."; + example = { ip = "192.168.1.100"; port = 6379; }; + }; + + masterAuth = mkOption { + type = with types; nullOr str; + default = null; + description = ''If the master is password protected (using the requirePass configuration) + it is possible to tell the slave to authenticate before starting the replication synchronization + process, otherwise the master will refuse the slave request. + (STORED PLAIN TEXT, WORLD-READABLE IN NIX STORE)''; + }; + + requirePass = mkOption { + type = with types; nullOr str; + default = null; + description = '' + Password for database (STORED PLAIN TEXT, WORLD-READABLE IN NIX STORE). + Use requirePassFile to store it outside of the nix store in a dedicated file. + ''; + example = "letmein!"; + }; + + requirePassFile = mkOption { + type = with types; nullOr path; + default = null; + description = "File with password for the database."; + example = "/run/keys/redis-password"; + }; + + appendOnly = mkOption { + type = types.bool; + default = false; + description = "By default data is only periodically persisted to disk, enable this option to use an append-only file for improved persistence."; + }; + + appendFsync = mkOption { + type = types.str; + default = "everysec"; # no, always, everysec + description = "How often to fsync the append-only log, options: no, always, everysec."; + }; + + slowLogLogSlowerThan = mkOption { + type = types.int; + default = 10000; + description = "Log queries whose execution take longer than X in milliseconds."; + example = 1000; + }; + + slowLogMaxLen = mkOption { + type = types.int; + default = 128; + description = "Maximum number of items to keep in slow log."; + }; + + settings = mkOption { + # TODO: this should be converted to freeformType + type = with types; attrsOf (oneOf [ bool int str (listOf str) ]); + default = {}; + description = '' + Redis configuration. Refer to + + for details on supported values. + ''; + example = literalExample '' + { + loadmodule = [ "/path/to/my_module.so" "/path/to/other_module.so" ]; + } + ''; }; }; + config.settings = mkMerge [ + { + port = config.port; + daemonize = false; + supervised = "systemd"; + loglevel = config.logLevel; + logfile = config.logfile; + syslog-enabled = config.syslog; + databases = config.databases; + maxclients = config.maxclients; + save = map (d: "${toString (builtins.elemAt d 0)} ${toString (builtins.elemAt d 1)}") config.save; + dbfilename = "dump.rdb"; + dir = "/var/lib/${redisName name}"; + appendOnly = config.appendOnly; + appendfsync = config.appendFsync; + slowlog-log-slower-than = config.slowLogLogSlowerThan; + slowlog-max-len = config.slowLogMaxLen; + } + (mkIf (config.bind != null) { bind = config.bind; }) + (mkIf (config.unixSocket != null) { + unixsocket = config.unixSocket; + unixsocketperm = toString config.unixSocketPerm; + }) + (mkIf (config.slaveOf != null) { slaveof = "${config.slaveOf.ip} ${toString config.slaveOf.port}"; }) + (mkIf (config.masterAuth != null) { masterauth = config.masterAuth; }) + (mkIf (config.requirePass != null) { requirepass = config.requirePass; }) + ]; })); - - default = null; - description = "IP and port to which this redis instance acts as a slave."; - example = { ip = "192.168.1.100"; port = 6379; }; - }; - - masterAuth = mkOption { - type = with types; nullOr str; - default = null; - description = ''If the master is password protected (using the requirePass configuration) - it is possible to tell the slave to authenticate before starting the replication synchronization - process, otherwise the master will refuse the slave request. - (STORED PLAIN TEXT, WORLD-READABLE IN NIX STORE)''; - }; - - requirePass = mkOption { - type = with types; nullOr str; - default = null; - description = '' - Password for database (STORED PLAIN TEXT, WORLD-READABLE IN NIX STORE). - Use requirePassFile to store it outside of the nix store in a dedicated file. - ''; - example = "letmein!"; - }; - - requirePassFile = mkOption { - type = with types; nullOr path; - default = null; - description = "File with password for the database."; - example = "/run/keys/redis-password"; - }; - - appendOnly = mkOption { - type = types.bool; - default = false; - description = "By default data is only periodically persisted to disk, enable this option to use an append-only file for improved persistence."; - }; - - appendFsync = mkOption { - type = types.str; - default = "everysec"; # no, always, everysec - description = "How often to fsync the append-only log, options: no, always, everysec."; - }; - - slowLogLogSlowerThan = mkOption { - type = types.int; - default = 10000; - description = "Log queries whose execution take longer than X in milliseconds."; - example = 1000; - }; - - slowLogMaxLen = mkOption { - type = types.int; - default = 128; - description = "Maximum number of items to keep in slow log."; - }; - - settings = mkOption { - type = with types; attrsOf (oneOf [ bool int str (listOf str) ]); + description = "Configuration of multiple redis-server instances."; default = {}; - description = '' - Redis configuration. Refer to - - for details on supported values. - ''; - example = literalExample '' - { - loadmodule = [ "/path/to/my_module.so" "/path/to/other_module.so" ]; - } - ''; }; }; @@ -230,77 +290,60 @@ in { ###### implementation - config = mkIf config.services.redis.enable { - assertions = [{ - assertion = cfg.requirePass != null -> cfg.requirePassFile == null; - message = "You can only set one services.redis.requirePass or services.redis.requirePassFile"; - }]; - boot.kernel.sysctl = (mkMerge [ + config = mkIf (enabledServers != {}) { + + assertions = attrValues (mapAttrs (name: conf: { + assertion = conf.requirePass != null -> conf.requirePassFile == null; + message = '' + You can only set one services.redis.servers.${name}.requirePass + or services.redis.servers.${name}.requirePassFile + ''; + }) enabledServers); + + boot.kernel.sysctl = mkMerge [ { "vm.nr_hugepages" = "0"; } ( mkIf cfg.vmOverCommit { "vm.overcommit_memory" = "1"; } ) - ]); + ]; - networking.firewall = mkIf cfg.openFirewall { - allowedTCPPorts = [ cfg.port ]; - }; - - users.users.redis = { - description = "Redis database user"; - isSystemUser = true; - }; - users.groups.redis = {}; + networking.firewall.allowedTCPPorts = concatMap (conf: + optional conf.openFirewall conf.port + ) (attrValues enabledServers); environment.systemPackages = [ cfg.package ]; - services.redis.settings = mkMerge [ - { - port = cfg.port; - daemonize = false; - supervised = "systemd"; - loglevel = cfg.logLevel; - logfile = cfg.logfile; - syslog-enabled = cfg.syslog; - databases = cfg.databases; - maxclients = cfg.maxclients; - save = map (d: "${toString (builtins.elemAt d 0)} ${toString (builtins.elemAt d 1)}") cfg.save; - dbfilename = "dump.rdb"; - dir = "/var/lib/redis"; - appendOnly = cfg.appendOnly; - appendfsync = cfg.appendFsync; - slowlog-log-slower-than = cfg.slowLogLogSlowerThan; - slowlog-max-len = cfg.slowLogMaxLen; - } - (mkIf (cfg.bind != null) { bind = cfg.bind; }) - (mkIf (cfg.unixSocket != null) { unixsocket = cfg.unixSocket; unixsocketperm = "${toString cfg.unixSocketPerm}"; }) - (mkIf (cfg.slaveOf != null) { slaveof = "${cfg.slaveOf.ip} ${toString cfg.slaveOf.port}"; }) - (mkIf (cfg.masterAuth != null) { masterauth = cfg.masterAuth; }) - (mkIf (cfg.requirePass != null) { requirepass = cfg.requirePass; }) - ]; + users.users = mapAttrs' (name: conf: nameValuePair (redisName name) { + description = "System user for the redis-server instance ${name}"; + isSystemUser = true; + }) enabledServers; + users.groups = mapAttrs' (name: conf: nameValuePair (redisName name) { + }) enabledServers; - systemd.services.redis = { - description = "Redis Server"; + systemd.services = mapAttrs' (name: conf: nameValuePair (redisName name) { + description = "Redis Server - ${redisName name}"; wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; - preStart = '' - install -m 600 ${redisConfig} /run/redis/redis.conf - '' + optionalString (cfg.requirePassFile != null) '' - password=$(cat ${escapeShellArg cfg.requirePassFile}) - echo "requirePass $password" >> /run/redis/redis.conf - ''; - serviceConfig = { - ExecStart = "${cfg.package}/bin/redis-server /run/redis/redis.conf"; + ExecStart = "${cfg.package}/bin/redis-server /run/${redisName name}/redis.conf"; + ExecStartPre = [("+"+pkgs.writeShellScript "${redisName name}-credentials" ('' + install -o '${conf.user}' -m 600 ${redisConfig conf.settings} /run/${redisName name}/redis.conf + '' + optionalString (conf.requirePassFile != null) '' + { + printf requirePass' ' + cat ${escapeShellArg conf.requirePassFile} + } >>/run/${redisName name}/redis.conf + '') + )]; Type = "notify"; # User and group - User = "redis"; - Group = "redis"; + User = conf.user; + Group = conf.user; # Runtime directory and mode - RuntimeDirectory = "redis"; + RuntimeDirectory = redisName name; RuntimeDirectoryMode = "0750"; # State directory and mode - StateDirectory = "redis"; + StateDirectory = redisName name; StateDirectoryMode = "0700"; # Access write directories UMask = "0077"; @@ -309,7 +352,7 @@ in { # Security NoNewPrivileges = true; # Process Properties - LimitNOFILE = "${toString ulimitNofile}"; + LimitNOFILE = mkDefault "${toString (conf.maxclients + 32)}"; # Sandboxing ProtectSystem = "strict"; ProtectHome = true; @@ -322,7 +365,9 @@ in { ProtectKernelModules = true; ProtectKernelTunables = true; ProtectControlGroups = true; - RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ]; + RestrictAddressFamilies = + optionals (conf.bind != null) ["AF_INET" "AF_INET6"] ++ + optional (conf.unixSocket != null) "AF_UNIX"; RestrictNamespaces = true; LockPersonality = true; MemoryDenyWriteExecute = true; @@ -333,6 +378,7 @@ in { SystemCallArchitectures = "native"; SystemCallFilter = "~@cpu-emulation @debug @keyring @memlock @mount @obsolete @privileged @resources @setuid"; }; - }; + }) enabledServers; + }; } diff --git a/nixos/modules/services/misc/sourcehut/builds.nix b/nixos/modules/services/misc/sourcehut/builds.nix deleted file mode 100644 index e446f08284f..00000000000 --- a/nixos/modules/services/misc/sourcehut/builds.nix +++ /dev/null @@ -1,234 +0,0 @@ -{ config, lib, pkgs, ... }: - -with lib; -let - cfg = config.services.sourcehut; - scfg = cfg.builds; - rcfg = config.services.redis; - iniKey = "builds.sr.ht"; - - drv = pkgs.sourcehut.buildsrht; -in -{ - options.services.sourcehut.builds = { - user = mkOption { - type = types.str; - default = "buildsrht"; - description = '' - User for builds.sr.ht. - ''; - }; - - port = mkOption { - type = types.port; - default = 5002; - description = '' - Port on which the "builds" module should listen. - ''; - }; - - database = mkOption { - type = types.str; - default = "builds.sr.ht"; - description = '' - PostgreSQL database name for builds.sr.ht. - ''; - }; - - statePath = mkOption { - type = types.path; - default = "${cfg.statePath}/buildsrht"; - description = '' - State path for builds.sr.ht. - ''; - }; - - enableWorker = mkOption { - type = types.bool; - default = false; - description = '' - Run workers for builds.sr.ht. - ''; - }; - - images = mkOption { - type = types.attrsOf (types.attrsOf (types.attrsOf types.package)); - default = { }; - example = lib.literalExample ''(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 = pkgs_unstable: (import ("${pkgs.sourcehut.buildsrht}/lib/images/nixos/image.nix") { - pkgs = (import pkgs_unstable {}); - }); - in - { - nixos.unstable.x86_64 = image_from_nixpkgs pkgs_unstable; - } - )''; - description = '' - Images for builds.sr.ht. Each package should be distro.release.arch and point to a /nix/store/package/root.img.qcow2. - ''; - }; - - }; - - config = with scfg; let - image_dirs = lib.lists.flatten ( - lib.attrsets.mapAttrsToList - (distro: revs: - lib.attrsets.mapAttrsToList - (rev: archs: - lib.attrsets.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) - scfg.images); - image_dir_pre = pkgs.symlinkJoin { - name = "builds.sr.ht-worker-images-pre"; - paths = image_dirs ++ [ - "${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 - lib.mkIf (cfg.enable && elem "builds" cfg.services) { - users = { - users = { - "${user}" = { - isSystemUser = true; - group = user; - extraGroups = lib.optionals cfg.builds.enableWorker [ "docker" ]; - description = "builds.sr.ht user"; - }; - }; - - groups = { - "${user}" = { }; - }; - }; - - services.postgresql = { - authentication = '' - local ${database} ${user} trust - ''; - ensureDatabases = [ database ]; - ensureUsers = [ - { - name = user; - ensurePermissions = { "DATABASE \"${database}\"" = "ALL PRIVILEGES"; }; - } - ]; - }; - - systemd = { - tmpfiles.rules = [ - "d ${statePath} 0755 ${user} ${user} -" - ] ++ (lib.optionals cfg.builds.enableWorker - [ "d ${statePath}/logs 0775 ${user} ${user} - -" ] - ); - - services = { - buildsrht = import ./service.nix { inherit config pkgs lib; } scfg drv iniKey - { - after = [ "postgresql.service" "network.target" ]; - requires = [ "postgresql.service" ]; - wantedBy = [ "multi-user.target" ]; - - description = "builds.sr.ht website service"; - - serviceConfig.ExecStart = "${cfg.python}/bin/gunicorn ${drv.pname}.app:app -b ${cfg.address}:${toString port}"; - - # Hack to bypass this hack: https://git.sr.ht/~sircmpwn/core.sr.ht/tree/master/item/srht-update-profiles#L6 - } // { preStart = " "; }; - - buildsrht-worker = { - enable = scfg.enableWorker; - after = [ "postgresql.service" "network.target" ]; - requires = [ "postgresql.service" ]; - wantedBy = [ "multi-user.target" ]; - partOf = [ "buildsrht.service" ]; - description = "builds.sr.ht worker service"; - path = [ pkgs.openssh pkgs.docker ]; - preStart = let qemuPackage = pkgs.qemu_kvm; - in '' - if [[ "$(docker images -q qemu:latest 2> /dev/null)" == "" || "$(cat ${statePath}/docker-image-qemu 2> /dev/null || true)" != "${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 - printf "%s" "${qemuPackage.version}" > ${statePath}/docker-image-qemu - fi - ''; - serviceConfig = { - Type = "simple"; - User = user; - Group = "nginx"; - Restart = "always"; - }; - serviceConfig.ExecStart = "${pkgs.sourcehut.buildsrht}/bin/builds.sr.ht-worker"; - }; - }; - }; - - services.sourcehut.settings = { - # URL builds.sr.ht is being served at (protocol://domain) - "builds.sr.ht".origin = mkDefault "http://builds.${cfg.originBase}"; - # Address and port to bind the debug server to - "builds.sr.ht".debug-host = mkDefault "0.0.0.0"; - "builds.sr.ht".debug-port = mkDefault port; - # Configures the SQLAlchemy connection string for the database. - "builds.sr.ht".connection-string = mkDefault "postgresql:///${database}?user=${user}&host=/var/run/postgresql"; - # Set to "yes" to automatically run migrations on package upgrade. - "builds.sr.ht".migrate-on-upgrade = mkDefault "yes"; - # builds.sr.ht's OAuth client ID and secret for meta.sr.ht - # Register your client at meta.example.org/oauth - "builds.sr.ht".oauth-client-id = mkDefault null; - "builds.sr.ht".oauth-client-secret = mkDefault null; - # The redis connection used for the celery worker - "builds.sr.ht".redis = mkDefault "redis://${rcfg.bind}:${toString rcfg.port}/3"; - # The shell used for ssh - "builds.sr.ht".shell = mkDefault "runner-shell"; - # Register the builds.sr.ht dispatcher - "git.sr.ht::dispatch".${builtins.unsafeDiscardStringContext "${pkgs.sourcehut.buildsrht}/bin/buildsrht-keys"} = mkDefault "${user}:${user}"; - - # Location for build logs, images, and control command - } // lib.attrsets.optionalAttrs scfg.enableWorker { - # Default worker stores logs that are accessible via this address:port - "builds.sr.ht::worker".name = mkDefault "127.0.0.1:5020"; - "builds.sr.ht::worker".buildlogs = mkDefault "${scfg.statePath}/logs"; - "builds.sr.ht::worker".images = mkDefault "${image_dir}/images"; - "builds.sr.ht::worker".controlcmd = mkDefault "${image_dir}/images/control"; - "builds.sr.ht::worker".timeout = mkDefault "3m"; - }; - - services.nginx.virtualHosts."logs.${cfg.originBase}" = - if scfg.enableWorker then { - listen = with builtins; let address = split ":" cfg.settings."builds.sr.ht::worker".name; - in [{ addr = elemAt address 0; port = lib.toInt (elemAt address 2); }]; - locations."/logs".root = "${scfg.statePath}"; - } else { }; - - services.nginx.virtualHosts."builds.${cfg.originBase}" = { - forceSSL = true; - locations."/".proxyPass = "http://${cfg.address}:${toString port}"; - locations."/query".proxyPass = "http://${cfg.address}:${toString (port + 100)}"; - locations."/static".root = "${pkgs.sourcehut.buildsrht}/${pkgs.sourcehut.python.sitePackages}/buildsrht"; - }; - }; -} diff --git a/nixos/modules/services/misc/sourcehut/default.nix b/nixos/modules/services/misc/sourcehut/default.nix index 9c812d6b043..c6c70e6ae2b 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/logs"; + "git.sr.ht".post-update-script = "/var/lib/sourcehut/gitsrht/bin/post-update-script"; + "git.sr.ht".repos = "/var/lib/sourcehut/gitsrht/repos"; + "hg.sr.ht".changegroup-script = "/var/lib/sourcehut/hgsrht/bin/changegroup-script"; + "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,69 +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}"; - 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 { @@ -94,105 +138,1222 @@ 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 = "unix+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"; + }; + 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"; + # Git hooks are run relative to their repository's directory, + # but gitsrht-update-hook looks up ../config.ini + apply = p: pkgs.writeShellScript "update-hook-wrapper" '' + test -e "''${PWD%/*}"/config.ini || + ln -s ${users."sshsrht".home}/../config.ini "''${PWD%/*}"/config.ini + exec -a "$0" '${p}' "$@" + ''; + }; + 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 = "unix+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"; + # Mercurial's changegroup hooks are run relative to their repository's directory, + # but hgsrht-hook-changegroup looks up ./config.ini + apply = p: pkgs.writeShellScript "hook-changegroup-wrapper" '' + test -e "''$PWD"/config.ini || + ln -s ${users."sshsrht".home}/../config.ini "''$PWD"/config.ini + exec -a "$0" '${p}' "$@" + ''; + }; + 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 = "unix+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 = "unix+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 = "unix+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 = "unix+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 = "unix+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."; - } - ]; + images = mkOption { + type = with types; attrsOf (attrsOf (attrsOf package)); + default = { }; + example = lib.literalExample ''(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. + ''; + }; + }; - 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); + git = { + package = mkOption { + type = types.package; + default = pkgs.git; + example = literalExample "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; + }; + }; - environment.systemPackages = [ pkgs.sourcehut.coresrht ]; + 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). + ''; + }; + }; - # 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"; - - 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 + # sshsrht-dispatch needs to read ${users."sshsrht".home}/../config.ini, + # 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/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/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 ${users."sshsrht".home} + ${cfg.python}/bin/gitsrht-dispatch "$@" + ''; + }; + systemd.services.sshd = let + # TODO: use a filtered config.ini containing only [git.sr.ht::dispatch] + configIni = settingsFormat.generate "sourcehut-dispatch-config.ini" + # Each service needs access to only a subset of sections (and secrets). + (filterAttrs (k: v: k == "git.sr.ht::dispatch") + cfg.settings); + in { + #path = optional cfg.git.enable [ cfg.git.package ]; + restartTriggers = [ configIni ]; + serviceConfig = { + RuntimeDirectory = [ "sourcehut/sshsrht/subdir" ]; + 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). + optionals cfg.builds.enable [ + "${pkgs.writeShellScript "buildsrht-keys-wrapper" '' + set -ex + cd /run/sourcehut/buildsrht/subdir + exec ${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 [ + "${pkgs.writeShellScript "gitsrht-keys-wrapper" '' + set -ex + cd /run/sourcehut/gitsrht/subdir + exec ${pkgs.sourcehut.gitsrht}/bin/gitsrht-keys "$@" + ''}:/usr/bin/gitsrht-keys" + "${pkgs.sourcehut.gitsrht}/bin/gitsrht-shell:/usr/bin/gitsrht-shell" + ] ++ + optionals cfg.hg.enable [ + "${pkgs.writeShellScript "hgsrht-keys-wrapper" '' + set -ex + cd /run/sourcehut/hgsrht/subdir + exec ${pkgs.sourcehut.hgsrht}/bin/hgsrht-keys "$@" + ''}:/usr/bin/hgsrht-keys" + "${pkgs.sourcehut.hgsrht}/bin/hgsrht-shell:/usr/bin/hgsrht-shell" + ]; + ExecStartPre = mkBefore [("+"+pkgs.writeShellScript "sshsrht-credentials" '' + # Replace values begining with a '<' by the content of the file whose name is after. + ${pkgs.gawk}/bin/gawk '{ if (match($0,/^([^=]+=)<(.+)/,m)) { getline f < m[2]; print m[1] f } else print $0 }' ${configIni} | + install -o ${users."sshsrht".name} -g ${groups."sshsrht".name} -m 440 \ + /dev/stdin ${users."sshsrht".home}/../config.ini + '')]; + }; + }; + users = { + users."sshsrht" = { + isSystemUser = true; + # srht-dispatch, *srht-keys, and *srht-shell + # look up in ../config.ini from this directory; + # that config.ini being set in *srht.service's ExecStartPre= + home = "/run/sourcehut/sshsrht/subdir"; + group = groups.nogroup.name; + description = "sourcehut user for sshd's AuthorizedKeysCommandUser"; + }; + groups."sshsrht" = {}; + }; + }) + ]); + + 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"; + RuntimeDirectory = [ "sourcehut/${serviceName}/subdir" ]; + # builds.sr.ht-worker looks up ../config.ini + LogsDirectory = [ "sourcehut/${serviceName}" ]; + StateDirectory = [ "sourcehut/${serviceName}" ]; + 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; + # Allow reading of ${users."sshsrht".home}/../config.ini + extraGroups = [ groups."sshsrht".name ]; + }; + + 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}:/var/log/nginx/buildsrht/logs" ]; + }; + 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 = "/var/log/nginx/buildsrht/logs/"; + } 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" ]; + serviceConfig.BindReadOnlyPaths = [ "${cfg.settings."git.sr.ht".post-update-script}:/var/lib/sourcehut/gitsrht/bin/post-update-script" ]; + }; + in { + inherit configIniOfService; + mainService = mkMerge [ baseService { + serviceConfig.StateDirectory = [ "sourcehut/gitsrht" "sourcehut/gitsrht/repos" ]; + } ]; + port = 5001; + webhooks = true; + extraTimers.gitsrht-periodic = { + service = baseService; + timerConfig.OnCalendar = ["20min"]; + }; + extraConfig = mkMerge [ + { + users.users.${cfg.git.user} = { + # 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... + shell = pkgs.bash; + # Allow reading of ${users."sshsrht".home}/../config.ini + extraGroups = [ groups."sshsrht".name ]; + home = users.sshsrht.home; + }; + 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" ]; + serviceConfig.BindReadOnlyPaths = [ "${cfg.settings."ht.sr.ht".changegroup-script}:/var/lib/sourcehut/hgsrht/bin/changegroup-script" ]; + }; + in { + inherit configIniOfService; + mainService = mkMerge [ baseService { + serviceConfig.StateDirectory = [ "sourcehut/hgsrht" "sourcehut/hgsrht/repos" ]; + } ]; + port = 5010; + webhooks = true; + extraTimers.hgsrht-periodic = { + service = baseService; + timerConfig.OnCalendar = ["20min"]; + }; + 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; + # Allow reading of ${users."sshsrht".home}/../config.ini + extraGroups = [ groups."sshsrht".name ]; + }; + 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.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/dispatch.nix b/nixos/modules/services/misc/sourcehut/dispatch.nix deleted file mode 100644 index a9db17bebe8..00000000000 --- a/nixos/modules/services/misc/sourcehut/dispatch.nix +++ /dev/null @@ -1,125 +0,0 @@ -{ config, lib, pkgs, ... }: - -with lib; -let - cfg = config.services.sourcehut; - cfgIni = cfg.settings; - scfg = cfg.dispatch; - iniKey = "dispatch.sr.ht"; - - drv = pkgs.sourcehut.dispatchsrht; -in -{ - options.services.sourcehut.dispatch = { - user = mkOption { - type = types.str; - default = "dispatchsrht"; - description = '' - User for dispatch.sr.ht. - ''; - }; - - port = mkOption { - type = types.port; - default = 5005; - description = '' - Port on which the "dispatch" module should listen. - ''; - }; - - database = mkOption { - type = types.str; - default = "dispatch.sr.ht"; - description = '' - PostgreSQL database name for dispatch.sr.ht. - ''; - }; - - statePath = mkOption { - type = types.path; - default = "${cfg.statePath}/dispatchsrht"; - description = '' - State path for dispatch.sr.ht. - ''; - }; - }; - - config = with scfg; lib.mkIf (cfg.enable && elem "dispatch" cfg.services) { - - users = { - users = { - "${user}" = { - isSystemUser = true; - group = user; - description = "dispatch.sr.ht user"; - }; - }; - - groups = { - "${user}" = { }; - }; - }; - - services.postgresql = { - authentication = '' - local ${database} ${user} trust - ''; - ensureDatabases = [ database ]; - ensureUsers = [ - { - name = user; - ensurePermissions = { "DATABASE \"${database}\"" = "ALL PRIVILEGES"; }; - } - ]; - }; - - systemd = { - tmpfiles.rules = [ - "d ${statePath} 0750 ${user} ${user} -" - ]; - - services.dispatchsrht = import ./service.nix { inherit config pkgs lib; } scfg drv iniKey { - after = [ "postgresql.service" "network.target" ]; - requires = [ "postgresql.service" ]; - wantedBy = [ "multi-user.target" ]; - - description = "dispatch.sr.ht website service"; - - serviceConfig.ExecStart = "${cfg.python}/bin/gunicorn ${drv.pname}.app:app -b ${cfg.address}:${toString port}"; - }; - }; - - services.sourcehut.settings = { - # URL dispatch.sr.ht is being served at (protocol://domain) - "dispatch.sr.ht".origin = mkDefault "http://dispatch.${cfg.originBase}"; - # Address and port to bind the debug server to - "dispatch.sr.ht".debug-host = mkDefault "0.0.0.0"; - "dispatch.sr.ht".debug-port = mkDefault port; - # Configures the SQLAlchemy connection string for the database. - "dispatch.sr.ht".connection-string = mkDefault "postgresql:///${database}?user=${user}&host=/var/run/postgresql"; - # Set to "yes" to automatically run migrations on package upgrade. - "dispatch.sr.ht".migrate-on-upgrade = mkDefault "yes"; - # dispatch.sr.ht's OAuth client ID and secret for meta.sr.ht - # Register your client at meta.example.org/oauth - "dispatch.sr.ht".oauth-client-id = mkDefault null; - "dispatch.sr.ht".oauth-client-secret = mkDefault null; - - # Github Integration - "dispatch.sr.ht::github".oauth-client-id = mkDefault null; - "dispatch.sr.ht::github".oauth-client-secret = mkDefault null; - - # Gitlab Integration - "dispatch.sr.ht::gitlab".enabled = mkDefault null; - "dispatch.sr.ht::gitlab".canonical-upstream = mkDefault "gitlab.com"; - "dispatch.sr.ht::gitlab".repo-cache = mkDefault "./repo-cache"; - # "dispatch.sr.ht::gitlab"."gitlab.com" = mkDefault "GitLab:application id:secret"; - }; - - services.nginx.virtualHosts."dispatch.${cfg.originBase}" = { - forceSSL = true; - locations."/".proxyPass = "http://${cfg.address}:${toString port}"; - locations."/query".proxyPass = "http://${cfg.address}:${toString (port + 100)}"; - locations."/static".root = "${pkgs.sourcehut.dispatchsrht}/${pkgs.sourcehut.python.sitePackages}/dispatchsrht"; - }; - }; -} diff --git a/nixos/modules/services/misc/sourcehut/git.nix b/nixos/modules/services/misc/sourcehut/git.nix deleted file mode 100644 index 99b9aec0612..00000000000 --- a/nixos/modules/services/misc/sourcehut/git.nix +++ /dev/null @@ -1,214 +0,0 @@ -{ config, lib, pkgs, ... }: - -with lib; -let - cfg = config.services.sourcehut; - scfg = cfg.git; - iniKey = "git.sr.ht"; - - rcfg = config.services.redis; - drv = pkgs.sourcehut.gitsrht; -in -{ - options.services.sourcehut.git = { - user = mkOption { - type = types.str; - visible = false; - internal = true; - readOnly = true; - default = "git"; - description = '' - User for git.sr.ht. - ''; - }; - - port = mkOption { - type = types.port; - default = 5001; - description = '' - Port on which the "git" module should listen. - ''; - }; - - database = mkOption { - type = types.str; - default = "git.sr.ht"; - description = '' - PostgreSQL database name for git.sr.ht. - ''; - }; - - statePath = mkOption { - type = types.path; - default = "${cfg.statePath}/gitsrht"; - description = '' - State path for git.sr.ht. - ''; - }; - - package = mkOption { - type = types.package; - default = pkgs.git; - example = literalExample "pkgs.gitFull"; - description = '' - Git package for git.sr.ht. This can help silence collisions. - ''; - }; - }; - - config = with scfg; lib.mkIf (cfg.enable && elem "git" cfg.services) { - # sshd refuses to run with `Unsafe AuthorizedKeysCommand ... bad ownership or modes for directory /nix/store` - environment.etc."ssh/gitsrht-dispatch" = { - mode = "0755"; - text = '' - #! ${pkgs.stdenv.shell} - ${cfg.python}/bin/gitsrht-dispatch "$@" - ''; - }; - - # Needs this in the $PATH when sshing into the server - environment.systemPackages = [ cfg.git.package ]; - - users = { - users = { - "${user}" = { - isSystemUser = true; - group = user; - # 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... - shell = pkgs.bash; - description = "git.sr.ht user"; - }; - }; - - groups = { - "${user}" = { }; - }; - }; - - services = { - cron.systemCronJobs = [ "*/20 * * * * ${cfg.python}/bin/gitsrht-periodic" ]; - fcgiwrap.enable = true; - - openssh.authorizedKeysCommand = ''/etc/ssh/gitsrht-dispatch "%u" "%h" "%t" "%k"''; - openssh.authorizedKeysCommandUser = "root"; - openssh.extraConfig = '' - PermitUserEnvironment SRHT_* - ''; - - postgresql = { - authentication = '' - local ${database} ${user} trust - ''; - ensureDatabases = [ database ]; - ensureUsers = [ - { - name = user; - ensurePermissions = { "DATABASE \"${database}\"" = "ALL PRIVILEGES"; }; - } - ]; - }; - }; - - systemd = { - tmpfiles.rules = [ - # /var/log is owned by root - "f /var/log/git-srht-shell 0644 ${user} ${user} -" - - "d ${statePath} 0750 ${user} ${user} -" - "d ${cfg.settings."${iniKey}".repos} 2755 ${user} ${user} -" - ]; - - services = { - gitsrht = import ./service.nix { inherit config pkgs lib; } scfg drv iniKey { - after = [ "redis.service" "postgresql.service" "network.target" ]; - requires = [ "redis.service" "postgresql.service" ]; - wantedBy = [ "multi-user.target" ]; - - # Needs internally to create repos at the very least - path = [ pkgs.git ]; - description = "git.sr.ht website service"; - - serviceConfig.ExecStart = "${cfg.python}/bin/gunicorn ${drv.pname}.app:app -b ${cfg.address}:${toString port}"; - }; - - gitsrht-webhooks = { - after = [ "postgresql.service" "network.target" ]; - requires = [ "postgresql.service" ]; - wantedBy = [ "multi-user.target" ]; - - description = "git.sr.ht webhooks service"; - serviceConfig = { - Type = "simple"; - User = user; - Restart = "always"; - }; - - serviceConfig.ExecStart = "${cfg.python}/bin/celery -A ${drv.pname}.webhooks worker --loglevel=info"; - }; - }; - }; - - services.sourcehut.settings = { - # URL git.sr.ht is being served at (protocol://domain) - "git.sr.ht".origin = mkDefault "http://git.${cfg.originBase}"; - # Address and port to bind the debug server to - "git.sr.ht".debug-host = mkDefault "0.0.0.0"; - "git.sr.ht".debug-port = mkDefault port; - # Configures the SQLAlchemy connection string for the database. - "git.sr.ht".connection-string = mkDefault "postgresql:///${database}?user=${user}&host=/var/run/postgresql"; - # Set to "yes" to automatically run migrations on package upgrade. - "git.sr.ht".migrate-on-upgrade = mkDefault "yes"; - # The redis connection used for the webhooks worker - "git.sr.ht".webhooks = mkDefault "redis://${rcfg.bind}:${toString rcfg.port}/1"; - - # A post-update script which is installed in every git repo. - "git.sr.ht".post-update-script = mkDefault "${pkgs.sourcehut.gitsrht}/bin/gitsrht-update-hook"; - - # git.sr.ht's OAuth client ID and secret for meta.sr.ht - # Register your client at meta.example.org/oauth - "git.sr.ht".oauth-client-id = mkDefault null; - "git.sr.ht".oauth-client-secret = mkDefault null; - # Path to git repositories on disk - "git.sr.ht".repos = mkDefault "/var/lib/git"; - - "git.sr.ht".outgoing-domain = mkDefault "http://git.${cfg.originBase}"; - - # The authorized keys hook uses this to dispatch to various handlers - # The format is a program to exec into as the key, and the user to match as the - # value. When someone tries to log in as this user, this program is executed - # and is expected to omit an AuthorizedKeys file. - # - # Discard of the string context is in order to allow derivation-derived strings. - # This is safe if the relevant package is installed which will be the case if the setting is utilized. - "git.sr.ht::dispatch".${builtins.unsafeDiscardStringContext "${pkgs.sourcehut.gitsrht}/bin/gitsrht-keys"} = mkDefault "${user}:${user}"; - }; - - services.nginx.virtualHosts."git.${cfg.originBase}" = { - forceSSL = true; - locations."/".proxyPass = "http://${cfg.address}:${toString port}"; - locations."/query".proxyPass = "http://${cfg.address}:${toString (port + 100)}"; - locations."/static".root = "${pkgs.sourcehut.gitsrht}/${pkgs.sourcehut.python.sitePackages}/gitsrht"; - extraConfig = '' - location = /authorize { - proxy_pass http://${cfg.address}:${toString port}; - proxy_pass_request_body off; - proxy_set_header Content-Length ""; - proxy_set_header X-Original-URI $request_uri; - } - location ~ ^/([^/]+)/([^/]+)/(HEAD|info/refs|objects/info/.*|git-upload-pack).*$ { - auth_request /authorize; - root /var/lib/git; - fastcgi_pass unix:/run/fcgiwrap.sock; - fastcgi_param SCRIPT_FILENAME ${pkgs.git}/bin/git-http-backend; - fastcgi_param PATH_INFO $uri; - fastcgi_param GIT_PROJECT_ROOT $document_root; - fastcgi_read_timeout 500s; - include ${pkgs.nginx}/conf/fastcgi_params; - gzip off; - } - ''; - - }; - }; -} diff --git a/nixos/modules/services/misc/sourcehut/hg.nix b/nixos/modules/services/misc/sourcehut/hg.nix deleted file mode 100644 index 5cd36bb0455..00000000000 --- a/nixos/modules/services/misc/sourcehut/hg.nix +++ /dev/null @@ -1,173 +0,0 @@ -{ config, lib, pkgs, ... }: - -with lib; -let - cfg = config.services.sourcehut; - scfg = cfg.hg; - iniKey = "hg.sr.ht"; - - rcfg = config.services.redis; - drv = pkgs.sourcehut.hgsrht; -in -{ - options.services.sourcehut.hg = { - user = mkOption { - type = types.str; - internal = true; - readOnly = true; - default = "hg"; - description = '' - User for hg.sr.ht. - ''; - }; - - port = mkOption { - type = types.port; - default = 5010; - description = '' - Port on which the "hg" module should listen. - ''; - }; - - database = mkOption { - type = types.str; - default = "hg.sr.ht"; - description = '' - PostgreSQL database name for hg.sr.ht. - ''; - }; - - statePath = mkOption { - type = types.path; - default = "${cfg.statePath}/hgsrht"; - description = '' - State path for hg.sr.ht. - ''; - }; - - cloneBundles = mkOption { - type = types.bool; - default = false; - description = '' - Generate clonebundles (which require more disk space but dramatically speed up cloning large repositories). - ''; - }; - }; - - config = with scfg; lib.mkIf (cfg.enable && elem "hg" cfg.services) { - # In case it ever comes into being - environment.etc."ssh/hgsrht-dispatch" = { - mode = "0755"; - text = '' - #! ${pkgs.stdenv.shell} - ${cfg.python}/bin/gitsrht-dispatch $@ - ''; - }; - - environment.systemPackages = [ pkgs.mercurial ]; - - users = { - users = { - "${user}" = { - isSystemUser = true; - group = user; - # Assuming hg.sr.ht needs this too - shell = pkgs.bash; - description = "hg.sr.ht user"; - }; - }; - - groups = { - "${user}" = { }; - }; - }; - - services = { - cron.systemCronJobs = [ "*/20 * * * * ${cfg.python}/bin/hgsrht-periodic" ] - ++ optional cloneBundles "0 * * * * ${cfg.python}/bin/hgsrht-clonebundles"; - - openssh.authorizedKeysCommand = ''/etc/ssh/hgsrht-dispatch "%u" "%h" "%t" "%k"''; - openssh.authorizedKeysCommandUser = "root"; - openssh.extraConfig = '' - PermitUserEnvironment SRHT_* - ''; - - postgresql = { - authentication = '' - local ${database} ${user} trust - ''; - ensureDatabases = [ database ]; - ensureUsers = [ - { - name = user; - ensurePermissions = { "DATABASE \"${database}\"" = "ALL PRIVILEGES"; }; - } - ]; - }; - }; - - systemd = { - tmpfiles.rules = [ - # /var/log is owned by root - "f /var/log/hg-srht-shell 0644 ${user} ${user} -" - - "d ${statePath} 0750 ${user} ${user} -" - "d ${cfg.settings."${iniKey}".repos} 2755 ${user} ${user} -" - ]; - - services.hgsrht = import ./service.nix { inherit config pkgs lib; } scfg drv iniKey { - after = [ "redis.service" "postgresql.service" "network.target" ]; - requires = [ "redis.service" "postgresql.service" ]; - wantedBy = [ "multi-user.target" ]; - - path = [ pkgs.mercurial ]; - description = "hg.sr.ht website service"; - - serviceConfig.ExecStart = "${cfg.python}/bin/gunicorn ${drv.pname}.app:app -b ${cfg.address}:${toString port}"; - }; - }; - - services.sourcehut.settings = { - # URL hg.sr.ht is being served at (protocol://domain) - "hg.sr.ht".origin = mkDefault "http://hg.${cfg.originBase}"; - # Address and port to bind the debug server to - "hg.sr.ht".debug-host = mkDefault "0.0.0.0"; - "hg.sr.ht".debug-port = mkDefault port; - # Configures the SQLAlchemy connection string for the database. - "hg.sr.ht".connection-string = mkDefault "postgresql:///${database}?user=${user}&host=/var/run/postgresql"; - # The redis connection used for the webhooks worker - "hg.sr.ht".webhooks = mkDefault "redis://${rcfg.bind}:${toString rcfg.port}/1"; - # A post-update script which is installed in every mercurial repo. - "hg.sr.ht".changegroup-script = mkDefault "${cfg.python}/bin/hgsrht-hook-changegroup"; - # hg.sr.ht's OAuth client ID and secret for meta.sr.ht - # Register your client at meta.example.org/oauth - "hg.sr.ht".oauth-client-id = mkDefault null; - "hg.sr.ht".oauth-client-secret = mkDefault null; - # Path to mercurial repositories on disk - "hg.sr.ht".repos = mkDefault "/var/lib/hg"; - # Path to the srht mercurial extension - # (defaults to where the hgsrht code is) - # "hg.sr.ht".srhtext = mkDefault null; - # .hg/store size (in MB) past which the nightly job generates clone bundles. - # "hg.sr.ht".clone_bundle_threshold = mkDefault 50; - # Path to hg-ssh (if not in $PATH) - # "hg.sr.ht".hg_ssh = mkDefault /path/to/hg-ssh; - - # The authorized keys hook uses this to dispatch to various handlers - # The format is a program to exec into as the key, and the user to match as the - # value. When someone tries to log in as this user, this program is executed - # and is expected to omit an AuthorizedKeys file. - # - # Uncomment the relevant lines to enable the various sr.ht dispatchers. - "hg.sr.ht::dispatch"."/run/current-system/sw/bin/hgsrht-keys" = mkDefault "${user}:${user}"; - }; - - # TODO: requires testing and addition of hg-specific requirements - services.nginx.virtualHosts."hg.${cfg.originBase}" = { - forceSSL = true; - locations."/".proxyPass = "http://${cfg.address}:${toString port}"; - locations."/query".proxyPass = "http://${cfg.address}:${toString (port + 100)}"; - locations."/static".root = "${pkgs.sourcehut.hgsrht}/${pkgs.sourcehut.python.sitePackages}/hgsrht"; - }; - }; -} diff --git a/nixos/modules/services/misc/sourcehut/hub.nix b/nixos/modules/services/misc/sourcehut/hub.nix deleted file mode 100644 index be3ea21011c..00000000000 --- a/nixos/modules/services/misc/sourcehut/hub.nix +++ /dev/null @@ -1,118 +0,0 @@ -{ config, lib, pkgs, ... }: - -with lib; -let - cfg = config.services.sourcehut; - cfgIni = cfg.settings; - scfg = cfg.hub; - iniKey = "hub.sr.ht"; - - drv = pkgs.sourcehut.hubsrht; -in -{ - options.services.sourcehut.hub = { - user = mkOption { - type = types.str; - default = "hubsrht"; - description = '' - User for hub.sr.ht. - ''; - }; - - port = mkOption { - type = types.port; - default = 5014; - description = '' - Port on which the "hub" module should listen. - ''; - }; - - database = mkOption { - type = types.str; - default = "hub.sr.ht"; - description = '' - PostgreSQL database name for hub.sr.ht. - ''; - }; - - statePath = mkOption { - type = types.path; - default = "${cfg.statePath}/hubsrht"; - description = '' - State path for hub.sr.ht. - ''; - }; - }; - - config = with scfg; lib.mkIf (cfg.enable && elem "hub" cfg.services) { - users = { - users = { - "${user}" = { - isSystemUser = true; - group = user; - description = "hub.sr.ht user"; - }; - }; - - groups = { - "${user}" = { }; - }; - }; - - services.postgresql = { - authentication = '' - local ${database} ${user} trust - ''; - ensureDatabases = [ database ]; - ensureUsers = [ - { - name = user; - ensurePermissions = { "DATABASE \"${database}\"" = "ALL PRIVILEGES"; }; - } - ]; - }; - - systemd = { - tmpfiles.rules = [ - "d ${statePath} 0750 ${user} ${user} -" - ]; - - services.hubsrht = import ./service.nix { inherit config pkgs lib; } scfg drv iniKey { - after = [ "postgresql.service" "network.target" ]; - requires = [ "postgresql.service" ]; - wantedBy = [ "multi-user.target" ]; - - description = "hub.sr.ht website service"; - - serviceConfig.ExecStart = "${cfg.python}/bin/gunicorn ${drv.pname}.app:app -b ${cfg.address}:${toString port}"; - }; - }; - - services.sourcehut.settings = { - # URL hub.sr.ht is being served at (protocol://domain) - "hub.sr.ht".origin = mkDefault "http://hub.${cfg.originBase}"; - # Address and port to bind the debug server to - "hub.sr.ht".debug-host = mkDefault "0.0.0.0"; - "hub.sr.ht".debug-port = mkDefault port; - # Configures the SQLAlchemy connection string for the database. - "hub.sr.ht".connection-string = mkDefault "postgresql:///${database}?user=${user}&host=/var/run/postgresql"; - # Set to "yes" to automatically run migrations on package upgrade. - "hub.sr.ht".migrate-on-upgrade = mkDefault "yes"; - # hub.sr.ht's OAuth client ID and secret for meta.sr.ht - # Register your client at meta.example.org/oauth - "hub.sr.ht".oauth-client-id = mkDefault null; - "hub.sr.ht".oauth-client-secret = mkDefault null; - }; - - services.nginx.virtualHosts."${cfg.originBase}" = { - forceSSL = true; - locations."/".proxyPass = "http://${cfg.address}:${toString port}"; - locations."/query".proxyPass = "http://${cfg.address}:${toString (port + 100)}"; - locations."/static".root = "${pkgs.sourcehut.hubsrht}/${pkgs.sourcehut.python.sitePackages}/hubsrht"; - }; - services.nginx.virtualHosts."hub.${cfg.originBase}" = { - globalRedirect = "${cfg.originBase}"; - forceSSL = true; - }; - }; -} diff --git a/nixos/modules/services/misc/sourcehut/lists.nix b/nixos/modules/services/misc/sourcehut/lists.nix deleted file mode 100644 index 7b1fe9fd463..00000000000 --- a/nixos/modules/services/misc/sourcehut/lists.nix +++ /dev/null @@ -1,185 +0,0 @@ -# Email setup is fairly involved, useful references: -# https://drewdevault.com/2018/08/05/Local-mail-server.html - -{ config, lib, pkgs, ... }: - -with lib; -let - cfg = config.services.sourcehut; - cfgIni = cfg.settings; - scfg = cfg.lists; - iniKey = "lists.sr.ht"; - - rcfg = config.services.redis; - drv = pkgs.sourcehut.listssrht; -in -{ - options.services.sourcehut.lists = { - user = mkOption { - type = types.str; - default = "listssrht"; - description = '' - User for lists.sr.ht. - ''; - }; - - port = mkOption { - type = types.port; - default = 5006; - description = '' - Port on which the "lists" module should listen. - ''; - }; - - database = mkOption { - type = types.str; - default = "lists.sr.ht"; - description = '' - PostgreSQL database name for lists.sr.ht. - ''; - }; - - statePath = mkOption { - type = types.path; - default = "${cfg.statePath}/listssrht"; - description = '' - State path for lists.sr.ht. - ''; - }; - }; - - config = with scfg; lib.mkIf (cfg.enable && elem "lists" cfg.services) { - users = { - users = { - "${user}" = { - isSystemUser = true; - group = user; - extraGroups = [ "postfix" ]; - description = "lists.sr.ht user"; - }; - }; - groups = { - "${user}" = { }; - }; - }; - - services.postgresql = { - authentication = '' - local ${database} ${user} trust - ''; - ensureDatabases = [ database ]; - ensureUsers = [ - { - name = user; - ensurePermissions = { "DATABASE \"${database}\"" = "ALL PRIVILEGES"; }; - } - ]; - }; - - systemd = { - tmpfiles.rules = [ - "d ${statePath} 0750 ${user} ${user} -" - ]; - - services = { - listssrht = import ./service.nix { inherit config pkgs lib; } scfg drv iniKey { - after = [ "postgresql.service" "network.target" ]; - requires = [ "postgresql.service" ]; - wantedBy = [ "multi-user.target" ]; - - description = "lists.sr.ht website service"; - - serviceConfig.ExecStart = "${cfg.python}/bin/gunicorn ${drv.pname}.app:app -b ${cfg.address}:${toString port}"; - }; - - listssrht-process = { - after = [ "postgresql.service" "network.target" ]; - requires = [ "postgresql.service" ]; - wantedBy = [ "multi-user.target" ]; - - description = "lists.sr.ht process service"; - serviceConfig = { - Type = "simple"; - User = user; - Restart = "always"; - ExecStart = "${cfg.python}/bin/celery -A ${drv.pname}.process worker --loglevel=info"; - }; - }; - - listssrht-lmtp = { - after = [ "postgresql.service" "network.target" ]; - requires = [ "postgresql.service" ]; - wantedBy = [ "multi-user.target" ]; - - description = "lists.sr.ht process service"; - serviceConfig = { - Type = "simple"; - User = user; - Restart = "always"; - ExecStart = "${cfg.python}/bin/listssrht-lmtp"; - }; - }; - - - listssrht-webhooks = { - after = [ "postgresql.service" "network.target" ]; - requires = [ "postgresql.service" ]; - wantedBy = [ "multi-user.target" ]; - - description = "lists.sr.ht webhooks service"; - serviceConfig = { - Type = "simple"; - User = user; - Restart = "always"; - ExecStart = "${cfg.python}/bin/celery -A ${drv.pname}.webhooks worker --loglevel=info"; - }; - }; - }; - }; - - services.sourcehut.settings = { - # URL lists.sr.ht is being served at (protocol://domain) - "lists.sr.ht".origin = mkDefault "http://lists.${cfg.originBase}"; - # Address and port to bind the debug server to - "lists.sr.ht".debug-host = mkDefault "0.0.0.0"; - "lists.sr.ht".debug-port = mkDefault port; - # Configures the SQLAlchemy connection string for the database. - "lists.sr.ht".connection-string = mkDefault "postgresql:///${database}?user=${user}&host=/var/run/postgresql"; - # Set to "yes" to automatically run migrations on package upgrade. - "lists.sr.ht".migrate-on-upgrade = mkDefault "yes"; - # lists.sr.ht's OAuth client ID and secret for meta.sr.ht - # Register your client at meta.example.org/oauth - "lists.sr.ht".oauth-client-id = mkDefault null; - "lists.sr.ht".oauth-client-secret = mkDefault null; - # Outgoing email for notifications generated by users - "lists.sr.ht".notify-from = mkDefault "CHANGEME@example.org"; - # The redis connection used for the webhooks worker - "lists.sr.ht".webhooks = mkDefault "redis://${rcfg.bind}:${toString rcfg.port}/2"; - # The redis connection used for the celery worker - "lists.sr.ht".redis = mkDefault "redis://${rcfg.bind}:${toString rcfg.port}/4"; - # Network-key - "lists.sr.ht".network-key = mkDefault null; - # Allow creation - "lists.sr.ht".allow-new-lists = mkDefault "no"; - # Posting Domain - "lists.sr.ht".posting-domain = mkDefault "lists.${cfg.originBase}"; - - # 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. - "lists.sr.ht::worker".sock = mkDefault "/tmp/lists.sr.ht-lmtp.sock"; - # The lmtp daemon will make the unix socket group-read/write for users in this - # group. - "lists.sr.ht::worker".sock-group = mkDefault "postfix"; - "lists.sr.ht::worker".reject-url = mkDefault "https://man.sr.ht/lists.sr.ht/etiquette.md"; - "lists.sr.ht::worker".reject-mimetypes = mkDefault "text/html"; - - }; - - services.nginx.virtualHosts."lists.${cfg.originBase}" = { - forceSSL = true; - locations."/".proxyPass = "http://${cfg.address}:${toString port}"; - locations."/query".proxyPass = "http://${cfg.address}:${toString (port + 100)}"; - locations."/static".root = "${pkgs.sourcehut.listssrht}/${pkgs.sourcehut.python.sitePackages}/listssrht"; - }; - }; -} diff --git a/nixos/modules/services/misc/sourcehut/man.nix b/nixos/modules/services/misc/sourcehut/man.nix deleted file mode 100644 index 7693396d187..00000000000 --- a/nixos/modules/services/misc/sourcehut/man.nix +++ /dev/null @@ -1,122 +0,0 @@ -{ config, lib, pkgs, ... }: - -with lib; -let - cfg = config.services.sourcehut; - cfgIni = cfg.settings; - scfg = cfg.man; - iniKey = "man.sr.ht"; - - drv = pkgs.sourcehut.mansrht; -in -{ - options.services.sourcehut.man = { - user = mkOption { - type = types.str; - default = "mansrht"; - description = '' - User for man.sr.ht. - ''; - }; - - port = mkOption { - type = types.port; - default = 5004; - description = '' - Port on which the "man" module should listen. - ''; - }; - - database = mkOption { - type = types.str; - default = "man.sr.ht"; - description = '' - PostgreSQL database name for man.sr.ht. - ''; - }; - - statePath = mkOption { - type = types.path; - default = "${cfg.statePath}/mansrht"; - description = '' - State path for man.sr.ht. - ''; - }; - }; - - config = with scfg; lib.mkIf (cfg.enable && elem "man" cfg.services) { - assertions = - [ - { - assertion = hasAttrByPath [ "git.sr.ht" "oauth-client-id" ] cfgIni; - message = "man.sr.ht needs access to git.sr.ht."; - } - ]; - - users = { - users = { - "${user}" = { - isSystemUser = true; - group = user; - description = "man.sr.ht user"; - }; - }; - - groups = { - "${user}" = { }; - }; - }; - - services.postgresql = { - authentication = '' - local ${database} ${user} trust - ''; - ensureDatabases = [ database ]; - ensureUsers = [ - { - name = user; - ensurePermissions = { "DATABASE \"${database}\"" = "ALL PRIVILEGES"; }; - } - ]; - }; - - systemd = { - tmpfiles.rules = [ - "d ${statePath} 0750 ${user} ${user} -" - ]; - - services.mansrht = import ./service.nix { inherit config pkgs lib; } scfg drv iniKey { - after = [ "postgresql.service" "network.target" ]; - requires = [ "postgresql.service" ]; - wantedBy = [ "multi-user.target" ]; - - description = "man.sr.ht website service"; - - serviceConfig.ExecStart = "${cfg.python}/bin/gunicorn ${drv.pname}.app:app -b ${cfg.address}:${toString port}"; - }; - }; - - services.sourcehut.settings = { - # URL man.sr.ht is being served at (protocol://domain) - "man.sr.ht".origin = mkDefault "http://man.${cfg.originBase}"; - # Address and port to bind the debug server to - "man.sr.ht".debug-host = mkDefault "0.0.0.0"; - "man.sr.ht".debug-port = mkDefault port; - # Configures the SQLAlchemy connection string for the database. - "man.sr.ht".connection-string = mkDefault "postgresql:///${database}?user=${user}&host=/var/run/postgresql"; - # Set to "yes" to automatically run migrations on package upgrade. - "man.sr.ht".migrate-on-upgrade = mkDefault "yes"; - # man.sr.ht's OAuth client ID and secret for meta.sr.ht - # Register your client at meta.example.org/oauth - "man.sr.ht".oauth-client-id = mkDefault null; - "man.sr.ht".oauth-client-secret = mkDefault null; - }; - - services.nginx.virtualHosts."man.${cfg.originBase}" = { - forceSSL = true; - locations."/".proxyPass = "http://${cfg.address}:${toString port}"; - locations."/query".proxyPass = "http://${cfg.address}:${toString (port + 100)}"; - locations."/static".root = "${pkgs.sourcehut.mansrht}/${pkgs.sourcehut.python.sitePackages}/mansrht"; - }; - }; -} diff --git a/nixos/modules/services/misc/sourcehut/meta.nix b/nixos/modules/services/misc/sourcehut/meta.nix deleted file mode 100644 index 56127a824eb..00000000000 --- a/nixos/modules/services/misc/sourcehut/meta.nix +++ /dev/null @@ -1,211 +0,0 @@ -{ config, lib, pkgs, ... }: - -with lib; -let - cfg = config.services.sourcehut; - cfgIni = cfg.settings; - scfg = cfg.meta; - iniKey = "meta.sr.ht"; - - rcfg = config.services.redis; - drv = pkgs.sourcehut.metasrht; -in -{ - options.services.sourcehut.meta = { - user = mkOption { - type = types.str; - default = "metasrht"; - description = '' - User for meta.sr.ht. - ''; - }; - - port = mkOption { - type = types.port; - default = 5000; - description = '' - Port on which the "meta" module should listen. - ''; - }; - - database = mkOption { - type = types.str; - default = "meta.sr.ht"; - description = '' - PostgreSQL database name for meta.sr.ht. - ''; - }; - - statePath = mkOption { - type = types.path; - default = "${cfg.statePath}/metasrht"; - description = '' - State path for meta.sr.ht. - ''; - }; - }; - - config = with scfg; lib.mkIf (cfg.enable && elem "meta" cfg.services) { - assertions = - [ - { - assertion = with cfgIni."meta.sr.ht::billing"; enabled == "yes" -> (stripe-public-key != null && stripe-secret-key != null); - message = "If meta.sr.ht::billing is enabled, the keys should be defined."; - } - ]; - - users = { - users = { - ${user} = { - isSystemUser = true; - group = user; - description = "meta.sr.ht user"; - }; - }; - - groups = { - "${user}" = { }; - }; - }; - - services.cron.systemCronJobs = [ "0 0 * * * ${cfg.python}/bin/metasrht-daily" ]; - services.postgresql = { - authentication = '' - local ${database} ${user} trust - ''; - ensureDatabases = [ database ]; - ensureUsers = [ - { - name = user; - ensurePermissions = { "DATABASE \"${database}\"" = "ALL PRIVILEGES"; }; - } - ]; - }; - - systemd = { - tmpfiles.rules = [ - "d ${statePath} 0750 ${user} ${user} -" - ]; - - services = { - metasrht = import ./service.nix { inherit config pkgs lib; } scfg drv iniKey { - after = [ "postgresql.service" "network.target" ]; - requires = [ "postgresql.service" ]; - wantedBy = [ "multi-user.target" ]; - - description = "meta.sr.ht website service"; - - preStart = '' - # Configure client(s) as "preauthorized" - ${concatMapStringsSep "\n\n" - (attr: '' - if ! test -e "${statePath}/${attr}.oauth" || [ "$(cat ${statePath}/${attr}.oauth)" != "${cfgIni."${attr}".oauth-client-id}" ]; then - # Configure ${attr}'s OAuth client as "preauthorized" - psql ${database} \ - -c "UPDATE oauthclient SET preauthorized = true WHERE client_id = '${cfgIni."${attr}".oauth-client-id}'" - - printf "%s" "${cfgIni."${attr}".oauth-client-id}" > "${statePath}/${attr}.oauth" - fi - '') - (builtins.attrNames (filterAttrs - (k: v: !(hasInfix "::" k) && builtins.hasAttr "oauth-client-id" v && v.oauth-client-id != null) - cfg.settings))} - ''; - - serviceConfig.ExecStart = "${cfg.python}/bin/gunicorn ${drv.pname}.app:app -b ${cfg.address}:${toString port}"; - }; - - metasrht-api = import ./service.nix { inherit config pkgs lib; } scfg drv iniKey { - after = [ "postgresql.service" "network.target" ]; - requires = [ "postgresql.service" ]; - wantedBy = [ "multi-user.target" ]; - - description = "meta.sr.ht api service"; - - preStart = '' - # Configure client(s) as "preauthorized" - ${concatMapStringsSep "\n\n" - (attr: '' - if ! test -e "${statePath}/${attr}.oauth" || [ "$(cat ${statePath}/${attr}.oauth)" != "${cfgIni."${attr}".oauth-client-id}" ]; then - # Configure ${attr}'s OAuth client as "preauthorized" - psql ${database} \ - -c "UPDATE oauthclient SET preauthorized = true WHERE client_id = '${cfgIni."${attr}".oauth-client-id}'" - - printf "%s" "${cfgIni."${attr}".oauth-client-id}" > "${statePath}/${attr}.oauth" - fi - '') - (builtins.attrNames (filterAttrs - (k: v: !(hasInfix "::" k) && builtins.hasAttr "oauth-client-id" v && v.oauth-client-id != null) - cfg.settings))} - ''; - - serviceConfig.ExecStart = "${pkgs.sourcehut.metasrht}/bin/metasrht-api -b :${toString (port + 100)}"; - }; - - metasrht-webhooks = { - after = [ "postgresql.service" "network.target" ]; - requires = [ "postgresql.service" ]; - wantedBy = [ "multi-user.target" ]; - - description = "meta.sr.ht webhooks service"; - serviceConfig = { - Type = "simple"; - User = user; - Restart = "always"; - ExecStart = "${cfg.python}/bin/celery -A ${drv.pname}.webhooks worker --loglevel=info"; - }; - - }; - }; - }; - - services.sourcehut.settings = { - # URL meta.sr.ht is being served at (protocol://domain) - "meta.sr.ht".origin = mkDefault "https://meta.${cfg.originBase}"; - # Address and port to bind the debug server to - "meta.sr.ht".debug-host = mkDefault "0.0.0.0"; - "meta.sr.ht".debug-port = mkDefault port; - # Configures the SQLAlchemy connection string for the database. - "meta.sr.ht".connection-string = mkDefault "postgresql:///${database}?user=${user}&host=/var/run/postgresql"; - # Set to "yes" to automatically run migrations on package upgrade. - "meta.sr.ht".migrate-on-upgrade = mkDefault "yes"; - # If "yes", the user will be sent the stock sourcehut welcome emails after - # signup (requires cron to be configured properly). These are specific to the - # sr.ht instance so you probably want to patch these before enabling this. - "meta.sr.ht".welcome-emails = mkDefault "no"; - - # The redis connection used for the webhooks worker - "meta.sr.ht".webhooks = mkDefault "redis://${rcfg.bind}:${toString rcfg.port}/6"; - - # If "no", public registration will not be permitted. - "meta.sr.ht::settings".registration = mkDefault "no"; - # Where to redirect new users upon registration - "meta.sr.ht::settings".onboarding-redirect = mkDefault "https://meta.${cfg.originBase}"; - # How many invites each user is issued upon registration (only applicable if - # open registration is disabled) - "meta.sr.ht::settings".user-invites = mkDefault 5; - - # Origin URL for API, 100 more than web - "meta.sr.ht".api-origin = mkDefault "http://localhost:5100"; - - # You can add aliases for the client IDs of commonly used OAuth clients here. - # - # Example: - "meta.sr.ht::aliases" = mkDefault { }; - # "meta.sr.ht::aliases"."git.sr.ht" = 12345; - - # "yes" to enable the billing system - "meta.sr.ht::billing".enabled = mkDefault "no"; - # Get your keys at https://dashboard.stripe.com/account/apikeys - "meta.sr.ht::billing".stripe-public-key = mkDefault null; - "meta.sr.ht::billing".stripe-secret-key = mkDefault null; - }; - - services.nginx.virtualHosts."meta.${cfg.originBase}" = { - forceSSL = true; - locations."/".proxyPass = "http://${cfg.address}:${toString port}"; - locations."/query".proxyPass = "http://${cfg.address}:${toString (port + 100)}"; - locations."/static".root = "${pkgs.sourcehut.metasrht}/${pkgs.sourcehut.python.sitePackages}/metasrht"; - }; - }; -} diff --git a/nixos/modules/services/misc/sourcehut/paste.nix b/nixos/modules/services/misc/sourcehut/paste.nix deleted file mode 100644 index b2d5151969e..00000000000 --- a/nixos/modules/services/misc/sourcehut/paste.nix +++ /dev/null @@ -1,133 +0,0 @@ -{ config, lib, pkgs, ... }: - -with lib; -let - cfg = config.services.sourcehut; - cfgIni = cfg.settings; - scfg = cfg.paste; - iniKey = "paste.sr.ht"; - - rcfg = config.services.redis; - drv = pkgs.sourcehut.pastesrht; -in -{ - options.services.sourcehut.paste = { - user = mkOption { - type = types.str; - default = "pastesrht"; - description = '' - User for paste.sr.ht. - ''; - }; - - port = mkOption { - type = types.port; - default = 5011; - description = '' - Port on which the "paste" module should listen. - ''; - }; - - database = mkOption { - type = types.str; - default = "paste.sr.ht"; - description = '' - PostgreSQL database name for paste.sr.ht. - ''; - }; - - statePath = mkOption { - type = types.path; - default = "${cfg.statePath}/pastesrht"; - description = '' - State path for pastesrht.sr.ht. - ''; - }; - }; - - config = with scfg; lib.mkIf (cfg.enable && elem "paste" cfg.services) { - users = { - users = { - "${user}" = { - isSystemUser = true; - group = user; - description = "paste.sr.ht user"; - }; - }; - - groups = { - "${user}" = { }; - }; - }; - - services.postgresql = { - authentication = '' - local ${database} ${user} trust - ''; - ensureDatabases = [ database ]; - ensureUsers = [ - { - name = user; - ensurePermissions = { "DATABASE \"${database}\"" = "ALL PRIVILEGES"; }; - } - ]; - }; - - systemd = { - tmpfiles.rules = [ - "d ${statePath} 0750 ${user} ${user} -" - ]; - - services = { - pastesrht = import ./service.nix { inherit config pkgs lib; } scfg drv iniKey { - after = [ "postgresql.service" "network.target" ]; - requires = [ "postgresql.service" ]; - wantedBy = [ "multi-user.target" ]; - - description = "paste.sr.ht website service"; - - serviceConfig.ExecStart = "${cfg.python}/bin/gunicorn ${drv.pname}.app:app -b ${cfg.address}:${toString port}"; - }; - - pastesrht-webhooks = { - after = [ "postgresql.service" "network.target" ]; - requires = [ "postgresql.service" ]; - wantedBy = [ "multi-user.target" ]; - - description = "paste.sr.ht webhooks service"; - serviceConfig = { - Type = "simple"; - User = user; - Restart = "always"; - ExecStart = "${cfg.python}/bin/celery -A ${drv.pname}.webhooks worker --loglevel=info"; - }; - - }; - }; - }; - - services.sourcehut.settings = { - # URL paste.sr.ht is being served at (protocol://domain) - "paste.sr.ht".origin = mkDefault "http://paste.${cfg.originBase}"; - # Address and port to bind the debug server to - "paste.sr.ht".debug-host = mkDefault "0.0.0.0"; - "paste.sr.ht".debug-port = mkDefault port; - # Configures the SQLAlchemy connection string for the database. - "paste.sr.ht".connection-string = mkDefault "postgresql:///${database}?user=${user}&host=/var/run/postgresql"; - # Set to "yes" to automatically run migrations on package upgrade. - "paste.sr.ht".migrate-on-upgrade = mkDefault "yes"; - # paste.sr.ht's OAuth client ID and secret for meta.sr.ht - # Register your client at meta.example.org/oauth - "paste.sr.ht".oauth-client-id = mkDefault null; - "paste.sr.ht".oauth-client-secret = mkDefault null; - "paste.sr.ht".webhooks = mkDefault "redis://${rcfg.bind}:${toString rcfg.port}/5"; - }; - - services.nginx.virtualHosts."paste.${cfg.originBase}" = { - forceSSL = true; - locations."/".proxyPass = "http://${cfg.address}:${toString port}"; - locations."/query".proxyPass = "http://${cfg.address}:${toString (port + 100)}"; - locations."/static".root = "${pkgs.sourcehut.pastesrht}/${pkgs.sourcehut.python.sitePackages}/pastesrht"; - }; - }; -} diff --git a/nixos/modules/services/misc/sourcehut/service.nix b/nixos/modules/services/misc/sourcehut/service.nix index 65b4ad020f9..b3c0efc07dd 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 = 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/modules/services/misc/sourcehut/todo.nix b/nixos/modules/services/misc/sourcehut/todo.nix deleted file mode 100644 index aec773b0669..00000000000 --- a/nixos/modules/services/misc/sourcehut/todo.nix +++ /dev/null @@ -1,161 +0,0 @@ -{ config, lib, pkgs, ... }: - -with lib; -let - cfg = config.services.sourcehut; - cfgIni = cfg.settings; - scfg = cfg.todo; - iniKey = "todo.sr.ht"; - - rcfg = config.services.redis; - drv = pkgs.sourcehut.todosrht; -in -{ - options.services.sourcehut.todo = { - user = mkOption { - type = types.str; - default = "todosrht"; - description = '' - User for todo.sr.ht. - ''; - }; - - port = mkOption { - type = types.port; - default = 5003; - description = '' - Port on which the "todo" module should listen. - ''; - }; - - database = mkOption { - type = types.str; - default = "todo.sr.ht"; - description = '' - PostgreSQL database name for todo.sr.ht. - ''; - }; - - statePath = mkOption { - type = types.path; - default = "${cfg.statePath}/todosrht"; - description = '' - State path for todo.sr.ht. - ''; - }; - }; - - config = with scfg; lib.mkIf (cfg.enable && elem "todo" cfg.services) { - users = { - users = { - "${user}" = { - isSystemUser = true; - group = user; - extraGroups = [ "postfix" ]; - description = "todo.sr.ht user"; - }; - }; - groups = { - "${user}" = { }; - }; - }; - - services.postgresql = { - authentication = '' - local ${database} ${user} trust - ''; - ensureDatabases = [ database ]; - ensureUsers = [ - { - name = user; - ensurePermissions = { "DATABASE \"${database}\"" = "ALL PRIVILEGES"; }; - } - ]; - }; - - systemd = { - tmpfiles.rules = [ - "d ${statePath} 0750 ${user} ${user} -" - ]; - - services = { - todosrht = import ./service.nix { inherit config pkgs lib; } scfg drv iniKey { - after = [ "postgresql.service" "network.target" ]; - requires = [ "postgresql.service" ]; - wantedBy = [ "multi-user.target" ]; - - description = "todo.sr.ht website service"; - - serviceConfig.ExecStart = "${cfg.python}/bin/gunicorn ${drv.pname}.app:app -b ${cfg.address}:${toString port}"; - }; - - todosrht-lmtp = { - after = [ "postgresql.service" "network.target" ]; - bindsTo = [ "postgresql.service" ]; - wantedBy = [ "multi-user.target" ]; - - description = "todo.sr.ht process service"; - serviceConfig = { - Type = "simple"; - User = user; - Restart = "always"; - ExecStart = "${cfg.python}/bin/todosrht-lmtp"; - }; - }; - - todosrht-webhooks = { - after = [ "postgresql.service" "network.target" ]; - requires = [ "postgresql.service" ]; - wantedBy = [ "multi-user.target" ]; - - description = "todo.sr.ht webhooks service"; - serviceConfig = { - Type = "simple"; - User = user; - Restart = "always"; - ExecStart = "${cfg.python}/bin/celery -A ${drv.pname}.webhooks worker --loglevel=info"; - }; - - }; - }; - }; - - services.sourcehut.settings = { - # URL todo.sr.ht is being served at (protocol://domain) - "todo.sr.ht".origin = mkDefault "http://todo.${cfg.originBase}"; - # Address and port to bind the debug server to - "todo.sr.ht".debug-host = mkDefault "0.0.0.0"; - "todo.sr.ht".debug-port = mkDefault port; - # Configures the SQLAlchemy connection string for the database. - "todo.sr.ht".connection-string = mkDefault "postgresql:///${database}?user=${user}&host=/var/run/postgresql"; - # Set to "yes" to automatically run migrations on package upgrade. - "todo.sr.ht".migrate-on-upgrade = mkDefault "yes"; - # todo.sr.ht's OAuth client ID and secret for meta.sr.ht - # Register your client at meta.example.org/oauth - "todo.sr.ht".oauth-client-id = mkDefault null; - "todo.sr.ht".oauth-client-secret = mkDefault null; - # Outgoing email for notifications generated by users - "todo.sr.ht".notify-from = mkDefault "CHANGEME@example.org"; - # The redis connection used for the webhooks worker - "todo.sr.ht".webhooks = mkDefault "redis://${rcfg.bind}:${toString rcfg.port}/1"; - # Network-key - "todo.sr.ht".network-key = mkDefault null; - - # 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. - "todo.sr.ht::mail".sock = mkDefault "/tmp/todo.sr.ht-lmtp.sock"; - # The lmtp daemon will make the unix socket group-read/write for users in this - # group. - "todo.sr.ht::mail".sock-group = mkDefault "postfix"; - - "todo.sr.ht::mail".posting-domain = mkDefault "todo.${cfg.originBase}"; - }; - - services.nginx.virtualHosts."todo.${cfg.originBase}" = { - forceSSL = true; - locations."/".proxyPass = "http://${cfg.address}:${toString port}"; - locations."/query".proxyPass = "http://${cfg.address}:${toString (port + 100)}"; - locations."/static".root = "${pkgs.sourcehut.todosrht}/${pkgs.sourcehut.python.sitePackages}/todosrht"; - }; - }; -} diff --git a/pkgs/applications/version-management/sourcehut/builds.nix b/pkgs/applications/version-management/sourcehut/builds.nix index c8163caf8ea..374736e80f5 100644 --- a/pkgs/applications/version-management/sourcehut/builds.nix +++ b/pkgs/applications/version-management/sourcehut/builds.nix @@ -11,13 +11,13 @@ , python }: let - version = "0.66.7"; + version = "0.71.0"; buildWorker = src: buildGoModule { inherit src version; pname = "builds-sr-ht-worker"; - vendorSha256 = "sha256-giOaldV46aBqXyFH/cQVsbUr6Rb4VMhbBO86o48tRZY="; + vendorSha256 = "sha256-ZEarWM/33t+pNXUEIpfd/DkBkhu3UUg17Hh8XXWOepA="; }; in buildPythonPackage rec { @@ -28,7 +28,7 @@ buildPythonPackage rec { owner = "~sircmpwn"; repo = "builds.sr.ht"; rev = version; - sha256 = "sha256-2MLs/DOXHjEYarXDVUcPZe3o0fmZbzVxn528SE72lhM="; + sha256 = "sha256-S3mMndUdVGi+YxAOI3wSNlSZrH3cwumxatXpErS2yQI="; }; nativeBuildInputs = srht.nativeBuildInputs; @@ -56,10 +56,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 d359d524eb2..939d9d7b750 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.67.18"; src = fetchgit { url = "https://git.sr.ht/~sircmpwn/core.sr.ht"; rev = version; - sha256 = "sha256-XvzFfcBK5Mq8p7xEBAF/eupUE1kkUBh5k+ByM/WA9bc="; + sha256 = "sha256-lPPB1DRWHANGBBfWeu0pUn1xBkd37ZhyreozLlxX6cA="; fetchSubmodules = true; }; @@ -46,7 +45,12 @@ buildPythonPackage rec { }; patches = [ - ./disable-npm-install.patch + # Disable check for npm + patches/disable-npm-install.patch + # Fix broken hack: removing dots from "builds.sr.ht" does not produce "buildsrht" + patches/srht-update-profiles/0001-fix-disgusting-hack-in-the-case-of-buildsrht.patch + # Add Unix socket support for redis-host= + patches/redis-socket/core/v2-0001-add-Unix-socket-support-for-redis-host.patch ]; nativeBuildInputs = [ @@ -87,6 +91,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..7dde841b543 100644 --- a/pkgs/applications/version-management/sourcehut/default.nix +++ b/pkgs/applications/version-management/sourcehut/default.nix @@ -22,10 +22,12 @@ 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 { }; scmsrht = self.callPackage ./scm.nix { }; + srht-keys = self.scmsrht.srht-keys; }; }; in @@ -40,6 +42,8 @@ with python.pkgs; recurseIntoAttrs { listssrht = toPythonApplication listssrht; mansrht = toPythonApplication mansrht; metasrht = toPythonApplication metasrht; + pagessrht = pagessrht; pastesrht = toPythonApplication pastesrht; todosrht = toPythonApplication todosrht; + srht-keys = scmsrht.srht-keys; } diff --git a/pkgs/applications/version-management/sourcehut/dispatch.nix b/pkgs/applications/version-management/sourcehut/dispatch.nix index 637c6f9c1df..9456d0c998c 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.32"; src = fetchFromSourcehut { owner = "~sircmpwn"; repo = "dispatch.sr.ht"; rev = version; - sha256 = "sha256-zWCGPjIgMKHXHJUs9aciV7IFgo0rpahon6KXHDwcfss="; + sha256 = "sha256-4P4cXhjcZ8IBzpRfmYIJkzl9U4Plo36a48Pf/KjmhFY="; }; 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..0a09530b14b 100644 --- a/pkgs/applications/version-management/sourcehut/git.nix +++ b/pkgs/applications/version-management/sourcehut/git.nix @@ -6,42 +6,109 @@ , srht , pygit2 , scmsrht +, srht-keys }: let - version = "0.72.8"; + version = "0.72.44"; src = fetchFromSourcehut { owner = "~sircmpwn"; repo = "git.sr.ht"; rev = version; - sha256 = "sha256-AB2uzajO5PtcpJfbOOTfuDFM6is5K39v3AZJ1hShRNc="; + sha256 = "sha256-U+hJiQpAJIHBC//M/Lfw82DLhZF46qHhz8zZW1ZvJoo="; }; - buildShell = src: buildGoModule { + gitsrht-shell = buildGoModule { inherit src version; + sourceRoot = "source/gitsrht-shell"; pname = "gitsrht-shell"; vendorSha256 = "sha256-aqUFICp0C2reqb2p6JCPAUIRsxzSv0t9BHoNWrTYfqk="; }; - buildDispatcher = src: buildGoModule { + gitsrht-dispatch = buildGoModule { inherit src version; + sourceRoot = "source/gitsrht-dispatch"; pname = "gitsrht-dispatcher"; vendorSha256 = "sha256-qWXPHo86s6iuRBhRMtmD5jxnAWKdrWHtA/iSUkdw89M="; + patches = [ + # Add support for supplementary groups + patches/redis-socket/git/v2-0003-gitsrht-dispatch-add-support-for-supplementary-gr.patch + ]; + patchFlags = "-p2"; }; - buildKeys = src: buildGoModule { + gitsrht-keys = buildGoModule rec { inherit src version; + sourceRoot = "source/gitsrht-keys"; pname = "gitsrht-keys"; - vendorSha256 = "1d94cqy7x0q0agwg515xxsbl70b3qrzxbzsyjhn1pbyj532brn7f"; + vendorSha256 = "sha256-0Rnyo4IRQFhM4LFi0499+xJaboMiKEYOgoR5BumzRE8="; + + # What follows is only to update go-redis + # go.{mod,sum} could be patched directly but that would be less resilient + # to changes from upstream, and thus harder to maintain the patching + # while it hasn't been merged upstream. + + overrideModAttrs = old: { + inherit patches patchFlags; + preBuild = '' + # This is a fixed-output derivation so it is not allowed to reference other derivations, + # but here srht-keys will be copied to vendor/ by go mod vendor + ln -s ${srht-keys} srht-keys + go mod edit -replace git.sr.ht/~sircmpwn/scm.sr.ht/srht-keys=$PWD/srht-keys + go get github.com/go-redis/redis/v8 + go get github.com/go-redis/redis@none + go mod tidy + ''; + # Pass updated go.{mod,sum} from go-modules to gitsrht-keys' vendor/go.{mod,sum} + postInstall = '' + cp --reflink=auto go.* $out/ + ''; + }; + + patches = [ + # Update go-redis to support Unix sockets + patches/redis-socket/git/v2-0001-gitsrht-keys-update-go-redis-to-support-Unix-sock.patch + ]; + patchFlags = "-p2"; + postConfigure = '' + cp -v vendor/go.{mod,sum} . + ''; }; - buildUpdateHook = src: buildGoModule { + gitsrht-update-hook = buildGoModule rec { inherit src version; + sourceRoot = "source/gitsrht-update-hook"; pname = "gitsrht-update-hook"; - vendorSha256 = "0fwzqpjv8x5y3w3bfjd0x0cvqjjak23m0zj88hf32jpw49xmjkih"; - }; + vendorSha256 = "sha256-UoHxGVYEgTDqFzVQ2Dv6BRT4jVt+/QpNqEH3G2UWFjs="; - updateHook = buildUpdateHook "${src}/gitsrht-update-hook"; + # What follows is only to update go-redis + + overrideModAttrs = old: { + inherit patches patchFlags; + preBuild = '' + # This is a fixed-output derivation so it is not allowed to reference other derivations, + # but here srht-keys will be copied to vendor/ by go mod vendor + ln -s ${srht-keys} srht-keys + go mod edit -replace git.sr.ht/~sircmpwn/scm.sr.ht/srht-keys=$PWD/srht-keys + go get github.com/go-redis/redis/v8 + go get github.com/go-redis/redis@none + go mod tidy + ''; + # Pass updated go.{mod,sum} from go-modules to gitsrht-keys' vendor/go.{mod,sum} + postInstall = '' + cp --reflink=auto go.* $out/ + ''; + }; + + patches = [ + # Update go-redis to support Unix sockets + patches/redis-socket/git/v2-0002-gitsrht-update-hook-update-go-redis-to-support-Un.patch + ]; + patchFlags = "-p2"; + postConfigure = '' + cp -v vendor/go.{mod,sum} . + ''; + }; in buildPythonPackage rec { @@ -63,19 +130,21 @@ buildPythonPackage rec { postInstall = '' mkdir -p $out/bin - cp ${buildShell "${src}/gitsrht-shell"}/bin/gitsrht-shell $out/bin/gitsrht-shell - cp ${buildDispatcher "${src}/gitsrht-dispatch"}/bin/gitsrht-dispatch $out/bin/gitsrht-dispatch - cp ${buildKeys "${src}/gitsrht-keys"}/bin/gitsrht-keys $out/bin/gitsrht-keys - cp ${updateHook}/bin/gitsrht-update-hook $out/bin/gitsrht-update-hook + cp ${gitsrht-shell}/bin/gitsrht-shell $out/bin/gitsrht-shell + cp ${gitsrht-dispatch}/bin/gitsrht-dispatch $out/bin/gitsrht-dispatch + cp ${gitsrht-keys}/bin/gitsrht-keys $out/bin/gitsrht-keys + cp ${gitsrht-update-hook}/bin/gitsrht-update-hook $out/bin/gitsrht-update-hook ''; passthru = { - inherit updateHook; + inherit gitsrht-shell gitsrht-dispatch gitsrht-keys gitsrht-update-hook; }; + 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..1d6062d81cc 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.27.6"; src = fetchhg { url = "https://hg.sr.ht/~sircmpwn/hg.sr.ht"; rev = version; - sha256 = "1c0qfi0gmbfngvds6917fy9ii2iglawn429757rh7b4bvzn7n6mr"; + sha256 = "ibijvKjS4CiWTYrO6Qdh3RkD0EUE7BY8wjdPwrD6vkA="; }; 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..31191cba713 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.13.8"; src = fetchFromSourcehut { owner = "~sircmpwn"; repo = "hub.sr.ht"; rev = version; - sha256 = "sha256-Kqzy4mh5Nn1emzHBco/LVuXro/tW3NX+OYqdEwBSQ/U="; + sha256 = "sha256-RsRJxwViEoQLh86o+8kQE5PBlLrOyIFM7hkSGjXhqdg="; }; 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..0882241401a 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.49.3"; src = fetchFromSourcehut { owner = "~sircmpwn"; repo = "lists.sr.ht"; rev = version; - sha256 = "sha256-bsakEMyvWaxiE4/SGcAP4mlGG9jkdHfFxpt9H+TJn/8="; + sha256 = "sha256-kEzKgB8godIL7hEXMrqaxVte6RJAegjT4keZifXbOq0="; }; 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..47c6bb0ac4f 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.20"; src = fetchFromSourcehut { owner = "~sircmpwn"; repo = "man.sr.ht"; rev = version; - sha256 = "sha256-MqH/8K9XRvEg6P7GHE6XXtWnhDP3wT8iGoNaFtYQbio="; + sha256 = "sha256-ulwdrVrw2bwdafgc3NrJ1J15evQ5btpHLTaiqsyA58U="; }; 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 a285d484ed2..dbb0483c5bb 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.54.4"; src = fetchFromSourcehut { owner = "~sircmpwn"; repo = "meta.sr.ht"; rev = version; - sha256 = "sha256-/+r/XLDkcSTW647xPMh5bcJmR2xZNNH74AJ5jemna2k="; + sha256 = "sha256-MleyF6aqFWbYtxRdMHXpy7HBgJKL9doBmDcYLLe8bW4="; }; buildApi = src: buildGoModule { inherit src version; pname = "metasrht-api"; - vendorSha256 = "sha256-eZyDrr2VcNMxI++18qUy7LA1Q1YDlWCoRtl00L8lfR4="; + vendorSha256 = "sha256-gi+dGQPVzrZI+1s9SSa2M3bdgi8vwpR/ofaLG2ZX4kU="; }; in @@ -66,10 +66,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-fix-syntax-error-in-schema.sql.patch b/pkgs/applications/version-management/sourcehut/pages-fix-syntax-error-in-schema.sql.patch new file mode 100644 index 00000000000..9b3f6fbc6fb --- /dev/null +++ b/pkgs/applications/version-management/sourcehut/pages-fix-syntax-error-in-schema.sql.patch @@ -0,0 +1,27 @@ +From 3df160ad289b25574322f587095d00d6641f057c Mon Sep 17 00:00:00 2001 +From: Juan Picca +Date: Wed, 21 Jul 2021 08:26:56 -0300 +Subject: [PATCH] Fix syntax error in schema.sql + +--- + schema.sql | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/schema.sql b/schema.sql +index 168377f..2e473ea 100644 +--- a/schema.sql ++++ b/schema.sql +@@ -28,8 +28,8 @@ CREATE TABLE sites ( + user_id integer NOT NULL references "user"(id), + domain varchar NOT NULL, + protocol protocol NOT NULL, +- version varchar NOT NULL +- UNIQUE (domain, protocol), ++ version varchar NOT NULL, ++ UNIQUE (domain, protocol) + ); + + COMMIT; +-- +2.32.0 + diff --git a/pkgs/applications/version-management/sourcehut/pages.nix b/pkgs/applications/version-management/sourcehut/pages.nix new file mode 100644 index 00000000000..d04182251a7 --- /dev/null +++ b/pkgs/applications/version-management/sourcehut/pages.nix @@ -0,0 +1,34 @@ +{ lib +, fetchFromSourcehut +, buildGoModule +}: +buildGoModule rec { + pname = "pagessrht"; + version = "0.4.8"; + + src = fetchFromSourcehut { + owner = "~sircmpwn"; + repo = "pages.sr.ht"; + rev = version; + sha256 = "sha256-z9w8v5e6LY6VUEczltyD55KEUUH7Gw1vUO00KPmT+D8="; + }; + + vendorSha256 = "sha256-xOd9i+PNlLxZrw/+z/C9V+AbOLEociW2YHY+x1K+mJI="; + + patches = [ + # Upstream after 0.4.8 + ./pages-fix-syntax-error-in-schema.sql.patch + ]; + + 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..ecd31c25deb 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.12.4"; src = fetchFromSourcehut { owner = "~sircmpwn"; repo = "paste.sr.ht"; rev = version; - sha256 = "sha256-QQhd2LeH9BLmlHilhsv+9fZ+RPNmEMSmOpFA3dsMBFc="; + sha256 = "sha256-hFjWa7L7JiQoG3Hm9NyoP2FNypDiW+nGDmQ2DoZkAIw="; }; 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/disable-npm-install.patch b/pkgs/applications/version-management/sourcehut/patches/disable-npm-install.patch similarity index 100% rename from pkgs/applications/version-management/sourcehut/disable-npm-install.patch rename to pkgs/applications/version-management/sourcehut/patches/disable-npm-install.patch diff --git a/pkgs/applications/version-management/sourcehut/patches/redis-socket/core/v2-0001-add-Unix-socket-support-for-redis-host.patch b/pkgs/applications/version-management/sourcehut/patches/redis-socket/core/v2-0001-add-Unix-socket-support-for-redis-host.patch new file mode 100644 index 00000000000..46241bc847f --- /dev/null +++ b/pkgs/applications/version-management/sourcehut/patches/redis-socket/core/v2-0001-add-Unix-socket-support-for-redis-host.patch @@ -0,0 +1,30 @@ +From c0ccc8db051a2f8278edf59b41ed238fa71aa4c0 Mon Sep 17 00:00:00 2001 +From: Julien Moutinho +Date: Mon, 23 Aug 2021 18:43:18 +0200 +Subject: [PATCH core.sr.ht v2] add Unix socket support for redis-host= + +--- + srht/redis.py | 11 ++--------- + 1 file changed, 2 insertions(+), 9 deletions(-) + +diff --git a/srht/redis.py b/srht/redis.py +index 8a9347c..2e91c35 100644 +--- a/srht/redis.py ++++ b/srht/redis.py +@@ -1,11 +1,4 @@ +-from redis import Redis ++from redis import from_url + from srht.config import cfg +-from urllib.parse import urlparse + +-url = cfg("sr.ht", "redis-host", "redis://localhost") +-url = urlparse(url) +- +-redis = Redis(host=url.hostname, +- port=(url.port or 6379), +- password=url.password, +- db=int(url.path[1:]) if url.path else 0) ++redis = from_url(cfg("sr.ht", "redis-host", "redis://localhost")) +-- +2.32.0 + diff --git a/pkgs/applications/version-management/sourcehut/patches/redis-socket/git/v2-0001-gitsrht-keys-update-go-redis-to-support-Unix-sock.patch b/pkgs/applications/version-management/sourcehut/patches/redis-socket/git/v2-0001-gitsrht-keys-update-go-redis-to-support-Unix-sock.patch new file mode 100644 index 00000000000..24fbc26399c --- /dev/null +++ b/pkgs/applications/version-management/sourcehut/patches/redis-socket/git/v2-0001-gitsrht-keys-update-go-redis-to-support-Unix-sock.patch @@ -0,0 +1,40 @@ +From 466528eabef3123c715420472dc2cc15e8807bdf Mon Sep 17 00:00:00 2001 +From: Julien Moutinho +Date: Fri, 27 Aug 2021 15:38:28 +0200 +Subject: [PATCH git.sr.ht v2 1/5] gitsrht-keys: update go-redis to support + Unix sockets + +--- + gitsrht-keys/main.go | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/gitsrht-keys/main.go b/gitsrht-keys/main.go +index 0c1aea1..b4278c3 100644 +--- a/gitsrht-keys/main.go ++++ b/gitsrht-keys/main.go +@@ -5,7 +5,7 @@ import ( + "os" + "path" + +- goredis "github.com/go-redis/redis" ++ goRedis "github.com/go-redis/redis/v8" + "github.com/vaughan0/go-ini" + "git.sr.ht/~sircmpwn/scm.sr.ht/srht-keys" + ) +@@ -53,11 +53,11 @@ func main() { + if redisHost == "" { + redisHost = "redis://localhost:6379" + } +- ropts, err := goredis.ParseURL(redisHost) ++ ropts, err := goRedis.ParseURL(redisHost) + if err != nil { + logger.Fatalf("Failed to parse redis host: %v", err) + } +- redis := goredis.NewClient(ropts) ++ redis := goRedis.NewClient(ropts) + + keyType, b64key, prefix, err = srhtkeys.ParseArgs(logger) + if err != nil { +-- +2.32.0 + diff --git a/pkgs/applications/version-management/sourcehut/patches/redis-socket/git/v2-0002-gitsrht-update-hook-update-go-redis-to-support-Un.patch b/pkgs/applications/version-management/sourcehut/patches/redis-socket/git/v2-0002-gitsrht-update-hook-update-go-redis-to-support-Un.patch new file mode 100644 index 00000000000..36566a6f7d9 --- /dev/null +++ b/pkgs/applications/version-management/sourcehut/patches/redis-socket/git/v2-0002-gitsrht-update-hook-update-go-redis-to-support-Un.patch @@ -0,0 +1,139 @@ +From 4f947b26e42d3bcab6d675718eed28ca2fdf4762 Mon Sep 17 00:00:00 2001 +From: Julien Moutinho +Date: Fri, 27 Aug 2021 15:39:29 +0200 +Subject: [PATCH git.sr.ht v2 2/5] gitsrht-update-hook: update go-redis to + support Unix sockets + +--- + gitsrht-update-hook/options.go | 16 +++++++++------- + gitsrht-update-hook/post-update.go | 8 ++++---- + gitsrht-update-hook/update.go | 8 ++++---- + 3 files changed, 17 insertions(+), 15 deletions(-) + +diff --git a/gitsrht-update-hook/options.go b/gitsrht-update-hook/options.go +index 8efbb0a..4e9d294 100644 +--- a/gitsrht-update-hook/options.go ++++ b/gitsrht-update-hook/options.go +@@ -1,15 +1,17 @@ + package main + + import ( ++ "context" + "fmt" + "os" + "strconv" + "strings" + "time" + +- goredis "github.com/go-redis/redis" ++ goRedis "github.com/go-redis/redis/v8" + ) + ++var ctx = context.Background() + var options map[string]string + + func loadOptions() { +@@ -26,19 +28,19 @@ func loadOptions() { + if !ok { + redisHost = "redis://localhost:6379" + } +- ropts, err := goredis.ParseURL(redisHost) ++ ropts, err := goRedis.ParseURL(redisHost) + if err != nil { + logger.Fatalf("Failed to parse redis host: %v", err) + } +- redis := goredis.NewClient(ropts) ++ redis := goRedis.NewClient(ropts) + + var n int + if nopts, ok := os.LookupEnv("GIT_PUSH_OPTION_COUNT"); ok { + n, _ = strconv.Atoi(nopts) +- redis.Set(fmt.Sprintf("git.sr.ht.options.%s", uuid), ++ redis.Set(ctx, fmt.Sprintf("git.sr.ht.options.%s", uuid), + nopts, 10*time.Minute) + } else { +- nopts, err := redis.Get(fmt.Sprintf( ++ nopts, err := redis.Get(ctx, fmt.Sprintf( + "git.sr.ht.options.%s", uuid)).Result() + if err != nil { + return +@@ -51,12 +53,12 @@ func loadOptions() { + opt, ok := os.LookupEnv(fmt.Sprintf("GIT_PUSH_OPTION_%d", i)) + optkey := fmt.Sprintf("git.sr.ht.options.%s.%d", uuid, i) + if !ok { +- opt, err = redis.Get(optkey).Result() ++ opt, err = redis.Get(ctx, optkey).Result() + if err != nil { + return + } + } else { +- redis.Set(optkey, opt, 10*time.Minute) ++ redis.Set(ctx, optkey, opt, 10*time.Minute) + } + parts := strings.SplitN(opt, "=", 2) + if len(parts) == 1 { +diff --git a/gitsrht-update-hook/post-update.go b/gitsrht-update-hook/post-update.go +index d14d616..37c08b3 100644 +--- a/gitsrht-update-hook/post-update.go ++++ b/gitsrht-update-hook/post-update.go +@@ -15,7 +15,7 @@ import ( + "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/object" + "github.com/go-git/go-git/v5/plumbing/storer" +- goredis "github.com/go-redis/redis" ++ goRedis "github.com/go-redis/redis/v8" + _ "github.com/lib/pq" + ) + +@@ -210,17 +210,17 @@ func postUpdate() { + if !ok { + redisHost = "redis://localhost:6379" + } +- ropts, err := goredis.ParseURL(redisHost) ++ ropts, err := goRedis.ParseURL(redisHost) + if err != nil { + logger.Fatalf("Failed to parse redis host: %v", err) + } + nbuilds := 0 +- redis := goredis.NewClient(ropts) ++ redis := goRedis.NewClient(ropts) + for i, refname := range refs { + var oldref, newref string + var oldobj, newobj object.Object + updateKey := fmt.Sprintf("update.%s.%s", pushUuid, refname) +- update, err := redis.Get(updateKey).Result() ++ update, err := redis.Get(ctx, updateKey).Result() + if update == "" || err != nil { + logger.Println("redis.Get: missing key") + continue +diff --git a/gitsrht-update-hook/update.go b/gitsrht-update-hook/update.go +index 72c661a..0968cfb 100644 +--- a/gitsrht-update-hook/update.go ++++ b/gitsrht-update-hook/update.go +@@ -5,7 +5,7 @@ import ( + "os" + "time" + +- goredis "github.com/go-redis/redis" ++ goRedis "github.com/go-redis/redis/v8" + ) + + // XXX: This is run once for every single ref that's pushed. If someone pushes +@@ -26,11 +26,11 @@ func update() { + if !ok { + redisHost = "redis://localhost:6379" + } +- ropts, err := goredis.ParseURL(redisHost) ++ ropts, err := goRedis.ParseURL(redisHost) + if err != nil { + logger.Fatalf("Failed to parse redis host: %v", err) + } +- redis := goredis.NewClient(ropts) +- redis.Set(fmt.Sprintf("update.%s.%s", pushUuid, refname), ++ redis := goRedis.NewClient(ropts) ++ redis.Set(ctx, fmt.Sprintf("update.%s.%s", pushUuid, refname), + fmt.Sprintf("%s:%s", oldref, newref), 10*time.Minute) + } +-- +2.32.0 + diff --git a/pkgs/applications/version-management/sourcehut/patches/redis-socket/git/v2-0003-gitsrht-dispatch-add-support-for-supplementary-gr.patch b/pkgs/applications/version-management/sourcehut/patches/redis-socket/git/v2-0003-gitsrht-dispatch-add-support-for-supplementary-gr.patch new file mode 100644 index 00000000000..a96f6430b56 --- /dev/null +++ b/pkgs/applications/version-management/sourcehut/patches/redis-socket/git/v2-0003-gitsrht-dispatch-add-support-for-supplementary-gr.patch @@ -0,0 +1,57 @@ +From ef02ce68925888b2bca77713c6321cb33023e026 Mon Sep 17 00:00:00 2001 +From: Julien Moutinho +Date: Fri, 27 Aug 2021 17:42:33 +0200 +Subject: [PATCH git.sr.ht v2 3/5] gitsrht-dispatch: add support for + supplementary groups + +--- + gitsrht-dispatch/main.go | 17 ++++++++++++++--- + 1 file changed, 14 insertions(+), 3 deletions(-) + +diff --git a/gitsrht-dispatch/main.go b/gitsrht-dispatch/main.go +index d7aee14..5f17b75 100644 +--- a/gitsrht-dispatch/main.go ++++ b/gitsrht-dispatch/main.go +@@ -17,6 +17,7 @@ type Dispatcher struct { + cmd string + uid int + gid int ++ gids []int + } + + func main() { +@@ -70,11 +71,20 @@ AuthorizedKeysUser=root`, os.Args[0]) + if err != nil { + logger.Fatalf("Error looking up group %s: %v", spec[1], err) + } ++ groups, err := user.GroupIds() ++ if err != nil { ++ logger.Fatalf("Error looking up supplementary groups of user %s: %v", spec[0], err) ++ } ++ gids := make([]int, len(groups)) ++ for i, grp := range groups { ++ sgid, _ := strconv.Atoi(grp) ++ gids[i] = sgid ++ } + uid, _ := strconv.Atoi(user.Uid) + gid, _ := strconv.Atoi(group.Gid) +- dispatchers[uid] = Dispatcher{cmd, uid, gid} +- logger.Printf("Registered dispatcher for %s(%d):%s(%d): %s", +- spec[0], uid, spec[1], gid, cmd) ++ dispatchers[uid] = Dispatcher{cmd, uid, gid, gids} ++ logger.Printf("Registered dispatcher for %s(%d):%s(%d):(%s): %s", ++ spec[0], uid, spec[1], gid, strings.Join(groups, ","), cmd) + } + + var user *osuser.User +@@ -93,6 +103,7 @@ AuthorizedKeysUser=root`, os.Args[0]) + + if dispatcher, ok := dispatchers[uid]; ok { + logger.Printf("Dispatching to %s", dispatcher.cmd) ++ syscall.Setgroups(dispatcher.gids) + syscall.Setgid(dispatcher.gid) + syscall.Setuid(dispatcher.uid) + if err := syscall.Exec(dispatcher.cmd, append([]string{ +-- +2.32.0 + diff --git a/pkgs/applications/version-management/sourcehut/patches/redis-socket/scm/v2-0001-srht-keys-update-go-redis-to-support-Unix-sockets.patch b/pkgs/applications/version-management/sourcehut/patches/redis-socket/scm/v2-0001-srht-keys-update-go-redis-to-support-Unix-sockets.patch new file mode 100644 index 00000000000..389cd71751a --- /dev/null +++ b/pkgs/applications/version-management/sourcehut/patches/redis-socket/scm/v2-0001-srht-keys-update-go-redis-to-support-Unix-sockets.patch @@ -0,0 +1,74 @@ +From 538ff956141f5b56b77233664d4d4ea5eff8ad08 Mon Sep 17 00:00:00 2001 +From: Julien Moutinho +Date: Fri, 27 Aug 2021 12:48:56 +0200 +Subject: [PATCH v2 1/2] srht-keys: update go-redis to support Unix sockets + +--- + srht-keys/srhtkeys.go | 13 ++++++++----- + 1 file changed, 8 insertions(+), 5 deletions(-) + +diff --git a/srht-keys/srhtkeys.go b/srht-keys/srhtkeys.go +index be925ed..4cc144c 100644 +--- a/srht-keys/srhtkeys.go ++++ b/srht-keys/srhtkeys.go +@@ -1,6 +1,7 @@ + package srhtkeys + + import ( ++ "context" + "database/sql" + "encoding/json" + "errors" +@@ -12,7 +13,7 @@ import ( + "path" + "time" + +- goredis "github.com/go-redis/redis" ++ goRedis "github.com/go-redis/redis/v8" + "github.com/google/uuid" + _ "github.com/lib/pq" + "github.com/vaughan0/go-ini" +@@ -37,6 +38,8 @@ type MetaSSHKey struct { + Owner MetaUser `json:"owner"` + } + ++var ctx = context.Background() ++ + // Stores the SSH key in the database and returns the user's ID. + func storeKey(logger *log.Logger, db *sql.DB, key *MetaSSHKey) (int, error) { + logger.Println("Storing meta.sr.ht key in database") +@@ -84,7 +87,7 @@ func storeKey(logger *log.Logger, db *sql.DB, key *MetaSSHKey) (int, error) { + } + + func fetchKeysFromMeta(logger *log.Logger, config ini.File, +- redis *goredis.Client, service string, b64key string) (string, int) { ++ redis *goRedis.Client, service string, b64key string) (string, int) { + + meta, ok := config.Get("meta.sr.ht", "internal-origin") + if !ok { +@@ -145,7 +148,7 @@ func fetchKeysFromMeta(logger *log.Logger, config ini.File, + if err != nil { + logger.Printf("Caching SSH key in redis failed: %v", err) + } else { +- redis.Set(cacheKey, cacheBytes, 7*24*time.Hour) ++ redis.Set(ctx, cacheKey, cacheBytes, 7*24*time.Hour) + } + + return key.Owner.Username, userId +@@ -164,11 +167,11 @@ func ParseArgs(logger *log.Logger) (string, string, string, error) { + } + + func UserFromKey(logger *log.Logger, config ini.File, +- redis *goredis.Client, service string, b64key string) (string, int) { ++ redis *goRedis.Client, service string, b64key string) (string, int) { + + cacheKey := fmt.Sprintf("%s.ssh-keys.%s", service, b64key) + logger.Printf("Cache key for SSH key lookup: %s", cacheKey) +- cacheBytes, err := redis.Get(cacheKey).Bytes() ++ cacheBytes, err := redis.Get(ctx, cacheKey).Bytes() + var ( + username string + userId int +-- +2.32.0 + diff --git a/pkgs/applications/version-management/sourcehut/patches/redis-socket/scm/v2-0002-srht-keys-update-go.-mod-sum-for-go-redis.patch b/pkgs/applications/version-management/sourcehut/patches/redis-socket/scm/v2-0002-srht-keys-update-go.-mod-sum-for-go-redis.patch new file mode 100644 index 00000000000..8db7dc674c7 --- /dev/null +++ b/pkgs/applications/version-management/sourcehut/patches/redis-socket/scm/v2-0002-srht-keys-update-go.-mod-sum-for-go-redis.patch @@ -0,0 +1,155 @@ +From d0862969b1470701edded4772337822ca52c8509 Mon Sep 17 00:00:00 2001 +From: Julien Moutinho +Date: Fri, 27 Aug 2021 13:06:27 +0200 +Subject: [PATCH v2 2/2] srht-keys: update go.{mod,sum} for go-redis + +--- + srht-keys/go.mod | 2 +- + srht-keys/go.sum | 103 ++++++++++++++++++++++++++++++++++++++++++++--- + 2 files changed, 99 insertions(+), 6 deletions(-) + +diff --git a/srht-keys/go.mod b/srht-keys/go.mod +index d275913..8d1c10a 100644 +--- a/srht-keys/go.mod ++++ b/srht-keys/go.mod +@@ -4,7 +4,7 @@ go 1.13 + + require ( + git.sr.ht/~sircmpwn/core-go v0.0.0-20201005173246-a9e49d17a1e6 +- github.com/go-redis/redis v6.15.9+incompatible ++ github.com/go-redis/redis/v8 v8.11.3 + github.com/google/uuid v1.1.1 + github.com/lib/pq v1.8.0 + github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec +diff --git a/srht-keys/go.sum b/srht-keys/go.sum +index 974326e..a264a26 100644 +--- a/srht-keys/go.sum ++++ b/srht-keys/go.sum +@@ -1,26 +1,119 @@ +-git.sr.ht/~sircmpwn/core-go v0.0.0-20200820135923-98806e712f5e h1:TJqf/neVU5peFAS9WcR1aADXcflPOvAd7ABEirmU7m0= +-git.sr.ht/~sircmpwn/core-go v0.0.0-20200820135923-98806e712f5e/go.mod h1:aXSNgRsGoI3tTFKlwD0xm2htbEzKlR2xUm1osRxfhOM= + git.sr.ht/~sircmpwn/core-go v0.0.0-20201005173246-a9e49d17a1e6 h1:Ky6HzcRmbMUxOrWXv04+mb97GkyxO/Nx7v8uJBUdpNk= + git.sr.ht/~sircmpwn/core-go v0.0.0-20201005173246-a9e49d17a1e6/go.mod h1:HpPX22ilJUWKOA4NDhrOcIyblQhdiKHPg4oMJFYdh0Y= ++github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= ++github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= + github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= ++github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= ++github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= ++github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= ++github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= ++github.com/fernet/fernet-go v0.0.0-20191111064656-eff2850e6001 h1:/UMxx5lGDg30aioUL9e7xJnbJfJeX7vhcm57fa5udaI= + github.com/fernet/fernet-go v0.0.0-20191111064656-eff2850e6001/go.mod h1:2H9hjfbpSMHwY503FclkV/lZTBh2YlOmLLSda12uL8c= +-github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg= +-github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= ++github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= ++github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= ++github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= ++github.com/go-redis/redis/v8 v8.11.3 h1:GCjoYp8c+yQTJfc0n69iwSiHjvuAdruxl7elnZCxgt8= ++github.com/go-redis/redis/v8 v8.11.3/go.mod h1:xNJ9xDG09FsIPwh3bWdk+0oDWHbtF9rPN0F/oD9XeKc= ++github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= ++github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= ++github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= ++github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= ++github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= ++github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= ++github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= ++github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= ++github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= ++github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= ++github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= ++github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= ++github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= ++github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= ++github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= ++github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= ++github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= + github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= + github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= ++github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= + github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg= + github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= ++github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= ++github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= ++github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= ++github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= ++github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= ++github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= ++github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= ++github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= ++github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= ++github.com/onsi/gomega v1.15.0 h1:WjP/FQ/sk43MRmnEcT+MlDw2TFvkrXlprrPST/IudjU= ++github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= ++github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= + github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= + github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= ++github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= ++github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= + github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= + github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec h1:DGmKwyZwEB8dI7tbLt/I/gQuP559o/0FrAkHKlQM/Ks= + github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec/go.mod h1:owBmyHYMLkxyrugmfwE/DLJyW8Ro9mkphwuVErQ0iUw= ++github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= + golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +-golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig= ++golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= ++golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= + golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= ++golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= ++golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= + golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= ++golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= ++golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= ++golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= ++golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0= ++golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= ++golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= ++golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= ++golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= ++golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= + golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= + golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= ++golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= ++golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= ++golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= ++golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= ++golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= ++golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= ++golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= ++golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c= ++golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= ++golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= + golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= ++golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= ++golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= ++golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= ++golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= ++golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= ++golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= ++golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= ++golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= ++golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= ++golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= ++golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= ++google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= ++google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= ++google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= ++google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= ++google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= ++google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= ++google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= ++google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= ++google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= ++gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= + gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= ++gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= ++gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= ++gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= ++gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= ++gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= ++gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= ++gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= ++gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= ++gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= + gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +-- +2.32.0 + diff --git a/pkgs/applications/version-management/sourcehut/patches/srht-update-profiles/0001-fix-disgusting-hack-in-the-case-of-buildsrht.patch b/pkgs/applications/version-management/sourcehut/patches/srht-update-profiles/0001-fix-disgusting-hack-in-the-case-of-buildsrht.patch new file mode 100644 index 00000000000..9db31c9e554 --- /dev/null +++ b/pkgs/applications/version-management/sourcehut/patches/srht-update-profiles/0001-fix-disgusting-hack-in-the-case-of-buildsrht.patch @@ -0,0 +1,25 @@ +From 8bb7c927c815582b26d026f4b2ea72f0245ccab7 Mon Sep 17 00:00:00 2001 +From: Julien Moutinho +Date: Mon, 23 Aug 2021 18:45:07 +0200 +Subject: [PATCH core.sr.ht] fix "disgusting hack" in the case of buildsrht + +--- + srht-update-profiles | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/srht-update-profiles b/srht-update-profiles +index 0d9588c..063d6e2 100755 +--- a/srht-update-profiles ++++ b/srht-update-profiles +@@ -3,7 +3,7 @@ import sys + import os + sys.path.append(os.getcwd()) + site = sys.argv[1] +-app = __import__(site.replace(".", "") + ".app").app.app # disgusting hack ++app = __import__(site.replace(".", "").replace("builds","build") + ".app").app.app # disgusting hack + from srht.config import cfg + from srht.database import db, DbSession + db = DbSession(cfg(site, "connection-string")) +-- +2.32.0 + diff --git a/pkgs/applications/version-management/sourcehut/scm.nix b/pkgs/applications/version-management/sourcehut/scm.nix index 1f385265360..6737251f833 100644 --- a/pkgs/applications/version-management/sourcehut/scm.nix +++ b/pkgs/applications/version-management/sourcehut/scm.nix @@ -1,22 +1,59 @@ { lib , fetchFromSourcehut +, buildGoModule , buildPythonPackage , srht , redis , pyyaml , buildsrht -, writeText +, applyPatches }: buildPythonPackage rec { pname = "scmsrht"; - version = "0.22.9"; + version = "0.22.13"; src = fetchFromSourcehut { owner = "~sircmpwn"; repo = "scm.sr.ht"; rev = version; - sha256 = "sha256-327G6C8FW+iZx+167D7TQsFtV6FGc8MpMVo9L/cUUqU="; + sha256 = "sha256-9iRmQBt4Cxr5itTk34b8iDRyXYDHTDfZjV0SIDT/kkM="; + }; + + passthru = { + srht-keys = buildGoModule rec { + inherit src version; + sourceRoot = "source/srht-keys"; + pname = "srht-keys"; + vendorSha256 = "sha256-lQk1dymMCefHMFJhO3yC/muBP/cxI//5Yz991D2YZY4="; + + # What follows is only to update go-redis + # go.{mod,sum} could be patched directly but that would be less resilient + # to changes from upstream, and thus harder to maintain the patching + # while it hasn't been merged upstream. + + overrideModAttrs = old: { + inherit patches patchFlags; + preBuild = '' + go get github.com/go-redis/redis/v8 + go get github.com/go-redis/redis@none + go mod tidy + ''; + # Pass updated go.{mod,sum} from go-modules to srht-keys's vendor/go.{mod,sum} + postInstall = '' + cp --reflink=auto go.* $out + ''; + }; + + patches = [ + # Update go-redis to support Unix sockets + patches/redis-socket/scm/v2-0001-srht-keys-update-go-redis-to-support-Unix-sockets.patch + ]; + patchFlags = "-p2"; + postInstall = '' + cp --reflink=auto *.go vendor/go.* $out + ''; + }; }; nativeBuildInputs = srht.nativeBuildInputs; @@ -33,11 +70,12 @@ buildPythonPackage rec { ''; dontUseSetuptoolsCheck = true; + pythonImportsCheck = [ "scmsrht" ]; meta = with lib; { homepage = "https://git.sr.ht/~sircmpwn/git.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..e091341c7dd 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.64.24"; src = fetchFromSourcehut { owner = "~sircmpwn"; repo = "todo.sr.ht"; rev = version; - sha256 = "sha256-huIAhn6h1F5w5ST4/yBwr82kAzyYwhLu+gpRuOQgnsE="; + sha256 = "sha256-H2XGOxHyurXw3GekZJXSO6RMChRjNbjqxik/mvFVqfY="; }; 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..6733046d000 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 -x -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,19 +16,18 @@ 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 } @@ -36,19 +38,33 @@ update_version() { (cd "$root" && update-source-version "sourcehut.python.pkgs.$1" "$version") + # Update vendorSha256 of Go modules + nixFile="${1%srht}".nix + nixFile="${nixFile/build/builds}" + 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|$(nix hash to-sri --type sha256 "$hash")|" "$nixFile"; retry=true; break;; + (*) printf >&2 "%s\n" "$origin${hash:+:$hash}" + esac + done + done + git add "$default_nix" - git commit -m "$1: $version_old -> $version" + git commit -m "sourcehut.$1: $version_old -> $version" } -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 + services=( "srht" "buildsrht" "dispatchsrht" "gitsrht" "hgsrht" "hubsrht" "listssrht" "mansrht" + "metasrht" "pagessrht" "pastesrht" "todosrht" "scmsrht" ) fi + +for service in "${services[@]}"; do + update_version "$service" +done