if srvMatch == null # Include sections shared by all services
|| head srvMatch == srv # Include sections for the service being configured
then v
- # *srht-{dispatch,keys,shell,update-hook} share the same config.ini
- else if srv == "ssh" && elem (head srvMatch) ["builds" "git" "hg"] && cfg.${head srvMatch}.enable then v
# Enable Web links and integrations between services.
else if tail srvMatch == [ null ] && elem (head srvMatch) cfg.services
then {
"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}.localhost.localdomain";
+ default = "https://${srv}.${domain}";
+ defaultText = "https://${srv}.example.com";
};
debug-host = mkOption {
description = "Address to bind the debug server to.";
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
nginx = {
enable = mkEnableOption ''local nginx integration'';
+ virtualHost = mkOption {
+ type = types.attrs;
+ default = {};
+ description = "Virtual-host configuration merged with all Sourcehut's virtual-hosts.";
+ };
};
postfix = {
};
redis = {
- enable = mkEnableOption ''local redis integration'';
- firstDatabase = mkOption {
- type = types.int;
- default = 0;
- description = ''
- Number of the first Redis database to use.
- At most 9 consecutive databases are currently used.
- '';
- };
+ enable = mkEnableOption ''local redis integration in a dedicated redis-server'';
};
settings = mkOption {
type = types.str;
default = "John Doe";
};
- redis-host = mkOption {
- type = with types; nullOr str;
- description = ''
- The redis host URL. This is used for caching and temporary storage, and must
- be shared between nodes (e.g. g - 1it1.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.
- '';
- };
site-blurb = mkOption {
description = "Blurb for your site.";
type = types.str;
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.";
+ description = "The Redis connection used for the Celery worker.";
type = types.str;
- default = "redis://localhost:6379/3";
+ default = "redis+socket:///run/redis-sourcehut-buildsrht/redis.sock?virtual_host=2";
};
shell = mkOption {
description = ''
# 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" ''
+ set -e
test -e "''${PWD%/*}"/config.ini ||
- ln -s ${users."sshsrht".home}/../config.ini "''${PWD%/*}"/config.ini
+ ln -s /run/sourcehut/gitsrht/config.ini "''${PWD%/*}"/config.ini
exec -a "$0" '${p}' "$@"
'';
};
default = "/var/lib/sourcehut/gitsrht/repos";
};
webhooks = mkOption {
- description = "The redis connection used for the webhooks worker.";
+ description = "The Redis connection used for the webhooks worker.";
type = types.str;
- default = "redis://localhost:6379/1";
+ default = "redis+socket:///run/redis-sourcehut-gitsrht/redis.sock?virtual_host=1";
+ };
+ };
+ options."git.sr.ht::api" = {
+ internal-ipnet = mkOption {
+ description = ''
+ Set of IP subnets which are permitted to utilize internal API
+ authentication. This should be limited to the subnets
+ from which your *.sr.ht services are running.
+ See <xref linkend="opt-services.sourcehut.listenAddress"/>.
+ '';
+ type = with types; listOf str;
+ default = [ "127.0.0.0/8" "::1/128" ];
};
};
# 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" ''
+ set -e
test -e "''$PWD"/config.ini ||
- ln -s ${users."sshsrht".home}/../config.ini "''$PWD"/config.ini
+ ln -s /run/sourcehut/hgsrht/config.ini "''$PWD"/config.ini
exec -a "$0" '${p}' "$@"
'';
};
defaultText = "\${pkgs.mercurial}/bin/hg-ssh";
};
webhooks = mkOption {
- description = "The redis connection used for the webhooks worker.";
+ description = "The Redis connection used for the webhooks worker.";
type = types.str;
- default = "redis://localhost:6379/8";
+ default = "redis+socket:///run/redis-sourcehut-hgsrht/redis.sock?virtual_host=1";
};
};
default = "lists.localhost.localdomain";
};
redis = mkOption {
- description = "The redis connection used for the celery worker.";
+ description = "The Redis connection used for the Celery worker.";
type = types.str;
- default = "redis://localhost:6379/4";
+ default = "redis+socket:///run/redis-sourcehut-listssrht/redis.sock?virtual_host=2";
};
webhooks = mkOption {
- description = "The redis connection used for the webhooks worker.";
+ description = "The Redis connection used for the webhooks worker.";
type = types.str;
- default = "redis://localhost:6379/2";
+ default = "redis+socket:///run/redis-sourcehut-listssrht/redis.sock?virtual_host=1";
};
};
options."lists.sr.ht::worker" = {
api-origin = mkOption {
description = "Origin URL for API, 100 more than web.";
type = types.str;
- default = "http://localhost:5100";
+ default = "http://${cfg.listenAddress}:${toString (cfg.meta.port + 100)}";
+ defaultText = ''http://<xref linkend="opt-services.sourcehut.listenAddress"/>:''${toString (<xref linkend="opt-services.sourcehut.meta.port"/> + 100)}'';
};
webhooks = mkOption {
- description = "The redis connection used for the webhooks worker.";
+ description = "The Redis connection used for the webhooks worker.";
type = types.str;
- default = "redis://localhost:6379/6";
+ default = "redis+socket:///run/redis-sourcehut-metasrht/redis.sock?virtual_host=1";
};
welcome-emails = mkEnableOption "sending stock sourcehut welcome emails after signup";
};
- options."meta.sr.ht::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 {
+ options."meta.sr.ht::api" = {
+ internal-ipnet = mkOption {
description = ''
- How many invites each user is issued upon registration
- (only applicable if open registration is disabled).
+ 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 <xref linkend="opt-services.sourcehut.listenAddress"/>.
'';
- type = types.ints.unsigned;
- default = 5;
+ type = with types; listOf str;
+ default = [ "127.0.0.0/8" "::1/128" ];
};
};
options."meta.sr.ht::aliases" = mkOption {
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 {
};
};
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 <xref linkend="opt-services.sourcehut.listenAddress"/>.
+ '';
+ type = with types; listOf str;
+ default = [ "127.0.0.0/8" "::1/128" ];
+ };
};
options."paste.sr.ht" = commonServiceSettings "paste" // {
- webhooks = mkOption {
- description = "The redis connection used for the webhooks worker.";
- type = types.str;
- default = "redis://localhost:6379/5";
- };
};
options."todo.sr.ht" = commonServiceSettings "todo" // {
default = "todo-notify@localhost.localdomain";
};
webhooks = mkOption {
- description = "The redis connection used for the webhooks worker.";
+ description = "The Redis connection used for the webhooks worker.";
type = types.str;
- default = "redis://localhost:6379/7";
+ default = "redis+socket:///run/redis-sourcehut-todosrht/redis.sock?virtual_host=1";
};
};
options."todo.sr.ht::mail" = {
'';
};
fcgiwrap.preforkProcess = mkOption {
- description = "Number of processes to prefork.";
+ description = "Number of fcgiwrap processes to prefork.";
type = types.int;
default = 4;
};
'';
};
};
+
+ 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 <literal>celeryconfig.py</literal> used by the Celery of <literal>listssrht-process</literal>.";
+ };
+ };
+ };
};
config = mkIf cfg.enable (mkMerge [
};
}
(mkIf cfg.postgresql.enable {
- services.postgresql.enable = true;
+ assertions = [
+ { assertion = postgresql.enable;
+ message = "postgresql must be enabled and configured";
+ }
+ ];
})
(mkIf cfg.postfix.enable {
- services.postfix.enable = true;
+ 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.enable = true;
- services.sourcehut.settings."sr.ht".redis-host = mkDefault ("redis://localhost:6379/" + toString cfg.redis.firstDatabase);
+ services.redis.vmOverCommit = mkDefault true;
})
(mkIf cfg.nginx.enable {
- services.nginx.enable = true;
+ 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
- authorizedKeysCommand = ''/etc/ssh/srht-dispatch "%u" "%h" "%t" "%k"'';
- # The sshsrht-dispatch user needs:
- # 1. to read ${users."sshsrht".home}/../config.ini,
- # 2. to access the redis server in redis-host,
- # 3. to access the postgresql server in the service's connection-string,
- # 4. to query metasrht-api (through the HTTP API).
- # Note that *srht-{dispatch,keys,shell,update-hook} will likely fail
- # to write their log on /var/log with that user, and will fallback to stderr,
- # making their log visible in sshd's log when sshd is in debug mode (-d).
- # Alternatively, you can touch and chown sshsrht /var/log/gitsrht-{dispatch,keys,shell,update-hook}
- # during your debug.
- authorizedKeysCommandUser = users."sshsrht".name;
+ # Note that sshd will continue to honor AuthorizedKeysFile.
+ # Note that you may want automatically rotate
+ # or link to /dev/null the following log files:
+ # - /var/log/gitsrht-dispatch
+ # - /var/log/{build,git,hg}srht-keys
+ # - /var/log/{git,hg}srht-shell
+ # - /var/log/gitsrht-update-hook
+ authorizedKeysCommand = ''/etc/ssh/sourcehut/subdir/srht-dispatch "%u" "%h" "%t" "%k"'';
+ # srht-dispatch will setuid/setgid according to [git.sr.ht::dispatch]
+ authorizedKeysCommandUser = "root";
extraConfig = ''
PermitUserEnvironment SRHT_*
'';
};
- environment.etc."ssh/srht-dispatch" = {
+ environment.etc."ssh/sourcehut/config.ini".source =
+ settingsFormat.generate "sourcehut-dispatch-config.ini"
+ (filterAttrs (k: v: k == "git.sr.ht::dispatch")
+ cfg.settings);
+ environment.etc."ssh/sourcehut/subdir/srht-dispatch" = {
# sshd_config(5): The program must be owned by root, not writable by group or others
mode = "0755";
source = pkgs.writeShellScript "srht-dispatch" ''
set -e
- cd ${users."sshsrht".home}
- exec ${cfg.python}/bin/gitsrht-dispatch "$@"
+ cd /etc/ssh/sourcehut/subdir
+ ${cfg.python}/bin/gitsrht-dispatch "$@"
'';
};
- systemd.services.sshd = let configIni = configIniOfService "ssh"; in {
+ systemd.services.sshd = {
#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 [*.sr.ht::dispatch] settings.
+ # for instance to get the user from the [git.sr.ht::dispatch] settings.
+ # *srht-keys needs to:
+ # - access a redis-server in [sr.ht] redis-host,
+ # - access the PostgreSQL server in [*.sr.ht] connection-string,
+ # - query metasrht-api (through the HTTP API).
+ # Using this has the side effect of creating empty files in /usr/bin/
optionals cfg.builds.enable [
- "${pkgs.sourcehut.buildsrht}/bin/buildsrht-keys:/usr/bin/buildsrht-keys"
+ "${pkgs.writeShellScript "buildsrht-keys-wrapper" ''
+ set -ex
+ cd /run/sourcehut/buildsrht/subdir
+ exec -a "$0" ${pkgs.sourcehut.buildsrht}/bin/buildsrht-keys "$@"
+ ''}:/usr/bin/buildsrht-keys"
"${pkgs.sourcehut.buildsrht}/bin/master-shell:/usr/bin/master-shell"
"${pkgs.sourcehut.buildsrht}/bin/runner-shell:/usr/bin/runner-shell"
] ++
optionals cfg.git.enable [
- "${pkgs.sourcehut.gitsrht}/bin/gitsrht-keys:/usr/bin/gitsrht-keys"
- "${pkgs.sourcehut.gitsrht}/bin/gitsrht-shell:/usr/bin/gitsrht-shell"
+ # /path/to/gitsrht-keys calls /path/to/gitsrht-shell,
+ # or [git.sr.ht] shell= if set.
+ "${pkgs.writeShellScript "gitsrht-keys-wrapper" ''
+ set -ex
+ cd /run/sourcehut/gitsrht/subdir
+ exec -a "$0" ${pkgs.sourcehut.gitsrht}/bin/gitsrht-keys "$@"
+ ''}:/usr/bin/gitsrht-keys"
+ "${pkgs.writeShellScript "gitsrht-shell-wrapper" ''
+ set -e
+ cd /run/sourcehut/gitsrht/subdir
+ exec -a "$0" ${pkgs.sourcehut.gitsrht}/bin/gitsrht-shell "$@"
+ ''}:/usr/bin/gitsrht-shell"
] ++
optionals cfg.hg.enable [
- "${pkgs.sourcehut.hgsrht}/bin/hgsrht-keys:/usr/bin/htsrht-keys"
- "${pkgs.sourcehut.hgsrht}/bin/hgsrht-shell:/usr/bin/htsrht-shell"
+ # /path/to/hgsrht-keys calls /path/to/hgsrht-shell,
+ # or [hg.sr.ht] shell= if set.
+ "${pkgs.writeShellScript "hgsrht-keys-wrapper" ''
+ set -ex
+ cd /run/sourcehut/hgsrht/subdir
+ exec -a "$0" ${pkgs.sourcehut.hgsrht}/bin/hgsrht-keys "$@"
+ ''}:/usr/bin/hgsrht-keys"
+ ":/usr/bin/hgsrht-shell"
+ "${pkgs.writeShellScript "hgsrht-shell-wrapper" ''
+ set -e
+ cd /run/sourcehut/hgsrht/subdir
+ exec -a "$0" ${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 =
- # Unfortunately, AuthorizedKeysCommandUser does not honor supplementary groups,
- # hence the main group is used.
- if cfg.postgresql.enable
- && hasSuffix "0" (postgresql.settings.unix_socket_permissions or "")
- then groups.postgres.name
- else groups.nogroup.name;
- description = "sourcehut user for sshd's AuthorizedKeysCommandUser";
};
- groups."sshsrht" = {};
};
})
]);
imports = [
+
(import ./service.nix "builds" {
inherit configIniOfService;
srvsrht = "buildsrht";
port = 5002;
- redisDatabase = 3;
+ # TODO: a celery worker on the master and worker are apparently needed
extraServices.buildsrht-worker = let
qemuPackage = pkgs.qemu_kvm;
serviceName = "buildsrht-worker";
'';
in mkMerge [
{
- users.users.${cfg.builds.user} = {
- shell = pkgs.bash;
- # Allow reading of ${users."sshsrht".home}/../config.ini
- extraGroups = [ groups."sshsrht".name ];
- };
+ users.users.${cfg.builds.user}.shell = pkgs.bash;
virtualisation.docker.enable = true;
services.sourcehut.settings = mkMerge [
- { # Register the builds.sr.ht dispatcher
+ { # 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}";
}
systemd.services.nginx = {
serviceConfig.BindReadOnlyPaths = [ "${cfg.settings."builds.sr.ht::worker".buildlogs}:/var/log/nginx/buildsrht/logs" ];
};
- services.nginx.virtualHosts."logs.${domain}" = {
+ services.nginx.virtualHosts."logs.${domain}" = mkMerge [ {
/* FIXME: is a listen needed?
listen = with builtins;
# FIXME: not compatible with IPv6
[{ 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" ];
};
- mainService = mkMerge [ baseService {
- serviceConfig.StateDirectory = [ "sourcehut/gitsrht" ];
- } ];
in {
inherit configIniOfService;
- inherit mainService;
+ mainService = mkMerge [ baseService {
+ serviceConfig.StateDirectory = [ "sourcehut/gitsrht" "sourcehut/gitsrht/repos" ];
+ } ];
port = 5001;
- webhooks.redisDatabase = 1;
+ webhooks = true;
extraTimers.gitsrht-periodic = {
- service = mainService;
- timerConfig = {
- OnCalendar = ["20min"];
- };
+ 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;
- };
+ # https://stackoverflow.com/questions/22314298/git-push-results-in-fatal-protocol-error-bad-line-length-character-this
+ # Probably could use gitsrht-shell if output is restricted to just parameters...
+ users.users.${cfg.git.user}.shell = pkgs.bash;
services.sourcehut.settings = {
- # Register the git.sr.ht dispatcher
"git.sr.ht::dispatch"."/usr/bin/gitsrht-keys" =
mkDefault "${cfg.git.user}:${cfg.git.group}";
};
];
extraServices.gitsrht-fcgiwrap = mkIf cfg.nginx.enable {
serviceConfig = {
- # Socket is passed by systemd.sockets.gitsrht-fcgiwrap
+ # 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 [];
DynamicUser = true;
BindReadOnlyPaths = [ "${cfg.settings."git.sr.ht".repos}:/var/lib/sourcehut/gitsrht/repos" ];
IPAddressDeny = "any";
- InaccessiblePaths = [ "-+/run/postgresql" "-+/run/redis" ];
+ InaccessiblePaths = [ "-+/run/postgresql" "-+/run/redis-sourcehut" ];
PrivateNetwork = true;
RestrictAddressFamilies = mkForce [ "none" ];
SystemCallFilter = mkForce [
};
};
}))
+
(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" ];
};
- mainService = mkMerge [ baseService {
- serviceConfig.StateDirectory = [ "sourcehut/hgsrht" ];
- } ];
in {
inherit configIniOfService;
- inherit mainService;
+ mainService = mkMerge [ baseService {
+ serviceConfig.StateDirectory = [ "sourcehut/hgsrht" "sourcehut/hgsrht/repos" ];
+ } ];
port = 5010;
- webhooks.redisDatabase = 8;
+ webhooks = true;
extraTimers.hgsrht-periodic = {
- service = mainService;
- timerConfig = {
- OnCalendar = ["20min"];
- };
+ service = baseService;
+ timerConfig.OnCalendar = ["20min"];
};
extraTimers.hgsrht-clonebundles = mkIf cfg.hg.cloneBundles {
- service = mainService;
- timerConfig = {
- OnCalendar = ["daily"];
- AccuracySec = "1h";
- };
+ 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 ];
- };
+ users.users.${cfg.hg.user}.shell = pkgs.bash;
services.sourcehut.settings = {
- # Register the hg.sr.ht dispatcher
- "hg.sr.ht::dispatch"."/usr/bin/hgsrht-keys" =
+ # 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;
})
];
}))
+
(import ./service.nix "hub" {
inherit configIniOfService;
port = 5014;
extraConfig = {
services.nginx = mkIf cfg.nginx.enable {
- virtualHosts."hub.${domain}" = {
+ virtualHosts."hub.${domain}" = mkMerge [ {
serverAliases = [ domain ];
- };
+ } cfg.nginx.virtualHost ];
};
};
})
- (import ./service.nix "lists" {
+
+ (import ./service.nix "lists" (let
+ srvsrht = "listssrht";
+ in {
inherit configIniOfService;
port = 5006;
- redisDatabase = 4;
- webhooks.redisDatabase = 2;
+ webhooks = true;
+ # Receive the mail from Postfix and enqueue them into Redis and PostgreSQL
extraServices.listssrht-lmtp = {
- requires = [ "postfix.service" ];
+ 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.ExecStart = "${cfg.python}/bin/celery -A listssrht.process worker --loglevel INFO --pool eventlet";
- # Avoid crashing: os.getloadavg()
- serviceConfig.ProcSubset = mkForce "all";
+ 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.transport = ''
- lists.${domain} lmtp:unix:${cfg.settings."lists.sr.ht::worker".sock}
- '';
+ 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.redisDatabase = 6;
+ webhooks = true;
extraServices.metasrht-api = {
serviceConfig.Restart = "always";
serviceConfig.RestartSec = "2s";
OnCalendar = ["daily"];
AccuracySec = "1h";
};
- extraConfig = {
- 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 = [
- (pkgs.writeShellScriptBin "metasrht-manageuser" ''
- set -eux
- test "$(${pkgs.coreutils}/bin/id -n -u)" = '${cfg.meta.user}' ||
- sudo -u '${cfg.meta.user}' "$0" "$@"
- # In order to load config.ini
- cd /run/sourcehut/metasrht ||
- cat <<EOF
- Please run: sudo systemctl start metasrht
- EOF
- ${cfg.python}/bin/metasrht-manageuser "$@"
- '')
- ];
- };
+ 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 <<EOF
+ Please run: sudo systemctl start metasrht
+ EOF
+ exit 1
+ fi
+ fi
+ '');
+ }
+ (mkIf cfg.nginx.enable {
+ services.nginx.virtualHosts."meta.${domain}" = {
+ locations."/query" = {
+ proxyPass = cfg.settings."meta.sr.ht".api-origin;
+ extraConfig = ''
+ if ($request_method = 'OPTIONS') {
+ add_header 'Access-Control-Allow-Origin' '*';
+ add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
+ add_header 'Access-Control-Allow-Headers' 'User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
+ add_header 'Access-Control-Max-Age' 1728000;
+ add_header 'Content-Type' 'text/plain; charset=utf-8';
+ add_header 'Content-Length' 0;
+ return 204;
+ }
+
+ add_header 'Access-Control-Allow-Origin' '*';
+ add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
+ add_header 'Access-Control-Allow-Headers' 'User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
+ add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
+ '';
+ };
+ };
+ })
+ ];
})
+
(import ./service.nix "pages" {
inherit configIniOfService;
port = 5112;
- #webhooks.redisDatabase = 9;
mainService = let
srvsrht = "pagessrht";
version = pkgs.sourcehut.${srvsrht}.version;
};
};
})
+
(import ./service.nix "paste" {
inherit configIniOfService;
port = 5011;
- webhooks.redisDatabase = 5;
})
+
(import ./service.nix "todo" {
inherit configIniOfService;
port = 5003;
- webhooks.redisDatabase = 7;
+ webhooks = true;
extraServices.todosrht-lmtp = {
- requires = [ "postfix.service" ];
+ 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)
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}
- '';
+ services.postfix = {
+ destination = [ "todo.${domain}" ];
+ # FIXME: an accurate recipient list should be queried
+ # from the todo.sr.ht PostgreSQL database to avoid backscattering.
+ # But usernames are unfortunately not in that database but in meta.sr.ht.
+ # Note that two syntaxes are allowed:
+ # - ~username/tracker-name@todo.${domain}
+ # - u.username.tracker-name@todo.${domain}
+ localRecipients = [ "@todo.${domain}" ];
+ transport = ''
+ todo.${domain} lmtp:unix:${cfg.settings."todo.sr.ht::mail".sock}
+ '';
+ };
};
})
+
(mkRenamedOptionModule [ "services" "sourcehut" "originBase" ]
[ "services" "sourcehut" "settings" "sr.ht" "global-domain" ])
(mkRenamedOptionModule [ "services" "sourcehut" "address" ]
[ "services" "sourcehut" "listenAddress" ])
+
];
meta.doc = ./sourcehut.xml;