create-op-node 0.0.1 → 0.1.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 +208 -6
- package/dist/cli.js +3281 -138
- package/dist/cli.js.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -30,7 +30,201 @@ And at any time after that:
|
|
|
30
30
|
npx create-op-node verify --domain your-domain.example
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
Off-LAN health probe of a live node
|
|
33
|
+
Off-LAN health probe of a live node, runnable from anywhere with internet
|
|
34
|
+
access. Five phases:
|
|
35
|
+
|
|
36
|
+
1. **TLS handshake** to `api.<domain>:443` — surfaces cert subject, issuer,
|
|
37
|
+
and days-to-expiry. Warns when the cert is within `--cert-warn-days`
|
|
38
|
+
of expiring (default 14d). Negative expiries render as
|
|
39
|
+
`expired Nd ago`.
|
|
40
|
+
2. **`GET https://api.<domain>/health`** must return 200.
|
|
41
|
+
3. **`POST https://api.<domain>/api`** with `{ __typename }` must return a
|
|
42
|
+
valid GraphQL envelope (catches the "TLS green, but a misconfigured
|
|
43
|
+
proxy returns HTML" case).
|
|
44
|
+
4. **Cloudflare Tunnel status** (optional) — looks up `connections` via
|
|
45
|
+
the CF API. Zero connectors registered → warning that cloudflared on
|
|
46
|
+
the Studio is offline. Requires all three of `--cf-token` (or
|
|
47
|
+
`--cf-token-file`), `--cf-account-id`, `--tunnel-id`; partial
|
|
48
|
+
configuration warns + names the missing flag.
|
|
49
|
+
5. **`cosign verify`** (optional, repeatable `--image`) — keyless
|
|
50
|
+
verification against the GitHub Actions OIDC issuer + Fulcio +
|
|
51
|
+
the Rekor transparency log. Silently skipped when `cosign` isn't on
|
|
52
|
+
`PATH` (install with `brew install cosign` to enable).
|
|
53
|
+
|
|
54
|
+
No phase short-circuits the others — verify always runs the full pass so
|
|
55
|
+
the operator sees the whole landscape in one report. Exits non-zero only
|
|
56
|
+
when at least one phase failed; warnings are reported but don't fail the
|
|
57
|
+
run. Skipped phases are hidden by default; add `--show-skipped` to see them.
|
|
58
|
+
|
|
59
|
+
Full flag set:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
npx create-op-node verify \
|
|
63
|
+
--domain yournode.example.org \
|
|
64
|
+
--cf-token-file ~/.config/opuspopuli/cf-token \
|
|
65
|
+
--cf-account-id $CF_ACCOUNT_ID \
|
|
66
|
+
--tunnel-id $TUNNEL_ID \
|
|
67
|
+
--image ghcr.io/opuspopuli/api:latest \
|
|
68
|
+
--image ghcr.io/opuspopuli/users:latest \
|
|
69
|
+
--cert-warn-days 21
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`--cf-token-file` is preferred over `--cf-token` for cron / systemd
|
|
73
|
+
invocations — the latter ends up in `ps` output, the former doesn't.
|
|
74
|
+
Use `--api-host <host>` to override the default `api.<domain>`
|
|
75
|
+
construction when your node exposes the API at a different subdomain.
|
|
76
|
+
|
|
77
|
+
## Bootstrapping a region config
|
|
78
|
+
|
|
79
|
+
A node serves data; **what** data it serves is defined by a declarative region
|
|
80
|
+
config in [`OpusPopuli/opuspopuli-regions`](https://github.com/OpusPopuli/opuspopuli-regions).
|
|
81
|
+
Hand-writing one of those JSON files against the schema is the same kind of
|
|
82
|
+
fiddly, error-prone step the rest of this CLI exists to remove — so there's a
|
|
83
|
+
subcommand for it. Run it from the root of your `opuspopuli-regions` checkout:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
npx create-op-node region
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The wizard walks you through level (state or county), names, the two-letter
|
|
90
|
+
state code, FIPS code, timezone, and at least one data source (URL, data type,
|
|
91
|
+
source type, content goal). It then:
|
|
92
|
+
|
|
93
|
+
1. Derives the `regionId` and keeps `name === config.regionId` (the invariant
|
|
94
|
+
the regions repo enforces).
|
|
95
|
+
2. Validates the generated file against the **vendored copy of
|
|
96
|
+
`region-plugin.schema.json`** (the canonical contract) using an ESM-native
|
|
97
|
+
JSON Schema validator, then layers on the cross-field rules the repo's
|
|
98
|
+
`pnpm test` adds in code: semver shape, FIPS length per level
|
|
99
|
+
(2 digits for state, 5 for county), county-id-prefixed-by-parent, no
|
|
100
|
+
duplicate data sources keyed by `(dataType, url)`. All checks run **before**
|
|
101
|
+
writing, so the file lands green instead of bouncing off CI.
|
|
102
|
+
3. Writes it to the canonical path
|
|
103
|
+
(`regions/<state>/<state>.json` or
|
|
104
|
+
`regions/<state>/counties/<county>/<county>.json`).
|
|
105
|
+
|
|
106
|
+
> **Conventions you may not expect**
|
|
107
|
+
>
|
|
108
|
+
> - New configs are stamped at **version `0.1.0`** — the documented starting
|
|
109
|
+
> point in `opuspopuli-regions/CLAUDE.md`. Bump manually as the config
|
|
110
|
+
> matures (additions → minor, breaking changes → major).
|
|
111
|
+
> - `boundarySources` is **not** prompted for. It's optional per the schema, so
|
|
112
|
+
> the scaffolded file is valid without it — but if your region has TIGER /
|
|
113
|
+
> ArcGIS boundary coverage and you want PostGIS point-in-polygon district
|
|
114
|
+
> lookups, you'll need to add the block by hand after scaffolding (see
|
|
115
|
+
> `regions/california/california.json` for a worked example).
|
|
116
|
+
> - `civics_blocks` is **not** part of the region config schema. It's a
|
|
117
|
+
> per-region taxonomy that lives elsewhere in the platform — don't look for
|
|
118
|
+
> a prompt for it here.
|
|
119
|
+
|
|
120
|
+
Then it's just `pnpm test` + a PR. Non-interactive flags (`--level`, `--name`,
|
|
121
|
+
`--parent`, `--state-code`, `--fips`, `--timezone`, `--out-dir`, `--force`) are
|
|
122
|
+
available for scripting; run `create-op-node region --help` for the list.
|
|
123
|
+
|
|
124
|
+
### What it looks like
|
|
125
|
+
|
|
126
|
+
```text
|
|
127
|
+
┌ create-op-node region
|
|
128
|
+
|
|
129
|
+
◇ What level is this region?
|
|
130
|
+
│ County
|
|
131
|
+
|
|
132
|
+
◇ County name?
|
|
133
|
+
│ Alameda
|
|
134
|
+
|
|
135
|
+
◇ Parent state slug?
|
|
136
|
+
│ california
|
|
137
|
+
|
|
138
|
+
◇ Display name?
|
|
139
|
+
│ Alameda County
|
|
140
|
+
|
|
141
|
+
◇ One-line description of the data coverage?
|
|
142
|
+
│ Civic data for Alameda County, California
|
|
143
|
+
|
|
144
|
+
◇ Two-letter state code?
|
|
145
|
+
│ CA
|
|
146
|
+
|
|
147
|
+
◇ County FIPS (5 digits)?
|
|
148
|
+
│ 06001
|
|
149
|
+
|
|
150
|
+
◇ IANA timezone?
|
|
151
|
+
│ America/Los_Angeles
|
|
152
|
+
|
|
153
|
+
◇ Data source #1 — URL?
|
|
154
|
+
│ https://bos.acgov.org/
|
|
155
|
+
|
|
156
|
+
◇ Data type?
|
|
157
|
+
│ meetings
|
|
158
|
+
|
|
159
|
+
◇ Source type?
|
|
160
|
+
│ html_scrape
|
|
161
|
+
|
|
162
|
+
◇ Content goal (what should the scraper extract)?
|
|
163
|
+
│ Fetch Board of Supervisors agendas, minutes, and votes
|
|
164
|
+
|
|
165
|
+
◇ Category label (optional)?
|
|
166
|
+
│ Board of Supervisors
|
|
167
|
+
|
|
168
|
+
◇ Add another data source?
|
|
169
|
+
│ No
|
|
170
|
+
|
|
171
|
+
◆ regions/california/counties/alameda/alameda.json (preview) ──────────╮
|
|
172
|
+
│ { │
|
|
173
|
+
│ "name": "california-alameda", │
|
|
174
|
+
│ "displayName": "Alameda County", │
|
|
175
|
+
│ "description": "Civic data for Alameda County, California", │
|
|
176
|
+
│ "version": "0.1.0", │
|
|
177
|
+
│ "config": { │
|
|
178
|
+
│ "regionId": "california-alameda", │
|
|
179
|
+
│ "regionName": "Alameda County", │
|
|
180
|
+
│ "description": "Civic data for Alameda County, California", │
|
|
181
|
+
│ "timezone": "America/Los_Angeles", │
|
|
182
|
+
│ "stateCode": "CA", │
|
|
183
|
+
│ "fipsCode": "06001", │
|
|
184
|
+
│ "dataSources": [ │
|
|
185
|
+
│ { │
|
|
186
|
+
│ "url": "https://bos.acgov.org/", │
|
|
187
|
+
│ "dataType": "meetings", │
|
|
188
|
+
│ "sourceType": "html_scrape", │
|
|
189
|
+
│ "contentGoal": "Fetch Board of Supervisors agendas, ...", │
|
|
190
|
+
│ "category": "Board of Supervisors" │
|
|
191
|
+
│ } │
|
|
192
|
+
│ ] │
|
|
193
|
+
│ }, │
|
|
194
|
+
│ "parentRegionId": "california" │
|
|
195
|
+
│ } │
|
|
196
|
+
├────────────────────────────────────────────────────────────────────────╯
|
|
197
|
+
|
|
198
|
+
◇ Write regions/california/counties/alameda/alameda.json?
|
|
199
|
+
│ Yes
|
|
200
|
+
|
|
201
|
+
◆ Done ────────────────────────────────────────────────────╮
|
|
202
|
+
│ ✓ Wrote regions/california/counties/alameda/alameda.json │
|
|
203
|
+
│ │
|
|
204
|
+
│ Next steps in your opuspopuli-regions checkout: │
|
|
205
|
+
│ pnpm test # schema + hierarchy │
|
|
206
|
+
│ pnpm test:connectivity # URL reachability │
|
|
207
|
+
│ git add … && git commit && open a PR │
|
|
208
|
+
├──────────────────────────────────────────────────────────────╯
|
|
209
|
+
|
|
210
|
+
└ Region scaffolded: california-alameda
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Caveats
|
|
214
|
+
|
|
215
|
+
A couple of honest sharp edges, since this command lives in the *node* CLI
|
|
216
|
+
rather than in the regions repo itself:
|
|
217
|
+
|
|
218
|
+
- **Run it from a `opuspopuli-regions` checkout.** The file is written relative
|
|
219
|
+
to `--out-dir` (default: current directory) at the canonical `regions/…`
|
|
220
|
+
path. Run it anywhere else and the file lands in the wrong tree.
|
|
221
|
+
- **The validation here mirrors the regions schema; it does not import it.**
|
|
222
|
+
`create-op-node` can't see `region-plugin.schema.json` at runtime, so its
|
|
223
|
+
pre-write checks are a hand-maintained copy of the rules. They can drift if
|
|
224
|
+
the schema changes. The regions repo's own `pnpm test` is the source of
|
|
225
|
+
truth — **always run it after scaffolding**; treat a green run there, not a
|
|
226
|
+
green run here, as the real signal. If the two ever disagree, the schema
|
|
227
|
+
wins and this command needs updating.
|
|
34
228
|
|
|
35
229
|
## Why this exists
|
|
36
230
|
|
|
@@ -48,8 +242,9 @@ The CLI itself never holds any credentials beyond the scope of a single command
|
|
|
48
242
|
│ │
|
|
49
243
|
npx create-op-node ─┤ ┌─ init ──────► Cloudflare API │
|
|
50
244
|
│ ├─ bootstrap GitHub API │
|
|
51
|
-
│
|
|
52
|
-
│
|
|
245
|
+
│ ├─ verify Terraform Cloud │
|
|
246
|
+
│ └─ region `op` CLI (optional)│
|
|
247
|
+
│ (writes a regions repo config) │
|
|
53
248
|
└──────────────────────────────────────┘
|
|
54
249
|
│
|
|
55
250
|
▼
|
|
@@ -73,12 +268,18 @@ The CLI itself never holds any credentials beyond the scope of a single command
|
|
|
73
268
|
|
|
74
269
|
## Status
|
|
75
270
|
|
|
76
|
-
|
|
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 1Password.
|
|
272
|
+
|
|
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 1Password, 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
|
+
|
|
275
|
+
**`verify` — scaffold stub.** Type-safe argument parsing only; prints a roadmap-style message and exits.
|
|
276
|
+
|
|
277
|
+
**`region`** — fully wired. Scaffolds schema-valid region configs for the `OpusPopuli/opuspopuli-regions` repo.
|
|
77
278
|
|
|
78
279
|
### Roadmap
|
|
79
280
|
|
|
80
|
-
- **v0.1.0**
|
|
81
|
-
- **v0.2.0**
|
|
281
|
+
- **v0.1.0** ✅ `init` end-to-end + `region` scaffolder.
|
|
282
|
+
- **v0.2.0** ✅ `bootstrap` fully wired on the Studio side.
|
|
82
283
|
- **v0.3.0** — `verify` fully wired: TLS + GraphQL + cosign signature checks.
|
|
83
284
|
- **v0.4.0** — Resend domain + DKIM automation, drift detection, automated backup-restore drill.
|
|
84
285
|
|
|
@@ -117,5 +318,6 @@ node dist/cli.js --help # test the built binary
|
|
|
117
318
|
## Related
|
|
118
319
|
|
|
119
320
|
- [`OpusPopuli/opuspopuli-node`](https://github.com/OpusPopuli/opuspopuli-node) — the per-region deployment template this CLI creates from.
|
|
321
|
+
- [`OpusPopuli/opuspopuli-regions`](https://github.com/OpusPopuli/opuspopuli-regions) — declarative region configs; `create-op-node region` scaffolds one.
|
|
120
322
|
- [`OpusPopuli/opuspopuli`](https://github.com/OpusPopuli/opuspopuli) — the central monorepo that builds + publishes `ghcr.io/opuspopuli/*` images.
|
|
121
323
|
- [`OpusPopuli/prompt-service`](https://github.com/OpusPopuli/prompt-service) — private prompt-template service consumed by every node.
|