#!/bin/bash # ff-apikey-guard.sh — repariert kaputte Arr-ApiKeys in FileFlows-Flows # # URSACHE: Die FileFlows-API liefert ApiKey-Felder im GET als "[REDACTED]". # Wird ein Flow aus der Web-UI (oder per GET->PUT-Roundtrip) gespeichert, # landet dieser Platzhalter in der DB. Die "Trigger Manual Import"-Nodes # schlagen dann still fehl (Arr antwortet 401), der n8n-Handoff bleibt auf # "processing" und der SAB-Job haengt bis zum 24h-Timeout in der Queue. # # Dieses Skript erkennt ApiKeys != 32 Zeichen und schreibt die echten Keys # aus /app-config/ff-guard/arr-keys.map zurueck (Format: " "). set -u BUTLER="${BUTLER:-http://10.5.85.2:8888}" GUARD_DIR="${GUARD_DIR:-/app-config/ff-guard}" TOKEN_FILE="$GUARD_DIR/butler.token" KEYMAP="$GUARD_DIR/arr-keys.map" LOG="$GUARD_DIR/guard.log" DB="${DB:-/app-config/fileflows-data/Data/FileFlows.sqlite}" mkdir -p "$GUARD_DIR" log() { echo "$(date '+%d.%m.%Y %H:%M:%S'): $1" >> "$LOG"; } for f in "$TOKEN_FILE" "$KEYMAP"; do [ -r "$f" ] || { log "FEHLER: Datei fehlt oder nicht lesbar: $f"; exit 1; } done [ -r "$DB" ] || { log "FEHLER: DB nicht lesbar: $DB"; exit 1; } TOKEN="$(tr -d '\n\r' < "$TOKEN_FILE")" # Flows mit defekten Keys ermitteln FLOWUIDS="$(python3 - "$DB" <<'PY' import sqlite3, json, sys con = sqlite3.connect("file:%s?mode=ro" % sys.argv[1], uri=True) for uid, name, typ, d in con.execute( "select Uid,Name,Type,Data from DbObject where Type like '%Flow%'"): try: o = json.loads(d) except Exception: continue for p in o.get("Parts", []): k = (p.get("Model") or {}).get("ApiKey") if k is not None and len(str(k)) != 32: print(uid) break PY )" if [ -z "$FLOWUIDS" ]; then log "OK: alle Flow-ApiKeys 32 Zeichen" exit 0 fi RC=0 for uid in $FLOWUIDS; do log "WARNUNG: defekte ApiKeys in Flow $uid — repariere" OUT="$(mktemp)"; PATCHED="$(mktemp)" if ! curl -sf -m 60 -H "Authorization: Bearer $TOKEN" \ "$BUTLER/fileflows/api/flow/$uid" -o "$OUT"; then log "FEHLER: GET flow $uid fehlgeschlagen" rm -f "$OUT" "$PATCHED"; RC=1; continue fi CHANGED="$(python3 - "$OUT" "$KEYMAP" "$PATCHED" <<'PY' import json, sys flow = json.load(open(sys.argv[1])) keys = {} for line in open(sys.argv[2]): parts = line.split() if len(parts) == 2 and len(parts[1]) == 32: keys[parts[0]] = parts[1] n = 0 for p in flow.get("Parts", []): mod = p.get("Model") or {} if "ApiKey" not in mod: continue url = str(mod.get("URL") or mod.get("ApiUrl") or "") port = url.rstrip("/").rsplit(":", 1)[-1] if port in keys and str(mod["ApiKey"]) != keys[port]: mod["ApiKey"] = keys[port] n += 1 json.dump(flow, open(sys.argv[3], "w")) print(n) PY )" if [ "${CHANGED:-0}" = "0" ]; then log "WARNUNG: Flow $uid — kein Key konnte gemappt werden (Ports pruefen)" rm -f "$OUT" "$PATCHED"; RC=1; continue fi HTTP="$(curl -s -m 60 -o /dev/null -w '%{http_code}' -X PUT \ -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \ "$BUTLER/fileflows/api/flow" --data @"$PATCHED")" if [ "$HTTP" = "200" ]; then log "Flow $uid repariert: $CHANGED Key(s) zurueckgeschrieben (HTTP 200)" else log "FEHLER: PUT flow $uid -> HTTP $HTTP" RC=1 fi rm -f "$OUT" "$PATCHED" done exit $RC