2 Commits
Author SHA1 Message Date
Logan CusanoandClaude Sonnet 5 4f942bd770 install.sh: re-run collects the key via pickup_secret, never re-enrolls
CI / lint (push) Successful in 12s
CI / test (push) Successful in 51s
CI / test (pull_request) Failing after 11m12s
CI / lint (pull_request) Failing after 11m21s
Confirmed prod bug: §5 keyed idempotency on configs/credentials.json only.
Re-running the installer on a pending-then-approved node (the exact flow the
script's own output tells you to do) found no credentials.json and fell
through to a fresh POST /nodes/enroll — which enrollment.py's CRITICAL GUARD
answers with 403 for an already-approved node_id, rendered as "use Reissue
key". The working path (GET /nodes/{id}/credentials with the saved
pickup_secret — no already-approved guard on that endpoint) was only ever
tried within a single run.

§5 rewritten as a decision tree that runs before any POST /nodes/enroll:
  - credentials.json has api_key            -> skip (unchanged)
  - configs/pickup_secret exists            -> GET /credentials with it:
      200 + api_key   -> write credentials.json, done
      200, no key     -> say "approve it, re-run"; clean exit, start stack
      401 (rotated)   -> re-enroll iff --token, else specific die
      404 (deleted)   -> re-enroll iff --token, else specific die
      000             -> connectivity die
  - no creds, no pickup_secret              -> fresh enroll (do_fresh_enroll)

Also: fresh-enroll 403/401/429 handlers are now specific and point at
pickup-secret recovery, not just "Reissue key"; --wait-approval polling now
applies on the re-run path; §7's "re-run the installer" banner is
conditional on pickup_secret existing.

Response shapes verified against enrollment.py @ v1. approve_node mints
node_keys/{id}.api_key synchronously — no second bug; assigning a system is
independent and not required for a key. bash -n clean.

Recovery for a node stuck by the old behaviour (approved, no credentials.json,
pickup_secret on disk): re-run the patched install.sh (no --token needed), or
  curl -fsS $C2_URL/nodes/$NODE_ID/credentials \
    -H "X-Pickup-Secret: $(cat configs/pickup_secret)" | jq '{api_key}' > configs/credentials.json

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 21:37:14 -04:00
Logan CusanoandClaude Sonnet 5 5b0048e8db node: one-shot install.sh for a fresh Pi; retire setup.sh (node-26#4)
CI / lint (pull_request) Successful in 10s
CI / lint (push) Successful in 11s
CI / test (pull_request) Successful in 46s
CI / test (push) Successful in 46s
install.sh is the `curl -fsSL <url> | sudo bash -s -- --token ...` bootstrap:
preflight (root/arch/apt/SDR) → install docker + compose + git + jq → clone
node-26 at a pinned ref (default `v1`, `--track-main` opt-in) → write .env
non-interactively from flags/env with a /dev/tty interactive fallback →
enroll with C2 (POST /nodes/enroll, poll GET /nodes/{id}/credentials, matches
drb-c2-core/app/routers/enrollment.py exactly) and write configs/credentials.json
→ docker compose pull && up -d (prebuilt; --build opts into the ~1h op25 build)
→ print the admin-approval step. Idempotent: re-run picks up the api_key after
approval; existing .env is preserved.

- setup.sh deleted — two scripts writing .env drift. install.sh owns it now.
- Makefile `setup:` no longer calls the removed script (cp .env.example fallback).
- README Setup section rewritten around the one-liner; `make setup`/`make up`
  kept as the local-dev path.

Notes carried in the script header: git.vpn.cusano.net is public (D1, settled);
`v1` must be re-cut at this change's merge commit so the tag actually contains
install.sh. Standing hazard D3: docker-compose.yml bind-mounts the app source
over the image, so a pinned ref and the pulled image tags must not diverge.

Client-side enrollment still belongs in the edge-node app (mqtt_manager.py:73-85);
install.sh doing it is the interim. Tracked for follow-up.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 19:14:40 -04:00