install.sh: re-run collects the key via pickup_secret, never re-enrolls #7

Merged
logan merged 1 commits from fix/install-rerun-pickup into main 2026-09-06 22:32:19 -04:00
Owner

Confirmed production bug. install.sh v1 §5 keyed idempotency on configs/credentials.json only. Re-running on a pending-then-approved node (which the script's own output tells you to do) found no credentials.json, fell through to a fresh POST /nodes/enroll, and hit enrollment.py's already-approved 403 → rendered as "use Reissue key". The working path — GET /nodes/{id}/credentialswith the savedpickup_secret` — was only tried within a single run.

§5 is now a decision tree run before any POST /nodes/enroll:

state action
credentials.json has api_key skip (unchanged)
configs/pickup_secret exists GET /credentials: 200+key → write & done; 200 no key → "approve it, re-run", clean exit; 401 → re-enroll iff --token else die; 404 → same; 000 → connectivity die
neither fresh enroll (do_fresh_enroll)

Also: fresh-enroll 403/401/429 handlers are specific and point at pickup-secret recovery; --wait-approval polling now applies on re-run; §7 banner conditional on pickup_secret.

Verified against enrollment.py @ v1. approve_node mints node_keys/{id}.api_key synchronously — no second bug found; system assignment is independent and not required for a key. bash -n clean; not run on a Pi from this env.

After merge: move v1 to this commit (git tag -f v1 <sha> && git push -f origin v1) — DEFAULT_REF and the mint-panel URL both point at v1.

Recovery for a node already stuck (approved, no credentials.json, pickup_secret on disk): re-run the patched install.sh with no --token, or the curl … /credentials -H X-Pickup-Secret one-liner.

🤖 Generated with Claude Code

**Confirmed production bug.** `install.sh` v1 §5 keyed idempotency on `configs/credentials.json` only. Re-running on a pending-then-approved node (which the script`'s own output tells you to do) found no `credentials.json`, fell through to a fresh `POST /nodes/enroll`, and hit `enrollment.py`'s already-approved 403 → rendered as "use Reissue key". The working path — `GET /nodes/{id}/credentials` with the saved `pickup_secret` — was only tried within a single run. §5 is now a decision tree run **before** any `POST /nodes/enroll`: | state | action | |---|---| | `credentials.json` has `api_key` | skip (unchanged) | | `configs/pickup_secret` exists | `GET /credentials`: 200+key → write & done; 200 no key → "approve it, re-run", clean exit; 401 → re-enroll iff `--token` else die; 404 → same; 000 → connectivity die | | neither | fresh enroll (`do_fresh_enroll`) | Also: fresh-enroll 403/401/429 handlers are specific and point at pickup-secret recovery; `--wait-approval` polling now applies on re-run; §7 banner conditional on `pickup_secret`. Verified against `enrollment.py` @ `v1`. `approve_node` mints `node_keys/{id}.api_key` synchronously — no second bug found; system assignment is independent and not required for a key. `bash -n` clean; not run on a Pi from this env. **After merge:** move `v1` to this commit (`git tag -f v1 <sha> && git push -f origin v1`) — `DEFAULT_REF` and the mint-panel URL both point at `v1`. **Recovery for a node already stuck** (approved, no `credentials.json`, `pickup_secret` on disk): re-run the patched `install.sh` with no `--token`, or the `curl … /credentials -H X-Pickup-Secret` one-liner. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
logan added 1 commit 2026-09-06 21:37:18 -04:00
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
4f942bd770
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>
logan merged commit 31e1176c45 into main 2026-09-06 22:32:19 -04:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: logan/node-26#7