feat(uno-q): board self-register on boot + QR claim prefill (onboarding slice 3)
Board-side half of "preloaded + self-register + claim", plus the QR flow.
- apess-selfregister.sh (on-board): pairs locally for a token, discovers
the LAN IP, and announces {kitId, url, token, claimCode} to APESS
/nodes/self-register (x-fleet-secret gated). Retries until APESS is up;
idempotent, safe on boot and on a timer.
- systemd/apess-selfregister.{service,timer}: self-register After the
daemon, re-announce every 5 min so a DHCP lease change can't strand a
board.
- apess-node.env.example: per-board identity (KIT_ID, CLAIM_CODE,
FLEET_SECRET, APESS_URL).
- gen-kit-codes.sh (host): mint per-kit 6-digit codes, write the
per-board env files, and emit the QR sticker CSV
(…/workshop?kit=KIT-NN&code=NNNNNN).
- web: BoardClaim accepts initialCode; TeamRegistration pre-fills it from
?code= so scanning the kit QR fills kit + code — one tap to claim.
- deploy/uno-q/README: documents the whole self-serve onboarding path.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
0442f7e865
commit
3f6c6ab399
Executable
+66
@@ -0,0 +1,66 @@
|
||||
#!/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).
|
||||
CODE=$("$ZEROCLAW" gateway get-paircode --new --port "$PORT" | grep -oE '[0-9]{6}' | head -1)
|
||||
TOKEN=$(curl -s -X POST "$GW/pair" -H "X-Pairing-Code: ${CODE}" \
|
||||
| python3 -c 'import sys,json;print(json.load(sys.stdin).get("token",""))')
|
||||
[ -n "$TOKEN" ] || { echo "local pairing failed" >&2; exit 1; }
|
||||
|
||||
# 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
|
||||
Reference in New Issue
Block a user