#!/usr/bin/env bash # build-deploy.sh — cross-build ZeroClaw for the Arduino Uno Q (aarch64) on this # Mac and deploy it to the board over adb, as a proper drop-in replacement for # /home/arduino/zeroclaw. One command for the "make a fix → get it on the board" # loop. # # WHY THE FLAGS (learned the hard way — see memory apess-node-modalities-and-083): # hardware MANDATORY. Without it the build silently drops the 10 # Uno Q peripheral tools (GPIO bridge, FLASH, sysfs_led, # camera, network, i2cdetect). Symptom: agent load log # shows "before:48" tools instead of "before:58". # peripheral-rpi the rppal-backed Linux peripheral path (sysfs LED, etc.). # embedded-web bakes the web dashboard SPA (web/dist) into the binary, # so the board serves the ZeroClaw dashboard at :8080/. # REQUIRES web/dist to be built FIRST (step 1) — and built # with base="/_app/" (the production vite build), else the # gateway serves index.html for /assets/* and the SPA won't # boot. If the embed ever goes stale, set # `[gateway] web_dist_dir = "/home/arduino/web-dist"` and # push web/dist to the board as a runtime fallback. # zeroclaw-gateway/gateway-voice-duplex # browser-mic streaming voice over /ws/chat (namespaced — # no root-crate alias). # musl target the deployed binary is static (glibc-independent). # cargo-zigbuild cross-links from macOS via zig (no cross toolchain). # (Alt without zig: cargo build with # CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER=aarch64-linux-musl-gcc.) # # PREREQS (already set up on this Mac): rustup targets aarch64-unknown-linux-musl, # cargo-zigbuild + zig on PATH, node/npm for the web build, adb, the board on USB. # # Usage: # deploy/uno-q/build-deploy.sh # build + deploy + verify # ZC_REPO=/path/to/zeroclaw deploy/uno-q/build-deploy.sh # SERIAL=65301572 deploy/uno-q/build-deploy.sh # deploy/uno-q/build-deploy.sh --build-only # build + verify, no deploy # deploy/uno-q/build-deploy.sh --rollback # restore the previous binary from backup set -euo pipefail ZC_REPO="${ZC_REPO:-$HOME/projects/zeroclaw}" SERIAL="${SERIAL:-65301572}" TARGET="aarch64-unknown-linux-musl" FEATURES="hardware,peripheral-rpi,embedded-web,zeroclaw-gateway/gateway-voice-duplex" BIN="$ZC_REPO/target/$TARGET/release/zeroclaw" REMOTE="/home/arduino/zeroclaw" SUPERVISOR="/home/arduino/zeroclaw-supervisor.sh" adbsh() { adb -s "$SERIAL" shell "$1" /dev/null)" = "device" ] || { echo "✗ board $SERIAL not connected (adb get-state != device)"; exit 1; } } rollback() { require_device echo "→ rolling back to $REMOTE.prev.bak" adbsh "test -f $REMOTE.prev.bak" || { echo "✗ no backup at $REMOTE.prev.bak"; exit 1; } adbsh "pkill -f '[z]eroclaw-supervisor'; sleep 1" adbsh "pkill -9 -f 'zeroclaw daemon'; sleep 1" adbsh "mv -f $REMOTE $REMOTE.rolledback.bak; mv -f $REMOTE.prev.bak $REMOTE; chmod +x $REMOTE" adbsh "setsid sh -c 'exec $SUPERVISOR' >/home/arduino/zeroclaw-supervisor.log 2>&1 /home/arduino/zeroclaw-supervisor.log 2>&1 /dev/null 2>&1 || true for i in $(seq 1 30); do [ "$(curl -s -m 3 -o /dev/null -w '%{http_code}' http://127.0.0.1:8080/health 2>/dev/null)" = "200" ] && { echo "✓ daemon healthy on new binary (sha:$(adbsh "sha256sum $REMOTE | cut -c1-16"))"; exit 0; } sleep 3 done echo "✗ daemon did not report healthy in time — check the board; roll back with: $0 --rollback" exit 1