The agent now drives the matrix in <1s via the resident responder instead of compiling+flashing a sketch (~95s). Pieces: - matrix-relay/: a tiny container (python-apps-base) running relay.py — a TCP:9999 → RouterBridge relay. The ZeroClaw daemon runs on the host for hardware access, but the RouterBridge python binding (arduino.app_utils) only ships in the App Lab image, so JUST the relay runs containerized, mounting the router socket + publishing :9999 to loopback. - config.template.toml: add matrix_pattern + matrix_text to the default risk profile's allowed_tools/auto_approve (the capability filter hides tools not listed — this was why matrix_text wasn't exposed to the agent). - skills/led-matrix: lead with "use matrix_text / matrix_pattern first"; only flash for custom frames (flashing overwrites the responder). NOTE skills load from each agent's workspace copy, not shared/skills — push to all workspaces. - provision-host-daemon.sh: flash the responder once + start the relay. Proven on-hardware: "show a heart" and "scroll GO CLAWS" both instant via the agent, no flash. Co-Authored-By: Claude Opus 4.8 <[email protected]>
100 lines
6.1 KiB
Bash
Executable File
100 lines
6.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# provision-host-daemon.sh — set up the ZeroClaw agent node as a HOST systemd
|
|
# service, NOT the App Lab container.
|
|
#
|
|
# WHY host, not container: the App Lab python-apps-base container can't actually
|
|
# drive the board. Proven on-hardware:
|
|
# • I2C/SPI: /dev is bind-mounted but the container's device cgroup blocks it
|
|
# (EPERM), and the image lacks i2cdetect.
|
|
# • Flashing: the image has no arduino-cli / Zephyr toolchain (uno_q_flash fails
|
|
# "arduino-cli not found").
|
|
# • /admin/reload: refused (the container publishes :8080 via NAT, so even
|
|
# host→localhost isn't loopback).
|
|
# On the host the daemon has native /dev, arduino-cli + the Zephyr toolchain, and
|
|
# loopback /admin/reload — everything the workshop needs.
|
|
#
|
|
# Usage (board on USB, cloud token in env):
|
|
# export ANTHROPIC_OAUTH_TOKEN=sk-ant-oat01-…
|
|
# ./deploy/uno-q/provision-host-daemon.sh
|
|
#
|
|
# Env: SERIAL (65301572), BOARD_PW (sudo password), ZEROCLAW_BIN (built aarch64
|
|
# binary). Assumes the base board provision already populated ~/.zeroclaw
|
|
# (config.toml, .secret_key, agents, shared/skills) — see provision-uno-q.sh.
|
|
set -u
|
|
SERIAL="${SERIAL:-65301572}"
|
|
BOARD_PW="${BOARD_PW:-clouddev249}"
|
|
ZEROCLAW_BIN="${ZEROCLAW_BIN:-$HOME/projects/zeroclaw/target/aarch64-unknown-linux-gnu/release-fast/zeroclaw}"
|
|
UNIT_DIR="$(cd "$(dirname "$0")/systemd" && pwd)"
|
|
S(){ adb -s "$SERIAL" shell "$@"; }
|
|
# run a command as root on the board (sudo -S reads the password from stdin)
|
|
SU(){ adb -s "$SERIAL" shell "echo '$BOARD_PW' | sudo -S sh -c '$1'" 2>&1 | grep -iv 'password for'; }
|
|
ok(){ printf ' \033[32m✓\033[0m %s\n' "$*"; }
|
|
bad(){ printf ' \033[31m✗\033[0m %s\n' "$*"; }
|
|
|
|
adb -s "$SERIAL" get-state >/dev/null 2>&1 || { bad "board $SERIAL not attached"; exit 1; }
|
|
[ -f "$ZEROCLAW_BIN" ] || { bad "binary not found: $ZEROCLAW_BIN (build it first)"; exit 1; }
|
|
[ -n "${ANTHROPIC_OAUTH_TOKEN:-}" ] || { bad "ANTHROPIC_OAUTH_TOKEN not set (cloud brain)"; exit 1; }
|
|
|
|
echo "→ retire the App Lab container model (it can't reach the hardware)"
|
|
S "cd /home/arduino/ArduinoApps/zeroclaw-node/.cache 2>/dev/null && docker compose -f app-compose.yaml down 2>/dev/null; arduino-app-cli properties set default none 2>/dev/null" >/dev/null 2>&1
|
|
ok "App Lab container down + default app cleared (won't grab :8080 on boot)"
|
|
|
|
echo "→ deploy the host binary"
|
|
adb -s "$SERIAL" push "$ZEROCLAW_BIN" /tmp/zeroclaw.new >/dev/null
|
|
SU "install -m755 -o arduino -g arduino /tmp/zeroclaw.new /home/arduino/zeroclaw; rm -f /tmp/zeroclaw.new"
|
|
ok "binary → /home/arduino/zeroclaw"
|
|
|
|
echo "→ cloud credential env-file (raw token AND the config-override that wires api_key)"
|
|
printf '%s' "$ANTHROPIC_OAUTH_TOKEN" | S "cat > /tmp/oat"
|
|
S "T=\$(cat /tmp/oat); { printf 'ANTHROPIC_OAUTH_TOKEN=%s\n' \"\$T\"; printf 'ZEROCLAW_providers__models__anthropic__max__api_key=%s\n' \"\$T\"; } > /home/arduino/.zeroclaw/daemon.env; chmod 600 /home/arduino/.zeroclaw/daemon.env; rm -f /tmp/oat"
|
|
ok "/home/arduino/.zeroclaw/daemon.env (0600, off the repo)"
|
|
|
|
echo "→ Arduino flashing prerequisite (the Zephyr core hard-requires this library)"
|
|
S "HOME=/home/arduino arduino-cli lib install Arduino_RouterBridge 2>&1 | tail -1"
|
|
ok "Arduino_RouterBridge installed"
|
|
|
|
echo "→ flash the resident matrix responder (enables instant matrix_text / matrix_pattern)"
|
|
# The responder sketch provides matrix_set/matrix_text over RouterBridge, so the
|
|
# agent can drive the matrix in <1s instead of compiling+flashing (~95s) — and
|
|
# without overwriting the MCU each time. One-time flash here.
|
|
RESPONDER_SKETCH="${RESPONDER_SKETCH:-$HOME/projects/zeroclaw/firmware/zeroclaw-node/sketch/sketch.ino}"
|
|
if [ -f "$RESPONDER_SKETCH" ]; then
|
|
S "rm -rf /tmp/responder && mkdir -p /tmp/responder/responder"
|
|
adb -s "$SERIAL" push "$RESPONDER_SKETCH" /tmp/responder/responder/responder.ino >/dev/null
|
|
S "HOME=/home/arduino arduino-cli compile --upload -b arduino:zephyr:unoq /tmp/responder/responder >/dev/null 2>&1 && echo flashed || echo 'responder flash failed'" | grep -q flashed \
|
|
&& ok "matrix responder flashed to the MCU" || bad "responder flash failed (matrix tools will need it)"
|
|
else
|
|
bad "responder sketch not found ($RESPONDER_SKETCH) — skipping (set RESPONDER_SKETCH)"
|
|
fi
|
|
|
|
echo "→ start the matrix relay container (:9999 → RouterBridge; the RouterBridge"
|
|
echo " python binding only ships in the App Lab image, so run JUST the relay there)"
|
|
S "mkdir -p /home/arduino/matrix-relay"
|
|
adb -s "$SERIAL" push "$(dirname "$0")/matrix-relay/relay.py" /home/arduino/matrix-relay/relay.py >/dev/null
|
|
S "docker rm -f matrix-relay >/dev/null 2>&1; docker run -d --name matrix-relay --restart unless-stopped -v /run/arduino-router.sock:/var/run/arduino-router.sock -v /home/arduino/matrix-relay/relay.py:/relay.py:ro -p 127.0.0.1:9999:9999 --entrypoint python3 ghcr.io/arduino/app-bricks/python-apps-base:0.11.0 /relay.py >/dev/null 2>&1 && echo up"
|
|
ok "matrix relay running on :9999"
|
|
|
|
echo "→ hardware device perms — udev rule so the agent can read I2C/SPI sensors"
|
|
SU "printf 'KERNEL==\"i2c-[0-9]*\", MODE=\"0666\"\nKERNEL==\"spidev[0-9]*\", MODE=\"0666\"\n' > /etc/udev/rules.d/99-apess-hw.rules; udevadm control --reload; udevadm trigger --subsystem-match=i2c-dev >/dev/null 2>&1"
|
|
ok "i2c/spi readable by the daemon user"
|
|
|
|
echo "→ install + enable the systemd service (boot-persistent)"
|
|
adb -s "$SERIAL" push "$UNIT_DIR/zeroclaw-daemon.service" /tmp/zeroclaw-daemon.service >/dev/null
|
|
SU "install -m644 /tmp/zeroclaw-daemon.service /etc/systemd/system/zeroclaw-daemon.service; rm -f /tmp/zeroclaw-daemon.service; systemctl daemon-reload; systemctl enable --now zeroclaw-daemon.service"
|
|
ok "zeroclaw-daemon.service enabled + started"
|
|
|
|
echo "→ verify the gateway comes up"
|
|
H=""
|
|
for _ in 1 2 3 4 5 6 7 8 9 10; do
|
|
sleep 3
|
|
H=$(S "curl -s -m3 http://127.0.0.1:8080/health -o /dev/null -w '%{http_code}' 2>/dev/null")
|
|
[ "$H" = "200" ] && break
|
|
done
|
|
if [ "$H" = "200" ]; then
|
|
ok "gateway healthy on :8080"
|
|
echo "Done — node runs the ZeroClaw daemon on the host with full hardware access."
|
|
else
|
|
bad "gateway did not come up; inspect: adb -s $SERIAL shell 'sudo journalctl -u zeroclaw-daemon -n50'"
|
|
exit 1
|
|
fi
|