Codify the verified board setup into a repeatable kit under deploy/uno-q/:
- config.template.toml — the tested node config: cloud / cloud+fallback /
on-board-Qwen providers, three agents (default / cloud / local) on the
hardware risk profile, gateway, peripherals. Secrets stripped; cloud
endpoint substituted at provision time.
- provision-uno-q.sh — one-shot adb-driven provisioner (no root): install
config, start llama-server + `zeroclaw daemon`, pair for a bearer token,
and POST /nodes to APESS.
- systemd/{zeroclaw-llama,zeroclaw-daemon}.service — production units
(daemon, not `gateway start`, so peripheral tools register).
- README.md — dev (adb) and production (systemd + LAN bind) runbooks.
The individual steps are hardware-verified: all three agent aliases resolve
and route correctly on the board (local→Qwen, cloud→cloud, default→cloud
with Qwen fallback). Full script orchestration + live phone-home land once
the api (with /nodes) is redeployed.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
54 lines
2.8 KiB
Bash
Executable File
54 lines
2.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Provision an Arduino Uno Q as an APESS workshop node — host-driven via adb,
|
|
# no root needed on the board. Installs config, brings up the on-board Qwen and
|
|
# the ZeroClaw daemon, pairs for a bearer token, and registers with APESS.
|
|
#
|
|
# APESS_ADMIN_CODE=adm-xxxx ./provision-uno-q.sh <adb-serial> <team-id> [apess-url]
|
|
#
|
|
# Env overrides: CLOUD_URI, CLOUD_MODEL (default: local shim :8090 / sonnet).
|
|
set -euo pipefail
|
|
|
|
SERIAL="${1:?usage: provision-uno-q.sh <adb-serial> <team-id> [apess-url]}"
|
|
TEAM="${2:?team id required}"
|
|
APESS="${3:-https://apess-api.redclaw.dev}"
|
|
CLOUD_URI="${CLOUD_URI:-http://127.0.0.1:8090/v1}"
|
|
CLOUD_MODEL="${CLOUD_MODEL:-sonnet}"
|
|
ADMIN_CODE="${APESS_ADMIN_CODE:?set APESS_ADMIN_CODE to register with APESS}"
|
|
|
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
a() { adb -s "$SERIAL" "$@"; }
|
|
|
|
echo "==> [1/5] install config (cloud=$CLOUD_URI)"
|
|
sed -e "s#__CLOUD_URI__#${CLOUD_URI}#g" -e "s#__CLOUD_MODEL__#${CLOUD_MODEL}#g" \
|
|
"$HERE/config.template.toml" > /tmp/apess-node-config.toml
|
|
a shell 'mkdir -p /home/arduino/.zeroclaw'
|
|
a push /tmp/apess-node-config.toml /home/arduino/.zeroclaw/config.toml >/dev/null
|
|
|
|
echo "==> [2/5] start on-board Qwen (llama-server :8083)"
|
|
a shell 'pgrep -f llama-server >/dev/null 2>&1 || (cd /home/arduino/llama && \
|
|
LD_LIBRARY_PATH=/home/arduino/llama nohup ./llama-server -m /home/arduino/models/qwen.gguf \
|
|
--host 127.0.0.1 --port 8083 -np 1 -c 16384 --jinja --mlock >/tmp/llama8083.log 2>&1 &)'
|
|
|
|
echo "==> [3/5] start zeroclaw daemon (:8080)"
|
|
a shell 'pkill -f "zeroclaw daemon" 2>/dev/null; pkill -f "zeroclaw gateway" 2>/dev/null; sleep 2; \
|
|
nohup /home/arduino/zeroclaw daemon >/tmp/zc-daemon.log 2>&1 &'
|
|
sleep 8
|
|
|
|
echo "==> [4/5] pair → bearer token"
|
|
a forward tcp:8080 tcp:8080 >/dev/null
|
|
until curl -sf --max-time 3 http://127.0.0.1:8080/health >/dev/null; do sleep 1; done
|
|
CODE=$(a shell '/home/arduino/zeroclaw gateway get-paircode --new --port 8080' | grep -oE '[0-9]{6}' | head -1)
|
|
TOKEN=$(curl -s -X POST http://127.0.0.1:8080/pair -H "X-Pairing-Code: ${CODE}" \
|
|
| python3 -c 'import sys,json;print(json.load(sys.stdin)["token"])')
|
|
[ -n "$TOKEN" ] || { echo "pairing failed" >&2; exit 1; }
|
|
|
|
echo "==> [5/5] register team '$TEAM' with APESS"
|
|
# Board's LAN IP — what participants reach in a real fleet (falls back to localhost).
|
|
IP=$(a shell "ip -4 -o addr show 2>/dev/null | grep -oE 'inet [0-9.]+' | grep -v '127.0.0.1' | awk '{print \$2}' | head -1" | tr -d '\r')
|
|
URL="http://${IP:-127.0.0.1}:8080"
|
|
curl -sf -X POST "${APESS}/nodes" -H "x-access-code: ${ADMIN_CODE}" -H 'content-type: application/json' \
|
|
-d "{\"teamId\":\"${TEAM}\",\"url\":\"${URL}\",\"token\":\"${TOKEN}\"}" >/dev/null \
|
|
&& echo " registered at ${URL}" || echo " WARN: APESS registration failed (is ${APESS} reachable?)"
|
|
|
|
echo "==> done — node '$TEAM' is live. Agents: default (cloud+fallback), cloud, local."
|