From c4cd91920a2483884ac09d67af61a082213e762f Mon Sep 17 00:00:00 2001 From: Will Anderson Date: Sat, 11 Apr 2026 11:14:23 -0500 Subject: [PATCH] Fix qBittorrent auth: stamp known password hash via initContainer LocalhostAuthEnabled=false isn't honored in this qBT version. Instead, initContainer stamps the PBKDF2 hash for admin:adminadmin before startup so all three pods have consistent credentials. portforward-helper and coordinator restored to cookie-based SID auth. --- .../legion/k8s/media/fornax-coordinator.yaml | 40 ++++++++++++++++++- servers/legion/k8s/media/fornax-workers.yaml | 28 +++++++------ .../legion/k8s/media/gluetun-qbittorrent.yaml | 15 ++++--- 3 files changed, 63 insertions(+), 20 deletions(-) diff --git a/servers/legion/k8s/media/fornax-coordinator.yaml b/servers/legion/k8s/media/fornax-coordinator.yaml index 9f7f86e..734ea48 100644 --- a/servers/legion/k8s/media/fornax-coordinator.yaml +++ b/servers/legion/k8s/media/fornax-coordinator.yaml @@ -26,10 +26,30 @@ data: PRIMARY = BACKENDS[0] if BACKENDS else "" sids = {} + def fetch_sid(backend): + data = urllib.parse.urlencode({"username": USER, "password": PASS}).encode() + req = urllib.request.Request(f"{backend}/api/v2/auth/login", data=data) + try: + with urllib.request.urlopen(req, timeout=5) as r: + for k, v in r.getheaders(): + if k.lower() == "set-cookie" and "SID=" in v: + return v.split("SID=")[1].split(";")[0] + except Exception as e: + print(f"auth failed for {backend}: {e}") + return None + + def sid(backend): + if not sids.get(backend): + sids[backend] = fetch_sid(backend) + return sids.get(backend, "") + def active_count(backend): """Return number of downloading torrents on this backend, or large int on error.""" try: - req = urllib.request.Request(f"{backend}/api/v2/torrents/info?filter=downloading") + req = urllib.request.Request( + f"{backend}/api/v2/torrents/info?filter=downloading", + headers={"Cookie": f"SID={sid(backend)}"}, + ) with urllib.request.urlopen(req, timeout=3) as r: return len(json.loads(r.read())) except Exception: @@ -42,7 +62,7 @@ data: return chosen def forward(backend, method, path, body, content_type): - headers = {} + headers = {"Cookie": f"SID={sid(backend)}"} if content_type: headers["Content-Type"] = content_type req = urllib.request.Request( @@ -53,6 +73,18 @@ data: with urllib.request.urlopen(req, timeout=30) as r: return r.status, r.read(), list(r.getheaders()) except urllib.error.HTTPError as e: + if e.code == 403: + sids[backend] = fetch_sid(backend) + headers["Cookie"] = f"SID={sids[backend]}" + req2 = urllib.request.Request( + f"{backend}{path}", data=body if method == "POST" else None, + headers=headers, method=method, + ) + try: + with urllib.request.urlopen(req2, timeout=30) as r: + return r.status, r.read(), list(r.getheaders()) + except Exception as e2: + return 500, str(e2).encode(), [] return e.code, e.read(), [] except Exception as e: return 500, str(e).encode(), [] @@ -107,6 +139,10 @@ spec: env: - name: BACKENDS value: "http://qbittorrent.media.svc:8080,http://fornax-worker-tx253.media.svc:8080,http://fornax-worker-tx34.media.svc:8080" + - name: QBT_USER + value: "admin" + - name: QBT_PASS + value: "adminadmin" ports: - containerPort: 8080 volumeMounts: diff --git a/servers/legion/k8s/media/fornax-workers.yaml b/servers/legion/k8s/media/fornax-workers.yaml index e8e888f..52eda94 100644 --- a/servers/legion/k8s/media/fornax-workers.yaml +++ b/servers/legion/k8s/media/fornax-workers.yaml @@ -41,16 +41,16 @@ spec: - -c - | CONF=/config/qBittorrent/qBittorrent.conf + HASH='@ByteArray(HqYj1eGsdXlQ4CSy597Y9A==:J9hsJIlU5FYfHb5rY5qQoIpVpTijryS/H+CE07oMtplL/ytneBVFd2tfVJtqGjhdht8tEi4wmqSSlqTgEu444w==)' mkdir -p /config/qBittorrent if [ -f "$CONF" ]; then - if grep -q "LocalhostAuthEnabled" "$CONF"; then - sed -i 's/LocalhostAuthEnabled=.*/LocalhostAuthEnabled=false/' "$CONF" + if grep -q "Password_PBKDF2" "$CONF"; then + sed -i "s|WebUI\\\\Password_PBKDF2=.*|WebUI\\\\Password_PBKDF2=\"$HASH\"|" "$CONF" else - sed -i '/^\[Preferences\]/a WebUI\\LocalhostAuthEnabled=false' "$CONF" || \ - printf '\n[Preferences]\nWebUI\\LocalhostAuthEnabled=false\n' >> "$CONF" + sed -i "/^\[Preferences\]/a WebUI\\\\Password_PBKDF2=\"$HASH\"" "$CONF" fi fi - echo "Config patched: $(grep LocalhostAuth $CONF 2>/dev/null || echo 'will be set on first run')" + echo "Password hash set." volumeMounts: - name: config mountPath: /config @@ -103,7 +103,9 @@ spec: PORT=$(cat /tmp/gluetun/forwarded_port) if [ -n "$PORT" ] && [ "$PORT" != "0" ]; then echo "$(date): Forwarded port: $PORT — updating qBittorrent" - curl -s -X POST http://localhost:8080/api/v2/app/setPreferences \ + curl -s -c /tmp/qbt.txt -X POST http://localhost:8080/api/v2/auth/login \ + -d "username=admin&password=adminadmin" >/dev/null 2>&1 + curl -s -b /tmp/qbt.txt -X POST http://localhost:8080/api/v2/app/setPreferences \ -d "json={\"listen_port\":$PORT,\"random_port\":false}" >/dev/null 2>&1 echo "$(date): qBittorrent listen port set to $PORT" else @@ -215,16 +217,16 @@ spec: - -c - | CONF=/config/qBittorrent/qBittorrent.conf + HASH='@ByteArray(HqYj1eGsdXlQ4CSy597Y9A==:J9hsJIlU5FYfHb5rY5qQoIpVpTijryS/H+CE07oMtplL/ytneBVFd2tfVJtqGjhdht8tEi4wmqSSlqTgEu444w==)' mkdir -p /config/qBittorrent if [ -f "$CONF" ]; then - if grep -q "LocalhostAuthEnabled" "$CONF"; then - sed -i 's/LocalhostAuthEnabled=.*/LocalhostAuthEnabled=false/' "$CONF" + if grep -q "Password_PBKDF2" "$CONF"; then + sed -i "s|WebUI\\\\Password_PBKDF2=.*|WebUI\\\\Password_PBKDF2=\"$HASH\"|" "$CONF" else - sed -i '/^\[Preferences\]/a WebUI\\LocalhostAuthEnabled=false' "$CONF" || \ - printf '\n[Preferences]\nWebUI\\LocalhostAuthEnabled=false\n' >> "$CONF" + sed -i "/^\[Preferences\]/a WebUI\\\\Password_PBKDF2=\"$HASH\"" "$CONF" fi fi - echo "Config patched: $(grep LocalhostAuth $CONF 2>/dev/null || echo 'will be set on first run')" + echo "Password hash set." volumeMounts: - name: config mountPath: /config @@ -276,7 +278,9 @@ spec: PORT=$(cat /tmp/gluetun/forwarded_port) if [ -n "$PORT" ] && [ "$PORT" != "0" ]; then echo "$(date): Forwarded port: $PORT — updating qBittorrent" - curl -s -X POST http://localhost:8080/api/v2/app/setPreferences \ + curl -s -c /tmp/qbt.txt -X POST http://localhost:8080/api/v2/auth/login \ + -d "username=admin&password=adminadmin" >/dev/null 2>&1 + curl -s -b /tmp/qbt.txt -X POST http://localhost:8080/api/v2/app/setPreferences \ -d "json={\"listen_port\":$PORT,\"random_port\":false}" >/dev/null 2>&1 echo "$(date): qBittorrent listen port set to $PORT" else diff --git a/servers/legion/k8s/media/gluetun-qbittorrent.yaml b/servers/legion/k8s/media/gluetun-qbittorrent.yaml index b11ac8a..b499e7f 100644 --- a/servers/legion/k8s/media/gluetun-qbittorrent.yaml +++ b/servers/legion/k8s/media/gluetun-qbittorrent.yaml @@ -35,16 +35,17 @@ spec: - -c - | CONF=/config/qBittorrent/qBittorrent.conf + HASH='@ByteArray(HqYj1eGsdXlQ4CSy597Y9A==:J9hsJIlU5FYfHb5rY5qQoIpVpTijryS/H+CE07oMtplL/ytneBVFd2tfVJtqGjhdht8tEi4wmqSSlqTgEu444w==)' mkdir -p /config/qBittorrent if [ -f "$CONF" ]; then - if grep -q "LocalhostAuthEnabled" "$CONF"; then - sed -i 's/LocalhostAuthEnabled=.*/LocalhostAuthEnabled=false/' "$CONF" + # Set known adminadmin password hash + if grep -q "Password_PBKDF2" "$CONF"; then + sed -i "s|WebUI\\\\Password_PBKDF2=.*|WebUI\\\\Password_PBKDF2=\"$HASH\"|" "$CONF" else - sed -i '/^\[Preferences\]/a WebUI\\LocalhostAuthEnabled=false' "$CONF" || \ - printf '\n[Preferences]\nWebUI\\LocalhostAuthEnabled=false\n' >> "$CONF" + sed -i "/^\[Preferences\]/a WebUI\\\\Password_PBKDF2=\"$HASH\"" "$CONF" fi fi - echo "Config patched: $(grep LocalhostAuth $CONF 2>/dev/null || echo 'will be set on first run')" + echo "Password hash set." volumeMounts: - name: config mountPath: /config @@ -96,7 +97,9 @@ spec: PORT=$(cat /tmp/gluetun/forwarded_port) if [ -n "$PORT" ] && [ "$PORT" != "0" ]; then echo "$(date): Forwarded port: $PORT — updating qBittorrent" - curl -s -X POST http://localhost:8080/api/v2/app/setPreferences \ + curl -s -c /tmp/qbt.txt -X POST http://localhost:8080/api/v2/auth/login \ + -d "username=admin&password=adminadmin" >/dev/null 2>&1 + curl -s -b /tmp/qbt.txt -X POST http://localhost:8080/api/v2/app/setPreferences \ -d "json={\"listen_port\":$PORT,\"random_port\":false}" >/dev/null 2>&1 echo "$(date): qBittorrent listen port set to $PORT" else