recover.sh: re-tunnels, relaunches the supervisor with the cloud token in its environment (env-only, read from ANTHROPIC_OAUTH_TOKEN — never on disk), starts the matrix bridge app only if down, preserves a running llama to skip cold reload, and verifies end-to-end (llama/daemon/bridge health + demo agent on claude-sonnet-5 + matrix_pattern fires). Turns a mid-demo disconnect into a ~30s fix. Tokens are read from the env, so the script carries no secrets. Co-Authored-By: Claude Opus 4.8 <[email protected]>
82 lines
4.2 KiB
Bash
Executable File
82 lines
4.2 KiB
Bash
Executable File
#!/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 & sleep 2; echo done" >/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"
|