diff --git a/.claude/commands/rmm.md b/.claude/commands/rmm.md index 3431103..a70491c 100644 --- a/.claude/commands/rmm.md +++ b/.claude/commands/rmm.md @@ -23,6 +23,7 @@ Interact with the GuruRMM agent fleet: list agents, run remote commands (PowerSh /rmm output Fetch full stdout + stderr for a completed command /rmm cancel Cancel a pending or running command /rmm history [N] Recent command history (default 10, max 500) +/rmm onboard [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": "", "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//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" </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/`, MSI `https://rmm.azcomputerguru.com/api/sites//installer`, and the vault path. Onboarding is a write → post a `[RMM] onboarded client '' + site '' ()` 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.