with lib;
let
- inherit (config.services) postgresql redis;
+ inherit (config.services) postgresql;
+ redis = config.services.redis.servers."sourcehut-${srvsrht}";
inherit (config.users) users;
cfg = config.services.sourcehut;
configIni = configIniOfService srv;
rootDir = "/run/sourcehut/chroots/${serviceName}";
in
mkMerge [ extraService {
- after = [ "network.target" ]
- ++ optional cfg.postgresql.enable "postgresql.service"
- ++ optional cfg.redis.enable "redis.service";
+ 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.service";
+ optional cfg.postgresql.enable "postgresql.service" ++
+ optional cfg.redis.enable "redis-sourcehut-${srvsrht}.service";
path = [ pkgs.gawk ];
environment.HOME = runDir;
serviceConfig = {
Group = mkDefault srvCfg.group;
RuntimeDirectory = [
"sourcehut/${serviceName}"
+ # Used by *srht-keys which reads ../config.ini
+ "sourcehut/${serviceName}/subdir"
"sourcehut/chroots/${serviceName}"
];
RuntimeDirectoryMode = "2750";
"/run/systemd"
] ++
optional cfg.postgresql.enable "/run/postgresql" ++
- optional cfg.redis.enable "/run/redis";
+ 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.
'';
};
+ 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.
+ '';
+ };
+ };
+
postgresql = {
database = mkOption {
type = types.str;
'';
};
};
+
+ 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 <literal>celeryconfig.py</literal> used by the Celery responsible for webhooks.";
+ };
+ };
};
config = lib.mkIf (cfg.enable && srvCfg.enable) (mkMerge [ extraConfig {
} // 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 ];
};
};
services.postgresql = mkIf cfg.postgresql.enable {
authentication = ''
local ${srvCfg.postgresql.database} ${srvCfg.user} trust
- ''
- # shrt-keys stores SSH keys in the PostgreSQL database of the service calling it
- + optionalString (elem srv ["builds" "git" "hg"]) ''
- local ${srvCfg.postgresql.database} ${users."sshsrht".name} trust
'';
ensureDatabases = [ srvCfg.postgresql.database ];
ensureUsers = map (name: {
inherit name;
ensurePermissions = { "DATABASE \"${srvCfg.postgresql.database}\"" = "ALL PRIVILEGES"; };
- }) ([srvCfg.user] ++ optional (elem srv ["builds" "git" "hg"]) users."sshsrht".name);
+ }) [srvCfg.user];
};
services.sourcehut.services = mkDefault (filter (s: cfg.${s}.enable)
services.sourcehut.settings = mkMerge [
{
- "${srv}.sr.ht" = {
- origin = mkDefault "https://${srv}.${cfg.settings."sr.ht".global-domain}";
- };
+ "${srv}.sr.ht".origin = mkDefault "https://${srv}.${cfg.settings."sr.ht".global-domain}";
}
(mkIf cfg.postgresql.enable {
})
];
+ 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 [
#RestartSec = mkDefault "2min";
StateDirectory = [ "sourcehut/${srvsrht}" ];
StateDirectoryMode = "2750";
- ExecStart = "${cfg.python}/bin/gunicorn ${srvsrht}.app:app -b ${cfg.listenAddress}:${toString srvCfg.port}";
+ 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;
"${srvsrht}-webhooks" = baseService "${srvsrht}-webhooks" {}
{
description = "sourcehut ${srv}.sr.ht webhooks service";
- before = [ "${srvsrht}.service" ];
- requiredBy = [ "${srvsrht}.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";
- # --queues enables sharing the Redis database amongst different celery workers
- ExecStart = "${cfg.python}/bin/celery -A ${srvsrht}.webhooks worker --queues sourcehut.${srvsrht}-webhooks --loglevel INFO --pool eventlet";
+ ExecStart = "${cfg.python}/bin/celery --app ${srvsrht}.webhooks worker --hostname ${srvsrht}-webhooks@%%h " + concatStringsSep " " srvCfg.webhooks.extraArgs;
# Avoid crashing: os.getloadavg()
ProcSubset = mkForce "all";
- InaccessiblePaths = [ "-+/run/postgresql" ];
};
};
})
description = "sourcehut ${serviceName} service";
# So that extraServices have the PostgreSQL database initialized.
after = [ "${srvsrht}.service" ];
- requiredBy = [ "${srvsrht}.service" ];
+ wantedBy = [ "${srvsrht}.service" ];
+ partOf = [ "${srvsrht}.service" ];
serviceConfig = {
Type = "simple";
Restart = mkDefault "always";