mirror of
https://github.com/NixOS/nixpkgs.git
synced 2024-10-31 22:51:22 +00:00
nixos/sourcehut: init (#113244)
* nixos/sourcehut: init * sourcehut: default nginx setup * sourcehut: documentation * sourcehut: re-structure settings * sourcehut: tests * nixos/sourcehut: adopt StateDirectory * Apply suggestions from code review Co-authored-by: Aaron Andersen <aaron@fosslib.net> Co-authored-by: Thibaut Marty <github@thibautmarty.fr> Co-authored-by: malte-v <34393802+malte-v@users.noreply.github.com> * nixos/sourcehut: PR suggestions * nixos/sourcehut: malte-v patch * nixos/sourcehut: add base virtualhost * nixos/sourcehut: remove superfluous key * nixos/sourcehut: use default from cfg * nixos/sourcehut: use originBase for logs * nixos/sourcehut: use toPythonApplication in systemPackages * nixos/sourcehut: directly use ExecStart * nixos/sourcehut: update docs Co-authored-by: Aaron Andersen <aaron@fosslib.net> Co-authored-by: Thibaut Marty <github@thibautmarty.fr> Co-authored-by: malte-v <34393802+malte-v@users.noreply.github.com>
This commit is contained in:
parent
81e982a7ca
commit
157aee00a5
@ -553,6 +553,7 @@
|
||||
./services/misc/siproxd.nix
|
||||
./services/misc/snapper.nix
|
||||
./services/misc/sonarr.nix
|
||||
./services/misc/sourcehut
|
||||
./services/misc/spice-vdagentd.nix
|
||||
./services/misc/ssm-agent.nix
|
||||
./services/misc/sssd.nix
|
||||
|
220
nixos/modules/services/misc/sourcehut/builds.nix
Normal file
220
nixos/modules/services/misc/sourcehut/builds.nix
Normal file
@ -0,0 +1,220 @@
|
||||
{ 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.
|
||||
Perform manually on machine: `cd ${scfg.statePath}/images; docker build -t qemu -f qemu/Dockerfile .`
|
||||
'';
|
||||
};
|
||||
|
||||
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.runCommandNoCC "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.runCommandNoCC "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 ];
|
||||
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";
|
||||
};
|
||||
};
|
||||
}
|
198
nixos/modules/services/misc/sourcehut/default.nix
Normal file
198
nixos/modules/services/misc/sourcehut/default.nix
Normal file
@ -0,0 +1,198 @@
|
||||
{ config, pkgs, lib, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
cfg = config.services.sourcehut;
|
||||
cfgIni = cfg.settings;
|
||||
settingsFormat = pkgs.formats.ini { };
|
||||
|
||||
# Specialized python containing all the modules
|
||||
python = pkgs.sourcehut.python.withPackages (ps: with ps; [
|
||||
gunicorn
|
||||
# Sourcehut services
|
||||
srht
|
||||
buildsrht
|
||||
dispatchsrht
|
||||
gitsrht
|
||||
hgsrht
|
||||
hubsrht
|
||||
listssrht
|
||||
mansrht
|
||||
metasrht
|
||||
pastesrht
|
||||
todosrht
|
||||
]);
|
||||
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
|
||||
'';
|
||||
};
|
||||
|
||||
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" ];
|
||||
description = ''
|
||||
Services to enable on the sourcehut network.
|
||||
'';
|
||||
};
|
||||
|
||||
originBase = 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.
|
||||
'';
|
||||
};
|
||||
|
||||
python = mkOption {
|
||||
internal = true;
|
||||
type = types.package;
|
||||
default = python;
|
||||
description = ''
|
||||
The python package to use. It should contain references to the *srht modules and also
|
||||
gunicorn.
|
||||
'';
|
||||
};
|
||||
|
||||
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.
|
||||
'';
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
type = lib.types.submodule {
|
||||
freeformType = settingsFormat.type;
|
||||
};
|
||||
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.";
|
||||
}
|
||||
|
||||
{
|
||||
assertion = hasAttrByPath [ "meta.sr.ht" "origin" ] cfgIni && cfgIni."meta.sr.ht".origin != null;
|
||||
message = "meta.sr.ht's origin must be defined.";
|
||||
}
|
||||
];
|
||||
|
||||
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);
|
||||
|
||||
environment.systemPackages = [ pkgs.sourcehut.coresrht ];
|
||||
|
||||
# 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;
|
||||
};
|
||||
};
|
||||
meta.doc = ./sourcehut.xml;
|
||||
meta.maintainers = with maintainers; [ tomberek ];
|
||||
}
|
125
nixos/modules/services/misc/sourcehut/dispatch.nix
Normal file
125
nixos/modules/services/misc/sourcehut/dispatch.nix
Normal file
@ -0,0 +1,125 @@
|
||||
{ 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";
|
||||
};
|
||||
};
|
||||
}
|
214
nixos/modules/services/misc/sourcehut/git.nix
Normal file
214
nixos/modules/services/misc/sourcehut/git.nix
Normal file
@ -0,0 +1,214 @@
|
||||
{ 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;
|
||||
}
|
||||
'';
|
||||
|
||||
};
|
||||
};
|
||||
}
|
173
nixos/modules/services/misc/sourcehut/hg.nix
Normal file
173
nixos/modules/services/misc/sourcehut/hg.nix
Normal file
@ -0,0 +1,173 @@
|
||||
{ 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";
|
||||
};
|
||||
};
|
||||
}
|
118
nixos/modules/services/misc/sourcehut/hub.nix
Normal file
118
nixos/modules/services/misc/sourcehut/hub.nix
Normal file
@ -0,0 +1,118 @@
|
||||
{ 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;
|
||||
};
|
||||
};
|
||||
}
|
185
nixos/modules/services/misc/sourcehut/lists.nix
Normal file
185
nixos/modules/services/misc/sourcehut/lists.nix
Normal file
@ -0,0 +1,185 @@
|
||||
# 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";
|
||||
};
|
||||
};
|
||||
}
|
122
nixos/modules/services/misc/sourcehut/man.nix
Normal file
122
nixos/modules/services/misc/sourcehut/man.nix
Normal file
@ -0,0 +1,122 @@
|
||||
{ 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";
|
||||
};
|
||||
};
|
||||
}
|
211
nixos/modules/services/misc/sourcehut/meta.nix
Normal file
211
nixos/modules/services/misc/sourcehut/meta.nix
Normal file
@ -0,0 +1,211 @@
|
||||
{ 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";
|
||||
};
|
||||
};
|
||||
}
|
133
nixos/modules/services/misc/sourcehut/paste.nix
Normal file
133
nixos/modules/services/misc/sourcehut/paste.nix
Normal file
@ -0,0 +1,133 @@
|
||||
{ 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";
|
||||
};
|
||||
};
|
||||
}
|
66
nixos/modules/services/misc/sourcehut/service.nix
Normal file
66
nixos/modules/services/misc/sourcehut/service.nix
Normal file
@ -0,0 +1,66 @@
|
||||
{ config, pkgs, lib }:
|
||||
serviceCfg: serviceDrv: iniKey: attrs:
|
||||
let
|
||||
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()
|
||||
'';
|
||||
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 {})
|
||||
;
|
||||
|
||||
preStart = ''
|
||||
if ! test -e ${statePath}/db; then
|
||||
# Setup the initial database
|
||||
${setupDB}
|
||||
|
||||
# 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
|
||||
|
||||
printf "%s" "${serviceDrv.version}" > ${statePath}/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 ${statePath}/webhook; then
|
||||
# Update ${iniKey}'s users' profile copy to the latest
|
||||
${cfg.python}/bin/srht-update-profiles ${iniKey}
|
||||
|
||||
touch ${statePath}/webhook
|
||||
fi
|
||||
|
||||
${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
|
||||
|
||||
# Mark down current package version
|
||||
printf "%s" "${serviceDrv.version}" > ${statePath}/db
|
||||
fi
|
||||
''}
|
||||
|
||||
${attrs.preStart or ""}
|
||||
'';
|
||||
}
|
||||
(builtins.removeAttrs attrs [ "path" "preStart" ])
|
115
nixos/modules/services/misc/sourcehut/sourcehut.xml
Normal file
115
nixos/modules/services/misc/sourcehut/sourcehut.xml
Normal file
@ -0,0 +1,115 @@
|
||||
<chapter xmlns="http://docbook.org/ns/docbook"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude"
|
||||
version="5.0"
|
||||
xml:id="module-services-sourcehut">
|
||||
<title>Sourcehut</title>
|
||||
<para>
|
||||
<link xlink:href="https://sr.ht.com/">Sourcehut</link> is an open-source,
|
||||
self-hostable software development platform. The server setup can be automated using
|
||||
<link linkend="opt-services.sourcehut.enable">services.sourcehut</link>.
|
||||
</para>
|
||||
|
||||
<section xml:id="module-services-sourcehut-basic-usage">
|
||||
<title>Basic usage</title>
|
||||
<para>
|
||||
Sourcehut is a Python and Go based set of applications.
|
||||
<literal><link linkend="opt-services.sourcehut.enable">services.sourcehut</link></literal>
|
||||
by default will use
|
||||
<literal><link linkend="opt-services.nginx.enable">services.nginx</link></literal>,
|
||||
<literal><link linkend="opt-services.nginx.enable">services.redis</link></literal>,
|
||||
<literal><link linkend="opt-services.nginx.enable">services.cron</link></literal>,
|
||||
and
|
||||
<literal><link linkend="opt-services.postgresql.enable">services.postgresql</link></literal>.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
A very basic configuration may look like this:
|
||||
<programlisting>
|
||||
{ pkgs, ... }:
|
||||
let
|
||||
fqdn =
|
||||
let
|
||||
join = hostName: domain: hostName + optionalString (domain != null) ".${domain}";
|
||||
in join config.networking.hostName config.networking.domain;
|
||||
in {
|
||||
|
||||
networking = {
|
||||
<link linkend="opt-networking.hostName">hostName</link> = "srht";
|
||||
<link linkend="opt-networking.domain">domain</link> = "tld";
|
||||
<link linkend="opt-networking.firewall.allowedTCPPorts">firewall.allowedTCPPorts</link> = [ 22 80 443 ];
|
||||
};
|
||||
|
||||
services.sourcehut = {
|
||||
<link linkend="opt-services.sourcehut.enable">enable</link> = true;
|
||||
<link linkend="opt-services.sourcehut.originBase">originBase</link> = fqdn;
|
||||
<link linkend="opt-services.sourcehut.services">services</link> = [ "meta" "man" "git" ];
|
||||
<link linkend="opt-services.sourcehut.settings">settings</link> = {
|
||||
"sr.ht" = {
|
||||
environment = "production";
|
||||
global-domain = fqdn;
|
||||
origin = "https://${fqdn}";
|
||||
# Produce keys with srht-keygen from <package>sourcehut.coresrht</package>.
|
||||
network-key = "SECRET";
|
||||
service-key = "SECRET";
|
||||
};
|
||||
webhooks.private-key= "SECRET";
|
||||
};
|
||||
};
|
||||
|
||||
<link linkend="opt-security.acme.certs._name_.extraDomainNames">security.acme.certs."${fqdn}".extraDomainNames</link> = [
|
||||
"meta.${fqdn}"
|
||||
"man.${fqdn}"
|
||||
"git.${fqdn}"
|
||||
];
|
||||
|
||||
services.nginx = {
|
||||
<link linkend="opt-services.nginx.enable">enable</link> = true;
|
||||
# only recommendedProxySettings are strictly required, but the rest make sense as well.
|
||||
<link linkend="opt-services.nginx.recommendedTlsSettings">recommendedTlsSettings</link> = true;
|
||||
<link linkend="opt-services.nginx.recommendedOptimisation">recommendedOptimisation</link> = true;
|
||||
<link linkend="opt-services.nginx.recommendedGzipSettings">recommendedGzipSettings</link> = true;
|
||||
<link linkend="opt-services.nginx.recommendedProxySettings">recommendedProxySettings</link> = true;
|
||||
|
||||
# Settings to setup what certificates are used for which endpoint.
|
||||
<link linkend="opt-services.nginx.virtualHosts">virtualHosts</link> = {
|
||||
<link linkend="opt-services.nginx.virtualHosts._name_.enableACME">"${fqdn}".enableACME</link> = true;
|
||||
<link linkend="opt-services.nginx.virtualHosts._name_.useACMEHost">"meta.${fqdn}".useACMEHost</link> = fqdn:
|
||||
<link linkend="opt-services.nginx.virtualHosts._name_.useACMEHost">"man.${fqdn}".useACMEHost</link> = fqdn:
|
||||
<link linkend="opt-services.nginx.virtualHosts._name_.useACMEHost">"git.${fqdn}".useACMEHost</link> = fqdn:
|
||||
};
|
||||
};
|
||||
}
|
||||
</programlisting>
|
||||
</para>
|
||||
|
||||
<para>
|
||||
The <literal>hostName</literal> option is used internally to configure the nginx
|
||||
reverse-proxy. The <literal>settings</literal> attribute set is
|
||||
used by the configuration generator and the result is placed in <literal>/etc/sr.ht/config.ini</literal>.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section xml:id="module-services-sourcehut-configuration">
|
||||
<title>Configuration</title>
|
||||
|
||||
<para>
|
||||
All configuration parameters are also stored in
|
||||
<literal>/etc/sr.ht/config.ini</literal> which is generated by
|
||||
the module and linked from the store to ensure that all values from <literal>config.ini</literal>
|
||||
can be modified by the module.
|
||||
</para>
|
||||
|
||||
</section>
|
||||
|
||||
<section xml:id="module-services-sourcehut-httpd">
|
||||
<title>Using an alternative webserver as reverse-proxy (e.g. <literal>httpd</literal>)</title>
|
||||
<para>
|
||||
By default, <package>nginx</package> is used as reverse-proxy for <package>sourcehut</package>.
|
||||
However, it's possible to use e.g. <package>httpd</package> by explicitly disabling
|
||||
<package>nginx</package> using <xref linkend="opt-services.nginx.enable" /> and fixing the
|
||||
<literal>settings</literal>.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
</chapter>
|
161
nixos/modules/services/misc/sourcehut/todo.nix
Normal file
161
nixos/modules/services/misc/sourcehut/todo.nix
Normal file
@ -0,0 +1,161 @@
|
||||
{ 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";
|
||||
};
|
||||
};
|
||||
}
|
29
nixos/tests/sourcehut.nix
Normal file
29
nixos/tests/sourcehut.nix
Normal file
@ -0,0 +1,29 @@
|
||||
import ./make-test-python.nix ({ pkgs, ... }:
|
||||
|
||||
{
|
||||
name = "sourcehut";
|
||||
|
||||
meta.maintainers = [ pkgs.lib.maintainers.tomberek ];
|
||||
|
||||
machine = { config, pkgs, ... }: {
|
||||
virtualisation.memorySize = 2048;
|
||||
networking.firewall.allowedTCPPorts = [ 80 ];
|
||||
|
||||
services.sourcehut = {
|
||||
enable = true;
|
||||
services = [ "meta" ];
|
||||
originBase = "sourcehut";
|
||||
settings."sr.ht".service-key = "8888888888888888888888888888888888888888888888888888888888888888";
|
||||
settings."sr.ht".network-key = "0000000000000000000000000000000000000000000=";
|
||||
settings.webhooks.private-key = "0000000000000000000000000000000000000000000=";
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
machine.wait_for_unit("multi-user.target")
|
||||
machine.wait_for_unit("metasrht.service")
|
||||
machine.wait_for_open_port(5000)
|
||||
machine.succeed("curl -sL http://localhost:5000 | grep meta.sourcehut")
|
||||
'';
|
||||
})
|
@ -31,6 +31,7 @@ let
|
||||
in
|
||||
with python.pkgs; recurseIntoAttrs {
|
||||
inherit python;
|
||||
coresrht = toPythonApplication srht;
|
||||
buildsrht = toPythonApplication buildsrht;
|
||||
dispatchsrht = toPythonApplication dispatchsrht;
|
||||
gitsrht = toPythonApplication gitsrht;
|
||||
|
Loading…
Reference in New Issue
Block a user