From 3b8f969af2c11261835930f381b89e63cb78709d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20van=20Br=C3=BCgge?= Date: Sat, 30 May 2026 09:27:07 +0200 Subject: [PATCH 1/3] stump: init at 0.1.5 --- pkgs/by-name/st/stump/package.nix | 116 ++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 pkgs/by-name/st/stump/package.nix diff --git a/pkgs/by-name/st/stump/package.nix b/pkgs/by-name/st/stump/package.nix new file mode 100644 index 000000000000..7045bcb575d4 --- /dev/null +++ b/pkgs/by-name/st/stump/package.nix @@ -0,0 +1,116 @@ +{ + lib, + stdenv, + nixosTests, + fetchFromGitHub, + fetchYarnDeps, + yarnConfigHook, + rustPlatform, + nodejs, + pdfium-binaries, + openssl, + dbus, + glib, + gtk3, + webkitgtk_4_1, + cacert, + pkg-config, + makeWrapper, +}: +let + version = "0.1.5"; + + src = fetchFromGitHub { + owner = "stumpapp"; + repo = "stump"; + tag = "v${version}"; + hash = "sha256-kstMk4HJopLHW22ynVZF0itWUixwiDkbsMUpYMvw1Ag="; + }; + + frontend = stdenv.mkDerivation (finalAttrs: { + pname = "stump-frontend"; + inherit src version; + + yarnOfflineCache = fetchYarnDeps { + yarnLock = finalAttrs.src + "/yarn.lock"; + hash = "sha256-Zh0GmxzDZ9YkUVK9i4cT4NKm83Rgcdi1qGmvA8RdDUM="; + }; + + nativeBuildInputs = [ + yarnConfigHook + nodejs + ]; + + buildPhase = '' + runHook preBuild + + pushd apps/web + node ./node_modules/.bin/vite build + popd + + runHook postBuild + ''; + + installPhase = '' + mv ./apps/web/dist $out + ''; + }); +in +rustPlatform.buildRustPackage (finalAttrs: { + pname = "stump"; + inherit src version; + + __structuredAttrs = true; + + cargoHash = "sha256-ZFIoxlArbhD+kZfX8K1iWmIaFSPfk9DeO9mL9PUZCnI="; + + cargoBuildFlags = [ + "--package" + "stump_server" + "--bin" + "stump_server" + ]; + + env.GIT_REV = "v${version}"; + + nativeBuildInputs = [ + pkg-config + makeWrapper + ]; + + nativeCheckInputs = [ + cacert + ]; + + buildInputs = [ + openssl + dbus + glib + gtk3 + webkitgtk_4_1 + ]; + + preCheck = '' + export HOME=$TMP + ''; + + postInstall = '' + wrapProgram $out/bin/stump_server \ + --set-default STUMP_CONFIG_DIR /var/lib/stump/config \ + --set-default STUMP_CLIENT_DIR ${frontend} \ + --set-default STUMP_PORT 10001 \ + --set-default STUMP_PROFILE release \ + --set-default PDFIUM_PATH ${pdfium-binaries}/lib/libpdfium.so \ + --set-default API_VERSION v1 + ''; + + passthru.tests = nixosTests.stump; + + meta = { + homepage = "https://stumpapp.dev/"; + description = "A free and open source comics, manga and digital book server with OPDS support"; + license = lib.licenses.mit; + platforms = [ "x86_64-linux" ]; + mainProgram = "stump_server"; + }; +}) From e5cfb2c2068f01eb6d66540ec3d8c347ff998690 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20van=20Br=C3=BCgge?= Date: Sat, 30 May 2026 10:31:11 +0200 Subject: [PATCH 2/3] nixos/stump: init module --- .../manual/release-notes/rl-2611.section.md | 2 + nixos/modules/module-list.nix | 1 + nixos/modules/services/web-apps/stump.nix | 172 ++++++++++++++++++ 3 files changed, 175 insertions(+) create mode 100644 nixos/modules/services/web-apps/stump.nix diff --git a/nixos/doc/manual/release-notes/rl-2611.section.md b/nixos/doc/manual/release-notes/rl-2611.section.md index aaf1554cf398..f54a03330c7b 100644 --- a/nixos/doc/manual/release-notes/rl-2611.section.md +++ b/nixos/doc/manual/release-notes/rl-2611.section.md @@ -18,6 +18,8 @@ - [CastSponsorSkip](https://github.com/gabe565/CastSponsorSkip/), skips YouTube sponsorships (and sometimes ads) on all local Google Cast devices. +- [Stump](https://www.stumpapp.dev/), a free and open source comics, manga and digital book server with OPDS support. Available as [services.stump](#opt-services.stump.enable). + - [FlapAlerted](https://github.com/Kioubit/FlapAlerted), detects BGP flapping events and provides statistics based on BGP update messages. Available as [services.flap-alerted](#opt-services.flap-alerted.enable). - [Unpackerr](https://unpackerr.zip), extracts downloads for Radarr, Sonarr, Lidarr, Readarr, and/or a Watch folder. Available as [services.unpackerr](#opt-services.unpackerr.enable). diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 0fd01c232583..9e3d2f772b48 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -1798,6 +1798,7 @@ ./services/web-apps/stirling-pdf.nix ./services/web-apps/strfry.nix ./services/web-apps/strichliste.nix + ./services/web-apps/stump.nix ./services/web-apps/suwayomi-server.nix ./services/web-apps/szurubooru.nix ./services/web-apps/tabbyapi.nix diff --git a/nixos/modules/services/web-apps/stump.nix b/nixos/modules/services/web-apps/stump.nix new file mode 100644 index 000000000000..8dbf1dfcb7da --- /dev/null +++ b/nixos/modules/services/web-apps/stump.nix @@ -0,0 +1,172 @@ +{ + lib, + config, + pkgs, + ... +}: + +let + cfg = config.services.stump; + + inherit (lib) + types + mkIf + mkOption + mkEnableOption + ; + + secret = types.nullOr ( + types.str + // { + # We don't want users to be able to pass a path literal here but + # it should look like a path. + check = it: lib.isString it && lib.types.path.check it; + } + ); +in +{ + options.services.stump = { + enable = mkEnableOption "Stump"; + package = lib.mkPackageOption pkgs "stump" { }; + + configLocation = mkOption { + type = types.path; + default = "/var/lib/stump"; + description = "Directory used to store the database and configuration files. If it is not the default, the directory has to be created manually such that the stump user is able to read and write to it."; + }; + + environment = mkOption { + type = types.attrsOf types.str; + default = { }; + example = { + STUMP_VERBOSITY = "2"; + }; + description = '' + Extra configuration environment variables. Refer to the [documentation](https://www.stumpapp.dev/docs/guides/configuration/server-config) for options. + ''; + }; + + environmentFile = mkOption { + type = secret; + example = "/run/secrets/stump"; + default = null; + description = '' + Path of a file with extra environment variables to be loaded from disk. + This file is not added to the nix store, so it can be used to pass secrets to stump. + Refer to the [documentation](https://www.stumpapp.dev/docs/guides/configuration/server-config) for options. + ''; + }; + + secretFiles = mkOption { + type = types.attrsOf secret; + example = { + STUMP_OIDC_CLIENT_SECRET = "/run/secrets/stump_client_secret"; + }; + default = { }; + description = '' + Attribute set containing paths to files to add to the environment of stump. + The files are not added to the nix store, so they can be used to pass secrets to stump. + Refer to the [documentation](https://www.stumpapp.dev/docs/guides/configuration/server-config) for options. + ''; + }; + + ip = mkOption { + type = types.str; + default = "127.0.0.1"; + description = "The IP address that Stump will listen on."; + }; + port = mkOption { + type = types.port; + default = 10001; + description = "The port that Stump will listen on."; + }; + openFirewall = mkOption { + type = types.bool; + default = false; + description = "Whether to open the Stump port in the firewall"; + }; + user = mkOption { + type = types.str; + default = "stump"; + description = "The user Stump should run as."; + }; + group = mkOption { + type = types.str; + default = "stump"; + description = "The group stump should run as."; + }; + }; + + config = mkIf cfg.enable { + networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ]; + + services.stump.environment = { + STUMP_IP = cfg.ip; + STUMP_PORT = toString cfg.port; + STUMP_CONFIG_DIR = cfg.configLocation; + }; + + systemd.services.stump = { + description = "Stump (A free and open source comics, manga and digital book server with OPDS support)"; + requires = [ "network-online.target" ]; + after = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + environment = cfg.environment; + serviceConfig = { + Type = "simple"; + Restart = "on-failure"; + RestartSec = 3; + + ExecStart = + if cfg.secretFiles == { } then + "${lib.getExe cfg.package}" + else + pkgs.writeShellScript "stump-env" '' + ${lib.strings.concatStringsSep "\n" ( + lib.attrsets.mapAttrsToList (key: path: "export ${key}=$(< \"${path}\")") cfg.secretFiles + )} + ${lib.getExe cfg.package} + ''; + EnvironmentFile = cfg.environmentFile; + StateDirectory = "stump"; + User = cfg.user; + Group = cfg.group; + + # Hardening + CapabilityBoundingSet = ""; + NoNewPrivileges = true; + PrivateUsers = true; + PrivateTmp = true; + PrivateDevices = true; + PrivateMounts = true; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + "AF_UNIX" + "AF_NETLINK" # is used to determine local ip + ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + }; + }; + + users.users = mkIf (cfg.user == "stump") { + stump = { + name = "stump"; + group = cfg.group; + isSystemUser = true; + }; + }; + users.groups = mkIf (cfg.group == "stump") { stump = { }; }; + + meta.maintainers = with lib.maintainers; [ jvanbruegge ]; + }; +} From 90ef12f95a5646e192058a8e263d3a45498f5688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20van=20Br=C3=BCgge?= Date: Sat, 30 May 2026 10:44:04 +0200 Subject: [PATCH 3/3] nixos/tests/stump: init tests --- nixos/tests/all-tests.nix | 1 + nixos/tests/web-apps/stump.nix | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 nixos/tests/web-apps/stump.nix diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 24b3d1216af7..d09057fed110 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -1579,6 +1579,7 @@ in strichliste = runTest ./web-apps/strichliste.nix; strongswan-swanctl = runTest ./strongswan-swanctl.nix; stub-ld = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./stub-ld.nix { }; + stump = runTest ./web-apps/stump.nix; stunnel = import ./stunnel.nix { inherit runTest; }; sudo = runTest ./sudo.nix; sudo-rs = runTest ./sudo-rs.nix; diff --git a/nixos/tests/web-apps/stump.nix b/nixos/tests/web-apps/stump.nix new file mode 100644 index 000000000000..ce67647404f0 --- /dev/null +++ b/nixos/tests/web-apps/stump.nix @@ -0,0 +1,26 @@ +{ ... }: +{ + name = "stump-nixos"; + + nodes.machine = + { ... }: + { + services.stump.enable = true; + }; + + testScript = '' + import json + + machine.wait_for_unit("stump.service") + + machine.wait_for_open_port(10001) + machine.succeed("curl --fail -s http://localhost:10001/") + + machine.succeed("curl --fail -s --data '{\"username\":\"admin\",\"password\":\"admin\"}' -H 'Content-Type: application/json' -X POST http://localhost:10001/api/v2/auth/register") + + response = machine.succeed("curl --fail -s -X GET http://localhost:10001/api/v2/claim") + is_claimed = json.loads(response)['isClaimed'] + + assert is_claimed + ''; +}