332 lines
9.9 KiB
Nix

{
config,
pkgs,
lib,
...
}:
let
cfg = config.services.szurubooru;
inherit (lib)
mkOption
mkEnableOption
mkIf
mkPackageOption
types
;
format = pkgs.formats.yaml { };
python = pkgs.python312;
in
{
options = {
services.szurubooru = {
enable = mkEnableOption "Szurubooru, an image board engine dedicated for small and medium communities";
user = mkOption {
type = types.str;
default = "szurubooru";
description = ''
User account under which Szurubooru runs.
'';
};
group = mkOption {
type = types.str;
default = "szurubooru";
description = ''
Group under which Szurubooru runs.
'';
};
dataDir = mkOption {
type = types.path;
default = "/var/lib/szurubooru";
example = "/var/lib/szuru";
description = ''
The path to the data directory in which Szurubooru will store its data.
'';
};
openFirewall = mkOption {
type = types.bool;
default = false;
example = true;
description = ''
Whether to open the firewall for the port in {option}`services.szurubooru.server.port`.
'';
};
server = {
package = mkPackageOption pkgs [
"szurubooru"
"server"
] { };
port = mkOption {
type = types.port;
default = 8080;
example = 9000;
description = ''
Port to expose HTTP service.
'';
};
threads = mkOption {
type = types.int;
default = 4;
example = 6;
description = ''Number of waitress threads to start.'';
};
settings = mkOption {
type = types.submodule {
freeformType = format.type;
options = {
name = mkOption {
type = types.str;
default = "szurubooru";
example = "Szuru";
description = ''Name shown in the website title and on the front page.'';
};
domain = mkOption {
type = types.str;
example = "http://example.com";
description = ''Full URL to the homepage of this szurubooru site (with no trailing slash).'';
};
# NOTE: this is not a real upstream option
secretFile = mkOption {
type = types.path;
example = "/run/secrets/szurubooru-server-secret";
description = ''
File containing a secret used to salt the users' password hashes and generate filenames for static content.
'';
};
delete_source_files = mkOption {
type = types.enum [
"yes"
"no"
];
default = "no";
example = "yes";
description = ''Whether to delete thumbnails and source files on post delete.'';
};
smtp = {
host = mkOption {
type = types.nullOr types.str;
default = null;
example = "localhost";
description = ''Host of the SMTP server used to send reset password.'';
};
port = mkOption {
type = types.nullOr types.port;
default = null;
example = 25;
description = ''Port of the SMTP server.'';
};
user = mkOption {
type = types.nullOr types.str;
default = null;
example = "bot";
description = ''User to connect to the SMTP server.'';
};
# NOTE: this is not a real upstream option
passFile = mkOption {
type = types.nullOr types.path;
default = null;
example = "/run/secrets/szurubooru-smtp-pass";
description = ''File containing the password associated to the given user for the SMTP server.'';
};
};
data_url = mkOption {
type = types.str;
default = "${cfg.server.settings.domain}/data/";
defaultText = lib.literalExpression ''"''${services.szurubooru.server.settings.domain}/data/"'';
example = "http://example.com/content/";
description = ''Full URL to the data endpoint.'';
};
data_dir = mkOption {
type = types.path;
default = "${cfg.dataDir}/data";
defaultText = lib.literalExpression ''"''${services.szurubooru.dataDir}/data"'';
example = "/srv/szurubooru/data";
description = ''Path to the static files.'';
};
debug = mkOption {
type = types.int;
default = 0;
example = 1;
description = ''Whether to generate server logs.'';
};
show_sql = mkOption {
type = types.int;
default = 0;
example = 1;
description = ''Whether to show SQL in server logs.'';
};
};
};
description = ''
Configuration to write to {file}`config.yaml`.
See <https://github.com/rr-/szurubooru/blob/master/server/config.yaml.dist> for more information.
'';
};
};
client = {
package = mkPackageOption pkgs [
"szurubooru"
"client"
] { };
};
database = {
host = mkOption {
type = types.str;
default = "localhost";
example = "192.168.1.2";
description = ''Host on which the PostgreSQL database runs.'';
};
port = mkOption {
type = types.port;
default = 5432;
description = ''The port under which PostgreSQL listens to.'';
};
name = mkOption {
type = types.str;
default = cfg.database.user;
defaultText = lib.literalExpression "szurubooru.database.name";
example = "szuru";
description = ''Name of the PostgreSQL database.'';
};
user = mkOption {
type = types.str;
default = "szurubooru";
example = "szuru";
description = ''PostgreSQL user.'';
};
passwordFile = mkOption {
type = types.path;
example = "/run/secrets/szurubooru-db-password";
description = ''A file containing the password for the PostgreSQL user.'';
};
};
};
};
config = mkIf cfg.enable {
networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.server.port ];
users.groups = mkIf (cfg.group == "szurubooru") {
szurubooru = { };
};
users.users = mkIf (cfg.user == "szurubooru") {
szurubooru = {
group = cfg.group;
description = "Szurubooru Daemon user";
isSystemUser = true;
};
};
systemd.services.szurubooru =
let
configFile = format.generate "config.yaml" (
lib.pipe cfg.server.settings [
(
settings:
lib.recursiveUpdate settings {
secretFile = null;
secret = "$SZURUBOORU_SECRET";
smtp.pass = if settings.smtp.passFile != null then "$SZURUBOORU_SMTP_PASS" else null;
smtp.passFile = null;
smtp.enable = null;
database = "postgresql://${cfg.database.user}:$SZURUBOORU_DATABASE_PASSWORD@${cfg.database.host}:${toString cfg.database.port}/${cfg.database.name}";
}
)
(lib.filterAttrsRecursive (_: x: x != null))
]
);
pyenv = python.buildEnv.override {
extraLibs = [ (python.pkgs.toPythonModule cfg.server.package) ];
};
in
{
description = "Server of Szurubooru, an image board engine dedicated for small and medium communities";
wantedBy = [
"multi-user.target"
"szurubooru-client.service"
];
before = [ "szurubooru-client.service" ];
after = [
"network.target"
"network-online.target"
];
wants = [ "network-online.target" ];
environment = {
PYTHONPATH = "${pyenv}/${pyenv.sitePackages}/";
};
path =
with pkgs;
[
envsubst
ffmpeg_4-full
]
++ (with python.pkgs; [
alembic
waitress
]);
script = ''
export SZURUBOORU_SECRET="$(<${cfg.server.settings.secretFile})"
export SZURUBOORU_DATABASE_PASSWORD="$(<${cfg.database.passwordFile})"
${lib.optionalString (cfg.server.settings.smtp.passFile != null) ''
export SZURUBOORU_SMTP_PASS=$(<${cfg.server.settings.smtp.passFile})
''}
install -m0640 ${cfg.server.package.src}/config.yaml.dist ${cfg.dataDir}/config.yaml.dist
envsubst -i ${configFile} -o ${cfg.dataDir}/config.yaml
sed 's|script_location = |script_location = ${cfg.server.package.src}/|' ${cfg.server.package.src}/alembic.ini > ${cfg.dataDir}/alembic.ini
alembic upgrade head
waitress-serve --port ${toString cfg.server.port} --threads ${toString cfg.server.threads} szurubooru.facade:app
'';
serviceConfig = {
User = cfg.user;
Group = cfg.group;
Type = "simple";
Restart = "on-failure";
StateDirectory = mkIf (cfg.dataDir == "/var/lib/szurubooru") "szurubooru";
WorkingDirectory = cfg.dataDir;
};
};
};
meta = {
maintainers = with lib.maintainers; [ ratcornu ];
doc = ./szurubooru.md;
};
}