Files
arr-init/modules/jellyseerr.nix
Simon Gardling 948c9e3a38 refactor: split module.nix into per-service modules
Replace the 1301-line monolithic module.nix with focused modules:
- modules/servarr.nix  (Sonarr/Radarr/Prowlarr)
- modules/bazarr.nix   (Bazarr provider connections)
- modules/jellyseerr.nix (Jellyseerr quality profiles)
- modules/default.nix  (import aggregator)

Python scripts (from prior commit) are referenced as standalone
files via PYTHONPATH, with config passed as a JSON file argument.

New options:
- Add bindAddress option to all services (default 127.0.0.1)
- Replace hardcoded wg.service dependency with configurable
  networkNamespaceService option
- Add systemd hardening: PrivateTmp, NoNewPrivileges, ProtectHome,
  ProtectKernelTunables/Modules, ProtectControlGroups,
  RestrictSUIDSGID, SystemCallArchitectures=native

Test updates:
- Extract mock qBittorrent/SABnzbd servers into tests/lib/mocks.nix
- Fix duplicate wait_for_unit calls in integration test
2026-04-16 17:29:25 -04:00

196 lines
5.5 KiB
Nix

{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.jellyseerrInit;
scriptDir = ../scripts;
pythonEnv = pkgs.python3.withPackages (
ps: with ps; [
pyyaml
requests
]
);
jellyseerrProviderModule = lib.types.submodule {
options = {
profileName = lib.mkOption {
type = lib.types.str;
description = "Quality profile name to set as the default. Resolved to an ID at runtime by querying the Servarr API.";
example = "Remux + WEB 2160p";
};
dataDir = lib.mkOption {
type = lib.types.str;
description = "Path to the Servarr application data directory containing config.xml.";
example = "/services/radarr";
};
bindAddress = lib.mkOption {
type = lib.types.str;
default = "127.0.0.1";
description = "IP address the Servarr application API is listening on.";
};
port = lib.mkOption {
type = lib.types.port;
description = "API port of the Servarr application.";
example = 7878;
};
serviceName = lib.mkOption {
type = lib.types.str;
description = "Name of the systemd service to depend on.";
example = "radarr";
};
};
};
jellyseerrInitModule = lib.types.submodule {
options = {
enable = lib.mkEnableOption "Jellyseerr quality profile initialization";
configDir = lib.mkOption {
type = lib.types.str;
description = "Path to Jellyseerr's data directory containing settings.json.";
example = "/services/jellyseerr";
};
apiTimeout = lib.mkOption {
type = lib.types.ints.positive;
default = 90;
description = "Seconds to wait for Radarr/Sonarr APIs to become available.";
};
radarr = lib.mkOption {
type = jellyseerrProviderModule;
description = "Radarr quality profile configuration for Jellyseerr.";
};
sonarr = lib.mkOption {
type = lib.types.submodule {
options = {
profileName = lib.mkOption {
type = lib.types.str;
description = "Quality profile name for TV series.";
example = "WEB-2160p";
};
animeProfileName = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "Quality profile name for anime. Defaults to profileName when null.";
};
dataDir = lib.mkOption {
type = lib.types.str;
description = "Path to the Sonarr data directory containing config.xml.";
example = "/services/sonarr";
};
bindAddress = lib.mkOption {
type = lib.types.str;
default = "127.0.0.1";
description = "IP address the Sonarr API is listening on.";
};
port = lib.mkOption {
type = lib.types.port;
description = "API port of Sonarr.";
example = 8989;
};
serviceName = lib.mkOption {
type = lib.types.str;
description = "Name of the systemd service to depend on.";
example = "sonarr";
};
};
};
description = "Sonarr quality profile configuration for Jellyseerr.";
};
};
};
mkJellyseerrInitConfig = builtins.toJSON {
configDir = cfg.configDir;
apiTimeout = cfg.apiTimeout;
radarr = {
profileName = cfg.radarr.profileName;
dataDir = cfg.radarr.dataDir;
bindAddress = cfg.radarr.bindAddress;
port = cfg.radarr.port;
};
sonarr = {
profileName = cfg.sonarr.profileName;
animeProfileName =
if cfg.sonarr.animeProfileName != null then
cfg.sonarr.animeProfileName
else
cfg.sonarr.profileName;
dataDir = cfg.sonarr.dataDir;
bindAddress = cfg.sonarr.bindAddress;
port = cfg.sonarr.port;
};
};
configFile = pkgs.writeText "jellyseerr-init-config.json" mkJellyseerrInitConfig;
jellyseerrDeps = [
"jellyseerr.service"
"${cfg.radarr.serviceName}.service"
"${cfg.sonarr.serviceName}.service"
];
hardeningConfig = {
PrivateTmp = true;
NoNewPrivileges = true;
ProtectKernelTunables = true;
ProtectKernelModules = true;
ProtectControlGroups = true;
RestrictSUIDSGID = true;
ProtectHome = true;
SystemCallArchitectures = "native";
};
in
{
options.services.jellyseerrInit = lib.mkOption {
type = jellyseerrInitModule;
default = {
enable = false;
};
description = ''
Jellyseerr quality profile initialization.
Patches Jellyseerr's settings.json so new requests default to the
correct Radarr/Sonarr quality profiles, resolved by name at runtime.
'';
};
config = lib.mkIf cfg.enable {
systemd.services.jellyseerr-init = {
description = "Initialize Jellyseerr quality profile defaults";
after = jellyseerrDeps;
requires = jellyseerrDeps;
wantedBy = [ "multi-user.target" ];
environment.PYTHONPATH = "${scriptDir}";
unitConfig = {
StartLimitIntervalSec = 5 * (cfg.apiTimeout + 30);
StartLimitBurst = 5;
};
serviceConfig =
{
Type = "oneshot";
RemainAfterExit = true;
Restart = "on-failure";
RestartSec = 30;
ExecStart = "${pythonEnv}/bin/python3 ${scriptDir}/jellyseerr_init.py ${configFile}";
}
// hardeningConfig;
};
};
}