Files
homelabstack/scripts/apply-media-external-auth.sh
T
ginnoir 9bad6f3d22 Point Servarr download clients at internal Docker hosts so API calls bypass Authentik forward_auth.
After batch 2 SSO, Sonarr/Radarr hitting nzbget.ginnoir.com got 302 from Caddy instead of NZBGet JSON-RPC.
2026-06-11 02:47:54 -05:00

182 lines
5.7 KiB
Bash

#!/usr/bin/env bash
set -euo pipefail
# TB-006 batch 2: disable local auth; Servarr -> External; skip tautulli.
# Run on valhalla as ginnoir.
set_servarr_external() {
local app="$1"
local cfg="/config/${app}/config.xml"
if [[ ! -f "$cfg" ]]; then
echo "SKIP ${app}: no ${cfg}"
return 0
fi
echo "=== ${app}: set External ==="
docker stop "$app" >/dev/null
python3 - "$cfg" <<'PY'
import sys
import xml.etree.ElementTree as ET
path = sys.argv[1]
tree = ET.parse(path)
root = tree.getroot()
auth_required = None
for el in list(root):
if el.tag == "AuthenticationRequired" and el.text:
auth_required = el.text.strip()
break
for el in list(root):
if el.tag in ("AuthenticationMethod", "AuthenticationRequired"):
root.remove(el)
ext = ET.SubElement(root, "AuthenticationMethod")
ext.text = "External"
req = ET.SubElement(root, "AuthenticationRequired")
req.text = auth_required or "Enabled"
tree.write(path, encoding="UTF-8", xml_declaration=True)
print(f" wrote External (AuthenticationRequired={req.text})")
PY
docker start "$app" >/dev/null
grep Authentication "$cfg"
}
for app in sonarr radarr prowlarr whisparr; do
set_servarr_external "$app"
done
echo "=== bazarr: verify auth disabled ==="
python3 - <<'PY'
import yaml
path = "/config/bazarr/config/config.yaml"
with open(path) as f:
d = yaml.safe_load(f)
auth = d.setdefault("auth", {})
changed = False
if auth.get("type") is not None:
auth["type"] = None
changed = True
if auth.get("username"):
auth["username"] = ""
changed = True
if auth.get("password"):
auth["password"] = ""
changed = True
if changed:
with open(path, "w") as f:
yaml.safe_dump(d, f, default_flow_style=False, sort_keys=False)
print(" updated bazarr auth -> type null")
else:
print(" already type null / no credentials")
PY
docker restart bazarr >/dev/null
echo "=== qbittorrent: disable WebUI auth (keep API key for *arr) ==="
QCONF="/config/qbittorrent/qBittorrent/qBittorrent.conf"
docker stop qbittorrent >/dev/null
python3 - "$QCONF" <<'PY'
from pathlib import Path
import sys
path = Path(sys.argv[1])
lines = path.read_text().splitlines()
out = []
added = False
for line in lines:
if line.startswith("WebUI\\AuthenticationEnabled="):
out.append("WebUI\\AuthenticationEnabled=false")
added = True
continue
out.append(line)
if not added:
final = []
for line in out:
final.append(line)
if line.strip() == "[Preferences]":
final.append("WebUI\\AuthenticationEnabled=false")
added = True
out = final
path.write_text("\n".join(out) + "\n")
print(" WebUI\\AuthenticationEnabled=false")
PY
grep 'WebUI\\AuthenticationEnabled' "$QCONF" || true
docker start qbittorrent >/dev/null
echo "=== nzbget: disable form auth (API creds unchanged) ==="
NZB="/config/nzbget/nzbget.conf"
sed -i 's/^FormAuth=.*/FormAuth=no/' "$NZB"
sed -i 's/^SecureControl=.*/SecureControl=no/' "$NZB"
grep -E '^(FormAuth|SecureControl)=' "$NZB"
docker restart nzbget >/dev/null
echo "=== stash: external authwall (Authentik + internal_only) ==="
python3 - <<'PY'
from pathlib import Path
import re
path = Path("/config/stash/config.yml")
text = path.read_text()
text = re.sub(r'^dangerous_allow_public_without_auth:.*$', 'dangerous_allow_public_without_auth: "true"', text, flags=re.M)
text = re.sub(r'^username:.*$', 'username: ""', text, flags=re.M)
text = re.sub(r'^password:.*$', 'password: ""', text, flags=re.M)
text = re.sub(r'^security_tripwire_accessed_from_public_internet:.*\n', '', text, flags=re.M)
path.write_text(text)
print(" dangerous_allow_public_without_auth=true, cleared username/password")
PY
docker restart stash >/dev/null
echo "=== *arr download clients: internal Docker URLs (bypass Caddy forward_auth) ==="
python3 - <<'PY'
import json
import sqlite3
# Servarr apps talk to download clients over the media network, not via *.ginnoir.com.
INTERNAL = {
"Nzbget": ("nzbget", 6789),
"QBittorrent": ("qbittorrent", 3232),
"Deluge": ("deluge", 8112),
}
PUBLIC_SUFFIX = ".ginnoir.com"
LOCALHOSTS = {"localhost", "127.0.0.1"}
for app in ("sonarr", "radarr", "prowlarr", "whisparr"):
db = f"/config/{app}/{app}.db"
conn = sqlite3.connect(db)
cur = conn.cursor()
cur.execute("SELECT Id, Name, Implementation, Settings FROM DownloadClients")
for cid, name, impl, settings_json in cur.fetchall():
if impl not in INTERNAL:
continue
settings = json.loads(settings_json)
host = (settings.get("host") or "").strip()
internal_host, port = INTERNAL[impl]
if host == internal_host and settings.get("port") == port and not settings.get("useSsl"):
continue
if PUBLIC_SUFFIX in host or host in LOCALHOSTS:
old = f"{host}:{settings.get('port')}"
if settings.get("useSsl"):
old += " ssl"
settings["host"] = internal_host
settings["port"] = port
settings["useSsl"] = False
cur.execute(
"UPDATE DownloadClients SET Settings=? WHERE Id=?",
(json.dumps(settings), cid),
)
print(f" {app}/{name}: {old} -> {internal_host}:{port} http")
conn.commit()
conn.close()
PY
echo "=== deluge: bypass web login (Authentik at edge; patch re-applies on recreate) ==="
if docker exec deluge grep -q 'homelab external auth' /lsiopy/lib/python3.12/site-packages/deluge/ui/web/auth.py 2>/dev/null; then
echo " already patched"
else
docker cp /tmp/patch-deluge-auth.py deluge:/tmp/patch-deluge-auth.py
docker exec deluge python3 /tmp/patch-deluge-auth.py
docker restart deluge >/dev/null
echo " patched and restarted"
fi
echo "=== done (tautulli untouched) ==="