Files
apress/deploy/uno-q/apess-selfregister.sh
T
Omar SobhandClaude Opus 4.8 7233a4b1c0 feat(uno-q): LAN-open boot + Telegram self-serve + guided lockdown modalities
Boards now boot LAN-open (host 0.0.0.0, allow_public_bind, require_pairing=false)
so a team reaches its node's embedded ZeroClaw dashboard + web chat directly by
IP with no token — the 'Open your node' path. Two more modalities ride the same
default (cloud-first) agent:

- Telegram: config seeds [channels.telegram.default] disabled + pre-bound to the
  default agent; a team enables it from the dashboard by pasting a @BotFather
  token. Dashboard writes only set pending_reload, and /admin/reload is
  loopback-only, so a new zeroclaw-reload-watcher.sh (loopback) applies dashboard
  config edits within seconds — no shell. provision-fleet pushes + launches it.
- Lockdown: zeroclaw-lockdown.sh flips require_pairing=true, reloads, and mints a
  pair code (open -> locked harden step); documented as a board/adb action since
  minting + reload are loopback-only.
- Voice: seeds a commented [channels.voice_duplex.default] + documents the
  aarch64 build (embedded-web + gateway-voice-duplex); build/hardware work is
  separate.

Onboarding scripts (apess-selfregister.sh, provision-uno-q.sh) now tolerate
open-boot: when the gateway mints no pair code, they announce with a placeholder
token (the open board ignores auth) instead of hard-failing.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-15 16:35:28 -07:00

73 lines
2.9 KiB
Bash
Executable File

#!/usr/bin/env bash
# Runs ON the Uno Q. Announces this board into APESS's unclaimed pool so an
# attendee can claim it (kit + code) with no operator in the loop — the
# board-side half of the "preloaded + self-register + claim" onboarding.
#
# It pairs locally for a bearer token, discovers the board's LAN IP, and POSTs
# {kitId, url, token, claimCode} to APESS /nodes/self-register (authorized by
# the shared fleet secret, NOT the admin code). Idempotent + retrying: safe to
# run on every boot and on a timer (re-announces if the DHCP lease changes).
#
# Config: /home/arduino/.zeroclaw/apess-node.env (see apess-node.env.example) —
# KIT_ID, CLAIM_CODE, FLEET_SECRET, APESS_URL [, GATEWAY_PORT]
set -euo pipefail
ENV_FILE="${APESS_NODE_ENV:-/home/arduino/.zeroclaw/apess-node.env}"
ZEROCLAW="${ZEROCLAW_BIN:-/home/arduino/zeroclaw}"
[ -r "$ENV_FILE" ] || { echo "missing $ENV_FILE" >&2; exit 1; }
# shellcheck disable=SC1090
. "$ENV_FILE"
: "${KIT_ID:?KIT_ID required}"
: "${CLAIM_CODE:?CLAIM_CODE required}"
: "${FLEET_SECRET:?FLEET_SECRET required}"
: "${APESS_URL:?APESS_URL required}"
PORT="${GATEWAY_PORT:-8080}"
GW="http://127.0.0.1:${PORT}"
log() { echo "[selfregister] $*"; }
# Wait for the local daemon to be up (systemd orders us After=, but be safe).
for _ in $(seq 1 60); do
curl -sf --max-time 3 "$GW/health" >/dev/null 2>&1 && break
sleep 2
done
# Pair locally for a bearer token (the token stays server-side once APESS has it).
# Under LAN-open setup (require_pairing=false) the gateway mints no code — the
# open board ignores auth, so announce with a placeholder token; lockdown later
# establishes real pairing (and, if wanted, re-announces a valid token).
CODE=$("$ZEROCLAW" gateway get-paircode --new --port "$PORT" 2>/dev/null | grep -oE '[0-9]{6}' | head -1 || true)
TOKEN=""
if [ -n "$CODE" ]; then
TOKEN=$(curl -s -X POST "$GW/pair" -H "X-Pairing-Code: ${CODE}" \
| python3 -c 'import sys,json;print(json.load(sys.stdin).get("token",""))' 2>/dev/null || true)
fi
TOKEN="${TOKEN:-open-lan}" # placeholder while the board is LAN-open
# The LAN IP participants (and APESS) reach; fall back to localhost for adb-only.
IP=$(ip -4 -o addr show 2>/dev/null | grep -oE 'inet [0-9.]+' | grep -v '127.0.0.1' \
| awk '{print $2}' | head -1)
URL="http://${IP:-127.0.0.1}:${PORT}"
payload=$(python3 - "$KIT_ID" "$URL" "$TOKEN" "$CLAIM_CODE" <<'PY'
import json, sys
kit, url, token, code = sys.argv[1:5]
print(json.dumps({"kitId": kit, "url": url, "token": token, "claimCode": code}))
PY
)
# Announce, retrying until APESS is reachable (it may boot after the boards).
for attempt in $(seq 1 30); do
if curl -sf -X POST "${APESS_URL%/}/nodes/self-register" \
-H "x-fleet-secret: ${FLEET_SECRET}" -H 'content-type: application/json' \
-d "$payload" >/dev/null; then
log "announced ${KIT_ID} at ${URL}"
exit 0
fi
log "APESS unreachable (attempt ${attempt}/30) — retrying"
sleep 10
done
echo "could not reach APESS at ${APESS_URL}" >&2
exit 1