create-op-node 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +124 -7
- package/dist/cli.js +625 -327
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,8 +13,8 @@ That's it. The wizard walks you through:
|
|
|
13
13
|
2. **GitHub** — creates your region's node repo from the [`OpusPopuli/opuspopuli-node`](https://github.com/OpusPopuli/opuspopuli-node) template via the GitHub API. Seeds the 5 required GitHub Secrets (Cloudflare token, account ID, zone ID, Terraform Cloud token, TFC org) for you.
|
|
14
14
|
3. **Terraform Cloud** — verifies your TFC token, prepares the workspace.
|
|
15
15
|
4. **First PR** — writes `environments/prod.tfvars` from your answers, commits, opens the first PR. The node repo's `cloudflare-infra.yml` workflow runs `terraform plan` against the PR; on merge to `main` it applies — Tunnel, DNS, R2 buckets, and Pages project come up automatically.
|
|
16
|
-
5. **pgsodium master key** — generates a fresh 64-hex root key, stores it in
|
|
17
|
-
6. **Tunnel token retrieval** — after `terraform apply` lands, fetches the Tunnel token from Terraform Cloud outputs and stores it
|
|
16
|
+
5. **pgsodium master key** — generates a fresh 64-hex root key, stores it in your **macOS login Keychain** as `org.opuspopuli.<region>/pgsodium-root-key`. No third-party password manager required.
|
|
17
|
+
6. **Tunnel token retrieval** — after `terraform apply` lands, fetches the Tunnel token from Terraform Cloud outputs and stores it alongside the pgsodium key in your Keychain.
|
|
18
18
|
|
|
19
19
|
Then on the Mac Studio itself:
|
|
20
20
|
|
|
@@ -22,9 +22,76 @@ Then on the Mac Studio itself:
|
|
|
22
22
|
npx create-op-node bootstrap
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
Configures macOS power settings, installs Homebrew + the CLI tool list, sets up Docker Desktop + Tailscale + Ollama, clones the node repo you created,
|
|
25
|
+
Configures macOS power settings, installs Homebrew + the CLI tool list, sets up Docker Desktop + Tailscale + Ollama, clones the node repo you created, reads the pgsodium key + Tunnel token from the Studio's Keychain (or prompts you to paste them once, then persists for re-runs), writes the LaunchAgent plist, logs into ghcr.io, pulls + warms the LLM model, and finally `docker compose pull && up -d` brings the whole stack online. Health-check loop waits until all 10 containers are `(healthy)`.
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
> **Secret transport between laptop and Studio**
|
|
28
|
+
>
|
|
29
|
+
> The macOS `security` CLI writes to the local login keychain — items
|
|
30
|
+
> don't sync to iCloud Keychain automatically. On the Studio's first
|
|
31
|
+
> bootstrap, the operator pastes the pgsodium key + Tunnel token once
|
|
32
|
+
> (from the laptop's Keychain Access, or wherever you copied them); the
|
|
33
|
+
> Studio bootstrap validates the format and persists locally so re-runs
|
|
34
|
+
> read straight through. Use AirDrop / `security find-generic-password`
|
|
35
|
+
> output / Tailscale `scp` to ferry the values.
|
|
36
|
+
|
|
37
|
+
## Resetting the Studio
|
|
38
|
+
|
|
39
|
+
To start over (e.g. before rerunning `bootstrap` against a different
|
|
40
|
+
region, or after a misconfiguration), reverse the Studio-side state:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npx create-op-node reset --region us-ca
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Three phases run in reverse-bootstrap order. By default volumes are
|
|
47
|
+
preserved — the database survives so you can bring the stack back up
|
|
48
|
+
with `bootstrap` without losing data.
|
|
49
|
+
|
|
50
|
+
1. **Stop the stack** — `docker compose down`. Pass `--wipe-data` to
|
|
51
|
+
add `-v` (destroys named volumes including the database). The wipe
|
|
52
|
+
mode requires retyping the region label as confirmation — and the
|
|
53
|
+
prompt deliberately doesn't pre-fill the answer, so you have to type
|
|
54
|
+
it from memory. `y` won't do it.
|
|
55
|
+
2. **Unload + remove the LaunchAgent** — `launchctl unload` then `rm`
|
|
56
|
+
the plist and the pgsodium key file. `--keep-key-file` leaves the
|
|
57
|
+
key in place as a belt-and-suspenders backup before a wipe-data run.
|
|
58
|
+
3. **`docker logout`** — clears the registry-credentials store entry
|
|
59
|
+
for `ghcr.io` (or override with `--registry`). This only clears the
|
|
60
|
+
store entry; if your credential helper caches the token elsewhere
|
|
61
|
+
(or `~/.docker/config.json` has stale entries from another host),
|
|
62
|
+
those need separate cleanup.
|
|
63
|
+
|
|
64
|
+
Reset does **not** touch cloud-side state: the Cloudflare resources,
|
|
65
|
+
the GitHub repo, and the TFC workspace remain. Keychain items on the
|
|
66
|
+
Studio are also left in place — `security delete-generic-password -s
|
|
67
|
+
org.opuspopuli.<region> -a pgsodium-root-key` etc. if you want them
|
|
68
|
+
gone.
|
|
69
|
+
`init` is idempotent against existing cloud setup, so re-running it
|
|
70
|
+
won't duplicate anything.
|
|
71
|
+
|
|
72
|
+
Useful flags:
|
|
73
|
+
|
|
74
|
+
- `--dry-run` — print the plan without acting. Phases that would run
|
|
75
|
+
show with a `?` icon; phases that are skipped show with `·`.
|
|
76
|
+
- `--skip-stack` / `--skip-launch-agent` / `--skip-docker-logout` —
|
|
77
|
+
surgical resets when only one piece needs cleaning.
|
|
78
|
+
- `--no-remove-orphans` — drop `--remove-orphans` from `compose down`.
|
|
79
|
+
Useful when you ran bootstrap with a custom `--compose-file` set and
|
|
80
|
+
reset without it.
|
|
81
|
+
- `--repo-dir <path>` — explicit path to the cloned node repo when
|
|
82
|
+
reset is run from outside the checkout. Passing a path that doesn't
|
|
83
|
+
look like a node repo is a hard error, not a silent skip.
|
|
84
|
+
- `--registry <reg>` — log out of a registry other than `ghcr.io`.
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
# Try-before-you-buy: preview every step.
|
|
88
|
+
npx create-op-node reset --region us-ca --dry-run
|
|
89
|
+
|
|
90
|
+
# Nuke from orbit: containers + volumes + LaunchAgent + ghcr credentials.
|
|
91
|
+
npx create-op-node reset --region us-ca --wipe-data
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Verifying a live node
|
|
28
95
|
|
|
29
96
|
```bash
|
|
30
97
|
npx create-op-node verify --domain your-domain.example
|
|
@@ -226,13 +293,63 @@ rather than in the regions repo itself:
|
|
|
226
293
|
green run here, as the real signal. If the two ever disagree, the schema
|
|
227
294
|
wins and this command needs updating.
|
|
228
295
|
|
|
296
|
+
## What lands where
|
|
297
|
+
|
|
298
|
+
`create-op-node` touches several secret stores. Here's the full map of
|
|
299
|
+
what we own (the two macOS Keychain items) vs. what we just route to its
|
|
300
|
+
destination.
|
|
301
|
+
|
|
302
|
+
### Stored by `create-op-node` in macOS Keychain
|
|
303
|
+
|
|
304
|
+
Two items per region, both generic-password class
|
|
305
|
+
(`kSecClassGenericPassword`). Visible in **Keychain Access.app**
|
|
306
|
+
(`/System/Applications/Utilities/Keychain Access.app`) — **not** in the
|
|
307
|
+
new Passwords.app, which is filtered to website-login items only.
|
|
308
|
+
|
|
309
|
+
| # | Service | Account | Label (GUI display) | Value format | Written by | Read by |
|
|
310
|
+
|---|---|---|---|---|---|---|
|
|
311
|
+
| 1 | `org.opuspopuli.<region>` | `pgsodium-root-key` | `Opus Populi (<region>) — pgsodium root key` | 64 lowercase hex chars | `init` on laptop | `bootstrap` on Studio |
|
|
312
|
+
| 2 | `org.opuspopuli.<region>` | `tunnel-token` | `Opus Populi (<region>) — Cloudflare Tunnel token` | JWT-style base64url string | `init` on laptop (after TFC apply) | `bootstrap` on Studio |
|
|
313
|
+
|
|
314
|
+
Both items also carry `-D 'Opus Populi secret'` (the "Kind" column in
|
|
315
|
+
Keychain Access) so you can filter for them at a glance.
|
|
316
|
+
|
|
317
|
+
Inspect from a shell:
|
|
318
|
+
|
|
319
|
+
```bash
|
|
320
|
+
# Metadata only (safe to share output):
|
|
321
|
+
security find-generic-password -s org.opuspopuli.us-ca -a pgsodium-root-key
|
|
322
|
+
|
|
323
|
+
# Reveal the value (you'll be prompted to allow access on first call):
|
|
324
|
+
security find-generic-password -s org.opuspopuli.us-ca -a pgsodium-root-key -w
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### Stored elsewhere (we don't put these in Keychain)
|
|
328
|
+
|
|
329
|
+
Everything else flows through transiently or lives in its destination
|
|
330
|
+
system's own credential store.
|
|
331
|
+
|
|
332
|
+
| Secret | Where it lives | Why not in Keychain |
|
|
333
|
+
|---|---|---|
|
|
334
|
+
| Cloudflare API token | Pasted into `init` prompt → forwarded to **GitHub Secrets** + **Terraform Cloud** vars | One-shot during init. Re-runs prompt again. We could store it; adds risk vs benefit. |
|
|
335
|
+
| Cloudflare account ID, zone ID | Same as above | Not really a "secret" but flow alongside the token |
|
|
336
|
+
| Terraform Cloud token | Pasted, used to verify + poll runs | Same one-shot pattern |
|
|
337
|
+
| GitHub PAT | Read from `gh auth token` if available, else pasted | `gh` already manages it |
|
|
338
|
+
| pgsodium key (Studio runtime form) | `~/.config/opuspopuli/pgsodium_root_key` (mode `0400`) | LaunchAgent reads it at every login → interpolates into the `PGSODIUM_ROOT_KEY` env var. Same value as in Keychain; file is runtime form. |
|
|
339
|
+
| Cloudflare Tunnel token (Studio runtime form) | Baked into `~/Library/LaunchAgents/org.opuspopuli.envloader.plist` (mode `0600`) | launchd's `launchctl setenv TUNNEL_TOKEN` injects it into the session at every boot. Same value as in Keychain; plist is runtime form. |
|
|
340
|
+
| ghcr.io credentials | `~/.docker/config.json` or `docker-credential-osxkeychain` | Docker manages its own credential store — it actually saves the ghcr token to a separate Keychain item under service `ghcr.io`. We just call `docker login`. |
|
|
341
|
+
|
|
342
|
+
Per region, the **only** persistent secrets `create-op-node` owns are
|
|
343
|
+
the two Keychain items above. Everything else is either transient
|
|
344
|
+
(prompted, used, forgotten) or lives in its destination system.
|
|
345
|
+
|
|
229
346
|
## Why this exists
|
|
230
347
|
|
|
231
348
|
Each Opus Populi region is operated independently by a local maintainer — its own Cloudflare account, its own Mac Studio, its own domain. The full bootstrap is a few hours of manual steps across Cloudflare, GitHub, Terraform Cloud, macOS Setup Assistant, Docker Desktop, Tailscale, Ollama, and the node's own Docker Compose stack. Doable from the runbook, but error-prone.
|
|
232
349
|
|
|
233
350
|
This CLI exists to make that bootstrap **foolproof** — every prompt validates immediately, every secret is retrieved from a secure source (never echoed, never written to disk in plaintext), and every step has an explicit "what happens next" message. The goal is zero documentation reading required to get a node running.
|
|
234
351
|
|
|
235
|
-
The CLI itself never holds any credentials beyond the scope of a single command — secrets flow from your
|
|
352
|
+
The CLI itself never holds any credentials beyond the scope of a single command — secrets flow from your macOS Keychain → through the CLI → directly into the destination (GitHub Secrets, Terraform Cloud workspace variables, Mac Studio LaunchAgent). Nothing persists in this process.
|
|
236
353
|
|
|
237
354
|
## Architecture
|
|
238
355
|
|
|
@@ -268,9 +385,9 @@ The CLI itself never holds any credentials beyond the scope of a single command
|
|
|
268
385
|
|
|
269
386
|
## Status
|
|
270
387
|
|
|
271
|
-
**`init` — fully wired.** Full Phase 1 of the runbook: prompts → Cloudflare 5-scope probe → Terraform Cloud verify → GitHub template clone → 5 repo secrets seeded → branch + prod.tfvars committed → PR opened → pgsodium key generated → (after operator merges PR) Terraform apply polled → Tunnel token retrieved + saved to
|
|
388
|
+
**`init` — fully wired.** Full Phase 1 of the runbook: prompts → Cloudflare 5-scope probe → Terraform Cloud verify → GitHub template clone → 5 repo secrets seeded → branch + prod.tfvars committed → PR opened → pgsodium key generated → (after operator merges PR) Terraform apply polled → Tunnel token retrieved + saved to the macOS Keychain.
|
|
272
389
|
|
|
273
|
-
**`bootstrap` — fully wired.** Phase 2 on the Mac Studio: macOS sanity (auto-restart, disk sleep), Homebrew + tool installs (gh, pnpm, jq, cloudflared, rclone, ollama, docker, tailscale), GitHub + Tailscale signin prompts, pgsodium key + Tunnel token read from
|
|
390
|
+
**`bootstrap` — fully wired.** Phase 2 on the Mac Studio: macOS sanity (auto-restart, disk sleep), Homebrew + tool installs (gh, pnpm, jq, cloudflared, rclone, ollama, docker, tailscale), GitHub + Tailscale signin prompts, pgsodium key + Tunnel token read from the Studio's Keychain (or pasted in once if first run on that machine, then persisted), LaunchAgent written + loaded, ghcr.io login, Ollama models pulled + warmed, region repo located or cloned, `docker compose pull && up -d`, health-check loop until everything reports `(healthy)`.
|
|
274
391
|
|
|
275
392
|
**`verify` — scaffold stub.** Type-safe argument parsing only; prints a roadmap-style message and exits.
|
|
276
393
|
|