Vendors the comprehensive UNO Q skill (SKILL.md + 7 references/*.md) and installs it into EVERY agent's workspace on each board, so agents know this board's specifics (dual-brain arch, Bridge/RPC, pin tables, LED matrix + the ArduinoGraphics-not-installed gotcha) instead of guessing generic Arduino. Why per-agent workspace: ZeroClaw's read_skill returns only SKILL.md; the agent reads references/*.md via the workspace-sandboxed file_read tool, so references are only reachable under ~/.zeroclaw/agents/<alias>/workspace/skills/. A shared/skills bundle surfaces the skill but its references get sandbox-blocked. - push-skill.sh installs a SKILL.md+references skill into every agent workspace (discovers aliases from the board); provision-fleet runs it per board. - config.template risk profile now allows + auto-approves read_skill + file_read so agents load skills without a human approver (webhook path is non-interactive). - Flattened the folded 'description: >-' to single-line (ZeroClaw's frontmatter parser is a flat scanner, not full YAML). Verified on board 65301572 with cloud/Sonnet-5: discovered arduino-uno-q → read_skill(SKILL.md) → file_read references/04-bridge-rpc.md → correct board-specific answer citing the file. Co-Authored-By: Claude Opus 4.8 <[email protected]>
117 lines
5.8 KiB
Bash
Executable File
117 lines
5.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Fleet onboarding provisioner. For each board in an assignments file, pushes
|
|
# its per-kit env (apess-node.env) + the self-register script over adb, enables
|
|
# the self-register systemd timer (root boards) or an equivalent cron (no-root,
|
|
# the default), and flashes the default boot animation onto the MCU. Run after
|
|
# gen-kit-codes.sh.
|
|
#
|
|
# ./provision-fleet.sh <assignments.csv> [kit-codes-dir]
|
|
#
|
|
# assignments.csv — map each kit to its board's adb serial (`adb devices`):
|
|
# kit,serial
|
|
# KIT-01,65301572
|
|
# KIT-02,88a1b2c3
|
|
# kit-codes-dir default: ./kit-codes (expects env/KIT-NN.env from gen-kit-codes.sh)
|
|
#
|
|
# Env:
|
|
# MODE=cron (default, no root) | systemd (needs a root-capable board)
|
|
# FLASH_DEFAULT=1 (default; 0 skips flashing the boot animation)
|
|
# FLASH_SKETCH=<dir> (default: sketches/matrix_rain)
|
|
set -uo pipefail
|
|
|
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
ASSIGN="${1:?usage: provision-fleet.sh <assignments.csv> [kit-codes-dir]}"
|
|
KCDIR="${2:-./kit-codes}"
|
|
ENVDIR="$KCDIR/env"
|
|
MODE="${MODE:-cron}"
|
|
FLASH_DEFAULT="${FLASH_DEFAULT:-1}"
|
|
FLASH_SKETCH="${FLASH_SKETCH:-$HERE/sketches/matrix_rain}"
|
|
|
|
[ -r "$ASSIGN" ] || { echo "no assignments file: $ASSIGN" >&2; exit 1; }
|
|
[ -d "$ENVDIR" ] || { echo "no env dir: $ENVDIR (run gen-kit-codes.sh first)" >&2; exit 1; }
|
|
[ -r "$HERE/apess-selfregister.sh" ] || { echo "missing apess-selfregister.sh next to this script" >&2; exit 1; }
|
|
if [ "$FLASH_DEFAULT" = 1 ]; then
|
|
[ -x "$HERE/flash-sketch.sh" ] || { echo "FLASH_DEFAULT=1 but flash-sketch.sh is missing/not executable (FLASH_DEFAULT=0 to skip)" >&2; exit 1; }
|
|
[ -d "$FLASH_SKETCH" ] || { echo "FLASH_DEFAULT=1 but sketch dir not found: $FLASH_SKETCH" >&2; exit 1; }
|
|
fi
|
|
|
|
provision() { # kit serial -> 0 ok / 1 fail
|
|
local kit="$1" serial="$2" env="$ENVDIR/$1.env"
|
|
[ -r "$env" ] || { echo " ! no env file for $kit ($env)"; return 1; }
|
|
adb -s "$serial" shell 'mkdir -p /home/arduino/.zeroclaw' >/dev/null 2>&1 || return 1
|
|
adb -s "$serial" push "$env" /home/arduino/.zeroclaw/apess-node.env >/dev/null 2>&1 || return 1
|
|
adb -s "$serial" push "$HERE/apess-selfregister.sh" /home/arduino/ >/dev/null 2>&1 || return 1
|
|
adb -s "$serial" shell 'chmod +x /home/arduino/apess-selfregister.sh' >/dev/null 2>&1 || return 1
|
|
|
|
# persistence: systemd timer (root) or cron (no root)
|
|
local persisted=""
|
|
if [ "$MODE" = systemd ]; then
|
|
adb -s "$serial" push "$HERE/systemd/apess-selfregister.service" /tmp/ >/dev/null 2>&1
|
|
adb -s "$serial" push "$HERE/systemd/apess-selfregister.timer" /tmp/ >/dev/null 2>&1
|
|
if adb -s "$serial" shell 'sudo cp /tmp/apess-selfregister.service /tmp/apess-selfregister.timer /etc/systemd/system/ \
|
|
&& sudo systemctl enable --now apess-selfregister.timer' >/dev/null 2>&1; then
|
|
persisted="systemd timer"
|
|
else
|
|
echo " ! systemd enable failed (no root?) — falling back to cron"
|
|
fi
|
|
fi
|
|
if [ -z "$persisted" ]; then
|
|
adb -s "$serial" shell 'setsid /home/arduino/apess-selfregister.sh >/tmp/selfreg.log 2>&1 </dev/null &' >/dev/null 2>&1 || true
|
|
adb -s "$serial" shell '(crontab -l 2>/dev/null | grep -v apess-selfregister.sh; \
|
|
echo "@reboot /home/arduino/apess-selfregister.sh"; \
|
|
echo "*/5 * * * * /home/arduino/apess-selfregister.sh") | crontab -' >/dev/null 2>&1 || true
|
|
persisted="cron: @reboot + every 5 min"
|
|
fi
|
|
echo " ok — onboarding ($persisted)"
|
|
|
|
# modalities — reload-watcher (applies browser dashboard config edits, e.g. a
|
|
# team enabling its Telegram bot) + the lockdown script. Best-effort.
|
|
if [ -r "$HERE/zeroclaw-reload-watcher.sh" ]; then
|
|
adb -s "$serial" push "$HERE/zeroclaw-reload-watcher.sh" /home/arduino/ >/dev/null 2>&1
|
|
adb -s "$serial" push "$HERE/zeroclaw-lockdown.sh" /home/arduino/ >/dev/null 2>&1
|
|
adb -s "$serial" shell 'chmod +x /home/arduino/zeroclaw-reload-watcher.sh /home/arduino/zeroclaw-lockdown.sh' >/dev/null 2>&1
|
|
adb -s "$serial" shell 'pgrep -f "[z]eroclaw-reload-watcher.sh" >/dev/null 2>&1 || \
|
|
setsid nohup /home/arduino/zeroclaw-reload-watcher.sh >/tmp/zc-reload.log 2>&1 </dev/null &' >/dev/null 2>&1
|
|
adb -s "$serial" shell '(crontab -l 2>/dev/null | grep -v zeroclaw-reload-watcher.sh; \
|
|
echo "@reboot /home/arduino/zeroclaw-reload-watcher.sh") | crontab -' >/dev/null 2>&1 || true
|
|
echo " ok — modalities (reload-watcher up; lockdown staged)"
|
|
fi
|
|
|
|
# default skill — install the arduino-uno-q skill (SKILL.md + references) into
|
|
# every agent's workspace so each node has it by default. Best-effort.
|
|
if [ -x "$HERE/push-skill.sh" ] && [ -r "$HERE/skills/arduino-uno-q/SKILL.md" ]; then
|
|
if "$HERE/push-skill.sh" "$serial" "$HERE/skills/arduino-uno-q" >/dev/null 2>&1; then
|
|
echo " ok — skill (arduino-uno-q → agent workspaces)"
|
|
else
|
|
echo " ! skill install failed (onboarding still ok)"
|
|
fi
|
|
fi
|
|
|
|
# default boot animation — best-effort (a flash hiccup doesn't fail onboarding)
|
|
if [ "$FLASH_DEFAULT" = 1 ]; then
|
|
if "$HERE/flash-sketch.sh" "$serial" "$FLASH_SKETCH" >/dev/null 2>&1; then
|
|
echo " ok — boot animation ($(basename "$FLASH_SKETCH"))"
|
|
else
|
|
echo " ! boot-animation flash failed (onboarding still ok)"
|
|
fi
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
ok=0 fail=0
|
|
while IFS=',' read -r kit serial _rest || [ -n "$kit" ]; do
|
|
kit="$(printf '%s' "$kit" | tr -d ' \r')"
|
|
serial="$(printf '%s' "$serial" | tr -d ' \r')"
|
|
[ -z "$kit" ] && continue
|
|
[ "$kit" = "kit" ] && continue # header
|
|
case "$kit" in \#*) continue ;; esac # comment
|
|
echo "==> $kit ($serial)"
|
|
if [ -z "$serial" ]; then echo " ! no serial — skipping"; fail=$((fail + 1)); continue; fi
|
|
if provision "$kit" "$serial"; then ok=$((ok + 1)); else echo " FAILED"; fail=$((fail + 1)); fi
|
|
done < "$ASSIGN"
|
|
|
|
anim="skipped"; [ "$FLASH_DEFAULT" = 1 ] && anim="$(basename "$FLASH_SKETCH")"
|
|
echo "----"
|
|
echo "provisioned $ok board(s), $fail failed/skipped (mode: $MODE, boot-animation: $anim)"
|
|
[ "$fail" -eq 0 ]
|