diff --git a/aria-brain/agent.py b/aria-brain/agent.py
index f0408df..8c1234f 100644
--- a/aria-brain/agent.py
+++ b/aria-brain/agent.py
@@ -2670,8 +2670,10 @@ class Agent:
# Langlaeufer brauchen mehr als den Bridge-Default (20s): eine APK ueber
# WLAN zu installieren dauert 30-60s. Ohne das meldet ARIA "Timeout",
# obwohl die Installation noch laeuft.
- slow = action.startswith("adb.") or action == "ssh.exec"
- sat_timeout = 200.0 if slow else 20.0
+ # adb.*: Warten auf die Geraete-Autorisierung (bis 90s) + Download +
+ # Installation koennen zusammen mehrere Minuten dauern.
+ sat_timeout = (320.0 if action.startswith("adb.")
+ else 200.0 if action == "ssh.exec" else 20.0)
result = _post("/internal/satellite", {
"op": "command", "satellite": sat,
"device": arguments.get("device") or "", "action": action, "params": params,
diff --git a/diagnostic/index.html b/diagnostic/index.html
index af40640..58fdecc 100644
--- a/diagnostic/index.html
+++ b/diagnostic/index.html
@@ -1605,8 +1605,9 @@
Installiert die Agent-App per ADB über das Netzwerk. Voraussetzung am Gerät:
ADB-Debugging aktiv (Fire TV: Einstellungen › Mein Fire TV ›
- Entwickleroptionen) — beim ersten Mal erscheint dort ein Dialog
- „USB-Debugging zulassen?", den Du bestätigen musst.
+ Entwickleroptionen). Beim ersten Mal erscheint dort
+ „USB-Debugging zulassen?" — einfach bestätigen, während der Rollout läuft:
+ er wartet darauf und macht dann automatisch weiter.
läuft …';
+ const log = document.getElementById('apk-log');
+ log.style.display = 'block';
+ log.innerHTML = '👀 Jetzt auf den Bildschirm des Geräts schauen.\n' +
+ 'Beim ersten Mal erscheint dort „USB-Debugging zulassen?" — bestätigen\n' +
+ '(Haken bei „Immer erlauben"). Ich warte bis zu 90 Sekunden darauf und\n' +
+ 'installiere dann automatisch weiter. Kein zweiter Klick nötig.\n\n' +
+ 'Danach: APK laden + installieren, zusammen 1–3 Minuten.';
send({ action: 'sat_command', satellite: apkTarget.satId, cmd: 'adb.install',
requestId: apkRequestId, params: { ip: apkTarget.ip, url } });
}
diff --git a/satellite/satellite.py b/satellite/satellite.py
index b0b89ba..80338d9 100644
--- a/satellite/satellite.py
+++ b/satellite/satellite.py
@@ -145,6 +145,10 @@ CONTROL_ALLOWLIST = [
# Download brauchen.
ADB_PORT = int(os.environ.get("ADB_PORT", "5555") or "5555")
ADB_TIMEOUT_SEC = float(os.environ.get("ADB_TIMEOUT_SEC", "180") or "180")
+# Beim ERSTEN Verbinden zeigt das Geraet "USB-Debugging zulassen?" auf dem
+# Bildschirm. Statt mit Fehler abzubrechen (und den Nutzer nochmal klicken zu
+# lassen) wartet der Satellit so lange auf die Bestaetigung.
+ADB_AUTH_WAIT_SEC = float(os.environ.get("ADB_AUTH_WAIT_SEC", "90") or "90")
APK_CACHE_DIR = os.environ.get("APK_CACHE_DIR", "/data/apk")
APK_MAX_BYTES = int(os.environ.get("APK_MAX_BYTES", str(64 * 1024 * 1024)) or str(64 * 1024 * 1024))
@@ -1121,6 +1125,28 @@ def _adb_state(target: str) -> str:
return ""
+def _adb_wait_authorized(target: str, seconds: float) -> tuple:
+ """Wartet, bis das Geraet autorisiert ist ('device').
+
+ Beim ersten Kontakt erscheint auf dem Fernseher/Handy der Dialog
+ "USB-Debugging zulassen?". Frueher brach die Installation hier ab und man
+ musste nach dem Bestaetigen ein zweites Mal starten — jetzt wartet der
+ Satellit einfach, bis der Nutzer bestaetigt hat. Gibt (ok, zustand).
+ """
+ import time as _time
+ deadline = _time.time() + max(0.0, seconds)
+ last = _adb_state(target)
+ while _time.time() < deadline:
+ if last == "device":
+ return True, last
+ _time.sleep(2.0)
+ # Erneut verbinden: nach dem Bestaetigen wechselt der Zustand oft erst
+ # mit einem frischen connect von 'unauthorized'/'offline' auf 'device'.
+ _adb_run(["connect", target], timeout=15)
+ last = _adb_state(target)
+ return last == "device", last
+
+
def _do_adb_devices(params: dict) -> dict:
"""Listet die dem Satelliten bekannten ADB-Geraete."""
rc, out, err = _adb_run(["devices", "-l"], timeout=20)
@@ -1140,10 +1166,19 @@ def _do_adb_connect(params: dict) -> dict:
if not target:
return {"ok": False, "error": "ip/host erforderlich."}
ok, msg = _adb_connect(target)
- if not ok:
+ state = _adb_state(target)
+ wait = float(params.get("auth_wait") or ADB_AUTH_WAIT_SEC)
+ if (not ok or state != "device") and wait > 0:
+ authed, state = _adb_wait_authorized(target, wait)
+ if not authed:
+ return {"ok": False, "error":
+ "Nicht autorisiert — auf dem Bildschirm des Geraets "
+ "'USB-Debugging zulassen?' bestaetigen. "
+ f"(gewartet: {wait:.0f}s, Zustand: '{state or '?'}')"}
+ msg = f"verbunden (nach Bestaetigung am Geraet)"
+ elif not ok:
return {"ok": False, "error": msg}
- return {"ok": True, "result": {"target": target, "state": _adb_state(target),
- "message": msg}}
+ return {"ok": True, "result": {"target": target, "state": state, "message": msg}}
def _do_adb_shell(params: dict) -> dict:
@@ -1241,12 +1276,23 @@ def _do_adb_install(params: dict) -> dict:
return {"ok": False, "error": err}
ok, msg = _adb_connect(target)
- if not ok:
- return {"ok": False, "error": msg}
state = _adb_state(target)
- if state and state != "device":
- return {"ok": False, "error": f"Geraet im Zustand '{state}' — "
- "bei 'unauthorized' den Dialog auf dem Bildschirm bestaetigen."}
+ # Erstkontakt: Das Geraet zeigt jetzt "USB-Debugging zulassen?" auf dem
+ # Bildschirm. Statt abzubrechen warten wir auf die Bestaetigung — ein Klick
+ # im Diagnostic genuegt, kein zweiter Anlauf noetig.
+ wait = float(params.get("auth_wait") or ADB_AUTH_WAIT_SEC)
+ if (not ok or state != "device") and wait > 0:
+ logger.info("[adb] %s: warte bis zu %.0fs auf Autorisierung (Zustand '%s')",
+ target, wait, state or "?")
+ authed, state = _adb_wait_authorized(target, wait)
+ if not authed:
+ hint = ("Das Geraet hat die Verbindung nicht autorisiert. Auf dem "
+ "Bildschirm erscheint 'USB-Debugging zulassen?' — bitte "
+ "bestaetigen (Haken bei 'Immer erlauben'). "
+ f"Ich habe {wait:.0f}s gewartet, Zustand: '{state or 'nicht erreichbar'}'.")
+ return {"ok": False, "error": hint if (state or ok) else msg}
+ elif not ok:
+ return {"ok": False, "error": msg}
args = ["-s", target, "install", "-r"] # -r = Update statt Neuinstallation
if params.get("downgrade"):