stump: init at 0.1.5, nixos/stump: init module, nixos/tests/stump: init tests (#525889)

This commit is contained in:
Adam C. Stephens
2026-06-22 15:58:16 +00:00
committed by GitHub
6 changed files with 318 additions and 0 deletions
@@ -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).
+1
View File
@@ -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
+172
View File
@@ -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 ];
};
}
+1
View File
@@ -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;
+26
View File
@@ -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
'';
}
+116
View File
@@ -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";
};
})