Skip to content

Commit 5566de0

Browse files
authored
add sabnzbd configuration (#6)
1 parent 3b3084a commit 5566de0

8 files changed

Lines changed: 1120 additions & 121 deletions

File tree

modules/default.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
./postgres.nix
88
./prowlarr
99
./radarr
10+
./sabnzbd
1011
./sonarr
1112
];
1213
}

modules/nixflix.nix

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,24 @@ in {
7171
'';
7272
};
7373

74+
downloadsDir = mkOption {
75+
type = types.path;
76+
default = "/data/downloads";
77+
example = "/data/downloads";
78+
description = ''
79+
The location of the downloads directory for download clients.
80+
81+
> **Warning:** Setting this to any path, where the subpath is not
82+
> owned by root, will fail! For example:
83+
>
84+
> ```nix
85+
> downloadsDir = /home/user/downloads
86+
> ```
87+
>
88+
> Is not supported, because `/home/user` is owned by `user`.
89+
'';
90+
};
91+
7492
stateDir = mkOption {
7593
type = types.path;
7694
default = "/data/.state";
@@ -145,6 +163,10 @@ in {
145163
${pkgs.coreutils}/bin/chown ${globals.libraryOwner.user}:${globals.libraryOwner.group} ${cfg.mediaDir}
146164
${pkgs.coreutils}/bin/chmod 0775 ${cfg.mediaDir}
147165
166+
${pkgs.coreutils}/bin/mkdir -p ${cfg.downloadsDir}
167+
${pkgs.coreutils}/bin/chown ${globals.libraryOwner.user}:${globals.libraryOwner.group} ${cfg.downloadsDir}
168+
${pkgs.coreutils}/bin/chmod 0775 ${cfg.downloadsDir}
169+
148170
# Service-registered directories
149171
${concatMapStringsSep "\n" (reg: ''
150172
${pkgs.coreutils}/bin/mkdir -p ${reg.dir}
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
{
2+
pkgs,
3+
lib,
4+
cfg,
5+
}:
6+
with lib; let
7+
configureScript =
8+
pkgs.writers.writePython3Bin "sabnzbd-configure-api" {
9+
libraries = with pkgs.python3Packages; [requests];
10+
} ''
11+
import requests
12+
import time
13+
import sys
14+
import os
15+
import json
16+
17+
# Read API key from environment or file
18+
API_KEY = os.environ.get("SABNZBD_API_KEY", "")
19+
BASE_URL = os.environ.get("SABNZBD_BASE_URL", "")
20+
21+
22+
def api_call(mode, **params):
23+
"""Make an API call to SABnzbd"""
24+
params.update({"mode": mode, "apikey": API_KEY, "output": "json"})
25+
try:
26+
response = requests.get(BASE_URL, params=params, timeout=10)
27+
response.raise_for_status()
28+
return response.json()
29+
except Exception as e:
30+
print(f"API call failed for mode={mode}: {e}", file=sys.stderr)
31+
return None
32+
33+
34+
def wait_for_sabnzbd():
35+
"""Wait for SABnzbd to be ready"""
36+
print("Waiting for SABnzbd to be ready...")
37+
for i in range(60):
38+
try:
39+
result = api_call("version")
40+
if result and "version" in result:
41+
print(f"SABnzbd is ready (version: {result['version']})")
42+
return True
43+
except Exception:
44+
pass
45+
time.sleep(1)
46+
print("SABnzbd did not become ready in time", file=sys.stderr)
47+
return False
48+
49+
50+
def set_config(section, keyword, value):
51+
"""Set a configuration value"""
52+
result = api_call("set_config", section=section, keyword=keyword,
53+
value=value)
54+
if result:
55+
print(f"Set {section}.{keyword} = {value}")
56+
return True
57+
return False
58+
59+
60+
def configure_misc_settings():
61+
"""Configure misc settings via API"""
62+
print("Configuring misc settings...")
63+
settings_json = os.environ.get("SETTINGS_JSON", "{}")
64+
settings = json.loads(settings_json)
65+
66+
for key, value in settings.items():
67+
# Convert booleans to 0/1
68+
if isinstance(value, bool):
69+
value = 1 if value else 0
70+
set_config("misc", key, str(value))
71+
72+
73+
def configure_servers():
74+
"""Configure SABnzbd servers via API"""
75+
print("Configuring servers...")
76+
servers_json = os.environ.get("SERVERS_JSON", "[]")
77+
servers = json.loads(servers_json)
78+
79+
for server in servers:
80+
name = server["name"]
81+
# Read secrets from environment if they're placeholders
82+
username = server["username"]
83+
password = server["password"]
84+
85+
if username.startswith("$"):
86+
username = os.environ.get(username[1:], username)
87+
if password.startswith("$"):
88+
password = os.environ.get(password[1:], password)
89+
90+
params = {
91+
"section": "servers",
92+
"name": name,
93+
"host": server["host"],
94+
"port": server["port"],
95+
"username": username,
96+
"password": password,
97+
"connections": server["connections"],
98+
"ssl": 1 if server["ssl"] else 0,
99+
"priority": server["priority"],
100+
"optional": 1 if server["optional"] else 0,
101+
"retention": server["retention"],
102+
"enable": 1 if server["enable"] else 0,
103+
}
104+
105+
result = api_call("set_config", **params)
106+
if result:
107+
print(f"Configured server: {name}")
108+
109+
110+
def configure_categories():
111+
"""Configure SABnzbd categories via API"""
112+
print("Configuring categories...")
113+
categories_json = os.environ.get("CATEGORIES_JSON", "[]")
114+
categories = json.loads(categories_json)
115+
116+
for category in categories:
117+
name = category["name"]
118+
params = {
119+
"section": "categories",
120+
"name": name,
121+
"dir": category["dir"],
122+
"priority": category["priority"],
123+
"pp": category["pp"],
124+
"script": category["script"],
125+
}
126+
127+
result = api_call("set_config", **params)
128+
if result:
129+
print(f"Configured category: {name}")
130+
131+
132+
if __name__ == "__main__":
133+
if not wait_for_sabnzbd():
134+
sys.exit(1)
135+
136+
configure_misc_settings()
137+
configure_servers()
138+
configure_categories()
139+
140+
print("SABnzbd configuration completed successfully")
141+
'';
142+
in {
143+
script = configureScript;
144+
145+
serviceConfig = {
146+
description = "Configure SABnzbd via API";
147+
after = ["sabnzbd.service"];
148+
wants = ["sabnzbd.service"];
149+
wantedBy = ["multi-user.target"];
150+
151+
serviceConfig = {
152+
Type = "oneshot";
153+
RemainAfterExit = true;
154+
};
155+
156+
script = ''
157+
# Export API key
158+
${optionalString (cfg.apiKeyPath != null) ''
159+
export SABNZBD_API_KEY=$(${pkgs.coreutils}/bin/cat ${cfg.apiKeyPath})
160+
''}
161+
162+
# Export base URL
163+
export SABNZBD_BASE_URL="http://${cfg.settings.host}:${toString cfg.settings.port}${cfg.settings.url_base}/api"
164+
165+
# Export configuration as JSON
166+
export SETTINGS_JSON='${builtins.toJSON (filterAttrs (n: v: n != "servers" && n != "categories") cfg.settings)}'
167+
export SERVERS_JSON='${builtins.toJSON cfg.settings.servers}'
168+
export CATEGORIES_JSON='${builtins.toJSON cfg.settings.categories}'
169+
170+
# Export environment secrets
171+
${concatMapStringsSep "\n" (secret: ''
172+
export ${secret.env}=$(${pkgs.coreutils}/bin/cat ${secret.path})
173+
'')
174+
cfg.environmentSecrets}
175+
176+
# Run configuration script
177+
${configureScript}/bin/sabnzbd-configure-api
178+
'';
179+
};
180+
}

0 commit comments

Comments
 (0)