#!/usr/bin/env bash # recover.sh — one-command recovery for the APESS Uno Q demo node after a USB drop. # # On a disconnect the daemon/llama/bridge die and the cloud token (env-only) is lost. # This re-tunnels, relaunches the supervisor WITH the token in its environment, # restarts the matrix bridge app, and verifies the whole chain end-to-end. # # Secrets are read from the environment — NEVER hardcoded here. Export first: # export ANTHROPIC_OAUTH_TOKEN=sk-ant-oat01-... # required: cloud brain # export NODE_TOKEN=zc_... # optional: end-to-end verify # ./recover.sh # # Env knobs: SERIAL (default 65301572), the two tokens above. set -u SERIAL="${SERIAL:-65301572}" A(){ adb -s "$SERIAL" "$@"; } S(){ adb -s "$SERIAL" shell "$@"; } ok(){ printf ' \033[32m✓\033[0m %s\n' "$*"; } bad(){ printf ' \033[31m✗\033[0m %s\n' "$*"; } step(){ printf '\n\033[1m%s\033[0m\n' "$*"; } step "0· Preconditions" if ! adb devices | grep -q "^${SERIAL}[[:space:]]*device"; then bad "board $SERIAL not attached — re-plug USB, then re-run"; exit 1 fi ok "board $SERIAL attached" [ -n "${ANTHROPIC_OAUTH_TOKEN:-}" ] || { bad "ANTHROPIC_OAUTH_TOKEN not set — cloud brain will fail. export it and re-run"; exit 1; } ok "cloud token present in env" step "1· Tunnel" A forward tcp:8080 tcp:8080 >/dev/null && ok "adb forward :8080 → laptop localhost:8080" step "2· Stop stale supervisor + daemons (preserve llama)" S 'for p in $(ps -C zeroclaw-supervisor -o pid= 2>/dev/null); do kill -9 $p 2>/dev/null; done for p in $(ps -C zeroclaw -o pid= 2>/dev/null); do kill -9 $p 2>/dev/null; done rm -f /home/arduino/.zc-supervisor.lock; sleep 2 echo " daemons left: $(ps -C zeroclaw -o pid= 2>/dev/null | wc -l)"' step "3· Relaunch supervisor WITH token env (env-only, never on disk)" S "export ANTHROPIC_OAUTH_TOKEN='$ANTHROPIC_OAUTH_TOKEN'; \ export ZEROCLAW_providers__models__anthropic__max__api_key='$ANTHROPIC_OAUTH_TOKEN'; \ setsid nohup /home/arduino/zeroclaw-supervisor.sh >/dev/null 2>&1 /dev/null S 'pgrep -f "[z]eroclaw-supervisor" >/dev/null' && ok "supervisor relaunched" || bad "supervisor did NOT start" step "4· Matrix bridge app (start only if down)" if [ "$(S 'printf "ping\n" | timeout 4 nc 127.0.0.1 9999 2>/dev/null')" = "pong" ]; then ok "bridge already running" else S 'cd ~/ArduinoApps/uno-q-bridge && TMPDIR=/tmp arduino-app-cli app start ~/ArduinoApps/uno-q-bridge 2>&1 | tail -1' fi step "5· Wait for services" for i in $(seq 1 30); do L=$(S 'curl -sf -m3 http://127.0.0.1:8083/health >/dev/null 2>&1 && echo 1 || echo 0') D=$(S 'curl -sf -m3 http://127.0.0.1:8080/health >/dev/null 2>&1 && echo 1 || echo 0') printf '\r [%02d] llama=%s daemon=%s ' "$i" "$L" "$D" [ "$D" = 1 ] && break; sleep 6 done; echo [ "$L" = 1 ] && ok "llama :8083 healthy" || bad "llama :8083 DOWN (cold load can take 3–5 min; re-check)" [ "$D" = 1 ] && ok "daemon :8080 healthy" || { bad "daemon :8080 DOWN"; exit 1; } step "6· Bridge (matrix responder)" P=$(S 'printf "ping\n" | timeout 4 nc 127.0.0.1 9999 2>/dev/null') [ "$P" = "pong" ] && ok "bridge :9999 responds (ping→pong)" || bad "bridge :9999 not responding — re-run step 4" step "7· End-to-end: demo agent = cloud sonnet + matrix fires" if [ -n "${NODE_TOKEN:-}" ]; then S 'printf "matrix 0\n" | timeout 5 nc 127.0.0.1 9999 >/dev/null 2>&1' R=$(curl -s -m 30 -X POST "http://127.0.0.1:8080/webhook?agent=demo" \ -H "Authorization: Bearer $NODE_TOKEN" -H 'Content-Type: application/json' \ -d '{"message":"Show the rain animation on the LED matrix"}') echo "$R" | grep -q "claude-sonnet-5" && ok "agent=demo on claude-sonnet-5" || bad "agent NOT on sonnet — token may not have loaded: $R" M=$(S "docker logs --since 40s uno-q-bridge-main-1 2>&1 | grep -c \"parts=\['matrix', '1'\]\"") [ "${M:-0}" -ge 1 ] && ok "matrix_pattern fired (rain)" || bad "matrix did not change" else echo " (NODE_TOKEN unset — skipping authenticated end-to-end check)" fi step "Recovery complete." echo " Voice proxy (laptop): if it was running it auto-recovers via the re-armed tunnel." echo " If not running: NODE_URL=http://127.0.0.1:8080 NODE_TOKEN=\$NODE_TOKEN python3 deploy/voice-client/serve.py 8090"