feat(rmm): add /rmm onboard — client+site provisioning + vault enrollment-key flow
Documents the full GuruRMM onboarding process (POST /api/clients, POST /api/sites with one-time api_key capture), the vault storage step, and the sops-encryption gotchas hit while onboarding Rednour Law Offices (--config required, quote dates, secrets under credentials:). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -23,6 +23,7 @@ Interact with the GuruRMM agent fleet: list agents, run remote commands (PowerSh
|
||||
/rmm output <command_id> Fetch full stdout + stderr for a completed command
|
||||
/rmm cancel <command_id> Cancel a pending or running command
|
||||
/rmm history <hostname|uuid> [N] Recent command history (default 10, max 500)
|
||||
/rmm onboard <client name> [site] Create a new client + site, vault the one-time enrollment key
|
||||
```
|
||||
|
||||
---
|
||||
@@ -620,6 +621,64 @@ ASCII only — no Unicode dashes or arrows. Use `-` and `->`.
|
||||
|
||||
---
|
||||
|
||||
## Client / Site onboarding (`/rmm onboard`)
|
||||
|
||||
Provision a new client and its first site, then store the agent enrollment key in the vault. The site `api_key` is **shown only once** in the create response — capture it before anything else.
|
||||
|
||||
**Verified API shapes (from `server/src/api/clients.rs` + `sites.rs`):**
|
||||
- `POST /api/clients` body `{"name": "...", "code"?: "...", "notes"?: "..."}` → `{"id", "name", "code", "is_active", "site_count", ...}`. `partner_id` is assigned server-side (default partner) — do NOT send it.
|
||||
- `POST /api/sites` body `{"client_id": "<uuid>", "name": "...", "address"?: "...", "notes"?: "..."}` → `{"site": {"id", "site_code", ...}, "api_key": "grmm_...", "message"}`. **`site_code` is server-generated** (`generate_unique_site_code`, e.g. `GREEN-FALCON-7214`) — never supply it.
|
||||
- If the key is lost: `POST /api/sites/:id/regenerate-key` issues a new one (invalidates the old).
|
||||
|
||||
**Workflow:**
|
||||
```bash
|
||||
# (Phase 0 bootstrap done → $TOKEN, $RMM, $REPO_ROOT)
|
||||
NAME="Rednour Law Offices"; SITE="Main"
|
||||
SLUG="rednour" # lowercase, no spaces/hyphens — matches existing vault convention
|
||||
|
||||
# 1. Guard against a duplicate client
|
||||
curl -s "$RMM/api/clients" -H "Authorization: Bearer $TOKEN" \
|
||||
| jq -r --arg n "$NAME" '.[] | select(.name|ascii_downcase==($n|ascii_downcase)) | "EXISTS id=\(.id)"'
|
||||
|
||||
# 2. Create client
|
||||
CID=$(curl -s -X POST "$RMM/api/clients" -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
|
||||
--data-binary "{\"name\":\"$NAME\"}" | jq -r '.id')
|
||||
|
||||
# 3. Create site — capture the ONE-TIME api_key immediately to a file
|
||||
curl -s -X POST "$RMM/api/sites" -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
|
||||
--data-binary "{\"client_id\":\"$CID\",\"name\":\"$SITE\"}" > /tmp/site.json
|
||||
SID=$(jq -r '.site.id' /tmp/site.json); SCODE=$(jq -r '.site.site_code' /tmp/site.json); AKEY=$(jq -r '.api_key' /tmp/site.json)
|
||||
|
||||
# 4. Vault the enrollment key (mirror existing clients/<slug>/gururmm-site-main.sops.yaml structure)
|
||||
VR=$(jq -r '.vault_path' "$REPO_ROOT/.claude/identity.json"); T="$VR/clients/$SLUG/gururmm-site-main.sops.yaml"
|
||||
mkdir -p "$(dirname "$T")"
|
||||
cat > "$T" <<YAML
|
||||
client: $NAME
|
||||
site: $SITE
|
||||
created: "$(date +%F)"
|
||||
credentials:
|
||||
client_id: "$CID"
|
||||
site_id: "$SID"
|
||||
site_code: "$SCODE"
|
||||
api_key: "$AKEY"
|
||||
installer_url: "https://rmm.azcomputerguru.com/install/$SCODE"
|
||||
msi_url: "https://rmm.azcomputerguru.com/api/sites/$SID/installer"
|
||||
YAML
|
||||
sops --config "$VR/.sops.yaml" --encrypt --in-place "$T" # encrypt (see gotchas)
|
||||
bash "$REPO_ROOT/.claude/scripts/vault.sh" get "clients/$SLUG/gururmm-site-main.sops.yaml" >/dev/null # verify round-trip
|
||||
git -C "$VR" add "clients/$SLUG/gururmm-site-main.sops.yaml" && git -C "$VR" commit -q -m "add: $NAME GuruRMM site $SITE enrollment key ($SCODE)" && git -C "$VR" push -q
|
||||
```
|
||||
|
||||
**Vault-encryption gotchas (learned 2026-05-29 onboarding Rednour):**
|
||||
- `sops --encrypt` needs the vault's `.sops.yaml`. From outside the vault dir it errors `config file not found` — always pass `--config "$VR/.sops.yaml"`. Encryption uses only the public age keys, so no private key / `SOPS_AGE_KEY_FILE` is required for the encrypt step.
|
||||
- **Quote all date/timestamp values** (`created: "2026-05-29"`). A bare YAML date makes sops 3.7.3 fail with `Cannot walk value, unknown type: time.Time`.
|
||||
- Put every secret under the `credentials:` block — the vault `encrypted_regex` covers `credentials|password|secret|api_key|token|...`. Fields outside it (`client`, `site`, `created`) stay plaintext as searchable metadata.
|
||||
- If `--encrypt --in-place` fails, the file is left **plaintext on disk** — fix and re-encrypt (or delete) immediately; never commit it unencrypted. Confirm with `grep -c 'ENC\[' "$T"` (should be > 0).
|
||||
|
||||
**Report to the user + bot alert:** client_id, site_id, site_code, install page `https://rmm.azcomputerguru.com/install/<SCODE>`, MSI `https://rmm.azcomputerguru.com/api/sites/<SID>/installer`, and the vault path. Onboarding is a write → post a `[RMM] <tech> onboarded client '<name>' + site '<site>' (<SCODE>)` bot alert.
|
||||
|
||||
---
|
||||
|
||||
## Known enrolled agents (verify with GET /api/agents — UUIDs change on re-enroll)
|
||||
|
||||
Do not use this table as authoritative — always resolve live. Treat as a starting hint only.
|
||||
|
||||
Reference in New Issue
Block a user