Files
apress/deploy/uno-q/recover.sh
T
Omar SobhandClaude Opus 4.8 3fef8c4d23 feat(uno-q): one-command demo-node recovery after USB drop
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]>
2026-07-21 04:51:43 -07:00

82 lines
4.2 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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 35 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"