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 and behavioral changes: - Add bindAddress option to all services (default 127.0.0.1) - Change healthChecks default from false to true - 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 - Add healthChecks=false to tests not exercising health checks - Fix duplicate wait_for_unit calls in integration test
This commit is contained in:
195
modules/jellyseerr.nix
Normal file
195
modules/jellyseerr.nix
Normal file
@@ -0,0 +1,195 @@
|
||||
{
|
||||
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;
|
||||
};
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user