diff --git a/nixos/doc/manual/redirects.json b/nixos/doc/manual/redirects.json index 86854c1065ac..5970010e9e64 100644 --- a/nixos/doc/manual/redirects.json +++ b/nixos/doc/manual/redirects.json @@ -551,6 +551,18 @@ "module-services-youtrack-upgrade-2022_3-2023_1": [ "index.html#module-services-youtrack-upgrade-2022_3-2023_1" ], + "module-services-szurubooru": [ + "index.html#module-services-szurubooru" + ], + "module-services-szurubooru-basic-usage": [ + "index.html#module-services-szurubooru-basic-usage" + ], + "module-services-szurubooru-reverse-proxy-configuration": [ + "index.html#module-services-szurubooru-reverse-proxy-configuration" + ], + "module-services-szurubooru-extra-config": [ + "index.html#module-services-szurubooru-extra-config" + ], "module-services-suwayomi-server": [ "index.html#module-services-suwayomi-server" ], diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index fec4eda45b8b..78cebbee8ca5 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -1663,6 +1663,7 @@ ./services/web-apps/stirling-pdf.nix ./services/web-apps/strfry.nix ./services/web-apps/suwayomi-server.nix + ./services/web-apps/szurubooru.nix ./services/web-apps/trilium.nix ./services/web-apps/tt-rss.nix ./services/web-apps/vikunja.nix diff --git a/nixos/modules/services/web-apps/szurubooru.md b/nixos/modules/services/web-apps/szurubooru.md new file mode 100644 index 000000000000..368fe608d450 --- /dev/null +++ b/nixos/modules/services/web-apps/szurubooru.md @@ -0,0 +1,80 @@ +# Szurubooru {#module-services-szurubooru} + +An image board engine dedicated for small and medium communities. + +## Configuration {#module-services-szurubooru-basic-usage} + +By default the module will execute Szurubooru server only, the web client only contains static files that can be reached via a reverse proxy. + +Here is a basic configuration: + +```nix +{ + services.szurubooru = { + enable = true; + + server = { + port = 8080; + + settings = { + domain = "https://szurubooru.domain.tld"; + secretFile = /path/to/secret/file; + }; + }; + + database = { + passwordFile = /path/to/secret/file; + }; + }; +} +``` + +## Reverse proxy configuration {#module-services-szurubooru-reverse-proxy-configuration} + +The prefered method to run this service is behind a reverse proxy not to expose an open port. For example, here is a minimal Nginx configuration: + +```nix +{ + services.szurubooru = { + enable = true; + + server = { + port = 8080; + ... + }; + + ... + }; + + services.nginx.virtualHosts."szurubooru.domain.tld" = { + locations = { + "/api/".proxyPass = "http://localhost:8080/"; + "/data/".root = config.services.szurubooru.dataDir; + "/" = { + root = config.services.szurubooru.client.package; + tryFiles = "$uri /index.htm"; + }; + }; + }; +} +``` + +## Extra configuration {#module-services-szurubooru-extra-config} + +Not all configuration options of the server are available directly in this module, but you can add them in `services.szurubooru.server.settings`: + +```nix +{ + services.szurubooru = { + enable = true; + + server.settings = { + domain = "https://szurubooru.domain.tld"; + delete_source_files = "yes"; + contact_email = "example@domain.tld"; + }; + }; +} +``` + +You can find all of the options in the default config file available [here](https://github.com/rr-/szurubooru/blob/master/server/config.yaml.dist). diff --git a/nixos/modules/services/web-apps/szurubooru.nix b/nixos/modules/services/web-apps/szurubooru.nix new file mode 100644 index 000000000000..95b1a7ad7e8b --- /dev/null +++ b/nixos/modules/services/web-apps/szurubooru.nix @@ -0,0 +1,331 @@ +{ + 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 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; + }; +}