create-op-node 0.9.1 → 0.10.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/dist/cli.js +126 -4
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -237,7 +237,10 @@ var FRIENDLY = {
|
|
|
237
237
|
"jwt-secret": "JWT signing secret",
|
|
238
238
|
"supabase-anon-key": "Supabase anon key",
|
|
239
239
|
"supabase-service-role-key": "Supabase service role key",
|
|
240
|
-
"dashboard-password": "Supabase Studio dashboard password"
|
|
240
|
+
"dashboard-password": "Supabase Studio dashboard password",
|
|
241
|
+
"prompts-db-password": "prompt-service Postgres password",
|
|
242
|
+
"prompt-service-api-key": "prompt-service HMAC API key",
|
|
243
|
+
"prompt-service-admin-api-key": "prompt-service admin API key"
|
|
241
244
|
};
|
|
242
245
|
function labelFor(coords) {
|
|
243
246
|
return `Opus Populi (${coords.region}) \u2014 ${FRIENDLY[coords.account]}`;
|
|
@@ -448,6 +451,9 @@ function generatePostgresPassword() {
|
|
|
448
451
|
function generateDashboardPassword() {
|
|
449
452
|
return base64url(randomBytes(24));
|
|
450
453
|
}
|
|
454
|
+
function generateHmacApiKey() {
|
|
455
|
+
return base64url(randomBytes(32));
|
|
456
|
+
}
|
|
451
457
|
function generateJwtSecret() {
|
|
452
458
|
return randomBytes(48).toString("base64");
|
|
453
459
|
}
|
|
@@ -1405,12 +1411,26 @@ async function teardownLaunchAgent(paths, opts = {}) {
|
|
|
1405
1411
|
|
|
1406
1412
|
// src/lib/op-compose-script.ts
|
|
1407
1413
|
var REGION_RE = /^[a-z0-9-]{2,32}$/;
|
|
1414
|
+
var PROMPT_SERVICE_URL_RE = /^[A-Za-z0-9:/_.-]+$/;
|
|
1408
1415
|
function renderOpComposeScript(input) {
|
|
1409
1416
|
if (!REGION_RE.test(input.region)) {
|
|
1410
1417
|
throw new Error(
|
|
1411
1418
|
`region ${JSON.stringify(input.region)} contains characters not allowed in a launchd / Keychain service identifier`
|
|
1412
1419
|
);
|
|
1413
1420
|
}
|
|
1421
|
+
if (input.promptServiceUrl !== void 0 && !PROMPT_SERVICE_URL_RE.test(input.promptServiceUrl)) {
|
|
1422
|
+
throw new Error(
|
|
1423
|
+
`promptServiceUrl ${JSON.stringify(input.promptServiceUrl)} contains characters not allowed in a wrapper export`
|
|
1424
|
+
);
|
|
1425
|
+
}
|
|
1426
|
+
const promptServiceUrlExport = input.promptServiceUrl !== void 0 ? `# Backend services call prompt-service via this URL. Service-level env
|
|
1427
|
+
# from docker-compose-prompt-service.yml overlay (when layered) wins
|
|
1428
|
+
# over this default \u2014 so colocated deployments still hit
|
|
1429
|
+
# http://opuspopuli-prompts:3210 even though we may have a public URL
|
|
1430
|
+
# baked in here.
|
|
1431
|
+
export PROMPT_SERVICE_URL="\${PROMPT_SERVICE_URL:-${input.promptServiceUrl}}"
|
|
1432
|
+
|
|
1433
|
+
` : "";
|
|
1414
1434
|
return `#!/bin/bash
|
|
1415
1435
|
# op-compose \u2014 wrapper that reads region secrets from macOS Keychain just
|
|
1416
1436
|
# before invoking \`docker compose\`. Auto-generated by
|
|
@@ -1470,6 +1490,29 @@ fi
|
|
|
1470
1490
|
# default to localhost for --local-only.
|
|
1471
1491
|
export SUPABASE_URL="\${SUPABASE_URL:-http://localhost:8000}"
|
|
1472
1492
|
|
|
1493
|
+
${promptServiceUrlExport}# prompt-service credentials. Always exported \u2014 harmless when the operator
|
|
1494
|
+
# is NOT colocating prompt-service (the main compose doesn't read these
|
|
1495
|
+
# vars; the overlay does). The PROMPT_SERVICE_API_KEYS list is built from
|
|
1496
|
+
# the operator's region + the single HMAC key in Keychain, matching the
|
|
1497
|
+
# prompt-service env format \`<region>:<key>,<region>:<key>,\u2026\`.
|
|
1498
|
+
PROMPTS_DB_PASSWORD_VAL="$(optional_secret prompts-db-password)"
|
|
1499
|
+
PROMPT_SERVICE_API_KEY_VAL="$(optional_secret prompt-service-api-key)"
|
|
1500
|
+
PROMPT_SERVICE_ADMIN_API_KEY_VAL="$(optional_secret prompt-service-admin-api-key)"
|
|
1501
|
+
if [ -n "$PROMPTS_DB_PASSWORD_VAL" ]; then
|
|
1502
|
+
export PROMPTS_DB_PASSWORD="$PROMPTS_DB_PASSWORD_VAL"
|
|
1503
|
+
fi
|
|
1504
|
+
if [ -n "$PROMPT_SERVICE_API_KEY_VAL" ]; then
|
|
1505
|
+
export PROMPT_SERVICE_API_KEY="$PROMPT_SERVICE_API_KEY_VAL"
|
|
1506
|
+
# API_KEYS env on prompt-service is \`<region>:<key>,\u2026\` \u2014 render the
|
|
1507
|
+
# single-region pair here so prompt-service accepts the same key the
|
|
1508
|
+
# backend sends. The prompts team adds additional regions on the
|
|
1509
|
+
# central deployment for Phase 2.
|
|
1510
|
+
export PROMPT_SERVICE_API_KEYS="${input.region}:$PROMPT_SERVICE_API_KEY_VAL"
|
|
1511
|
+
fi
|
|
1512
|
+
if [ -n "$PROMPT_SERVICE_ADMIN_API_KEY_VAL" ]; then
|
|
1513
|
+
export PROMPT_SERVICE_ADMIN_API_KEYS="$PROMPT_SERVICE_ADMIN_API_KEY_VAL"
|
|
1514
|
+
fi
|
|
1515
|
+
|
|
1473
1516
|
exec docker compose "$@"
|
|
1474
1517
|
`;
|
|
1475
1518
|
}
|
|
@@ -1478,7 +1521,10 @@ exec docker compose "$@"
|
|
|
1478
1521
|
async function installOpComposeWrapper(input) {
|
|
1479
1522
|
let content;
|
|
1480
1523
|
try {
|
|
1481
|
-
content = renderOpComposeScript({
|
|
1524
|
+
content = renderOpComposeScript({
|
|
1525
|
+
region: input.region,
|
|
1526
|
+
...input.promptServiceUrl !== void 0 ? { promptServiceUrl: input.promptServiceUrl } : {}
|
|
1527
|
+
});
|
|
1482
1528
|
} catch (err) {
|
|
1483
1529
|
return { ok: false, reason: err.message };
|
|
1484
1530
|
}
|
|
@@ -1872,6 +1918,16 @@ var bootstrapCommand = new Command("bootstrap").description(
|
|
|
1872
1918
|
"--supabase-url <url>",
|
|
1873
1919
|
"Public-facing Supabase URL (what browsers + microservices use). Default: `http://localhost:8000` in --local-only mode, otherwise `https://supabase.<domain>` (which the operator can override)."
|
|
1874
1920
|
)
|
|
1921
|
+
).addOption(
|
|
1922
|
+
new Option(
|
|
1923
|
+
"--node-type <type>",
|
|
1924
|
+
"Deployment topology. `region` (default) = region node connecting to remote prompt-service. `region-with-prompts` = region node + colocated prompt-service overlay (team/dev use). `prompts-only` = future stub for the central prompts deployment."
|
|
1925
|
+
).choices(["region", "region-with-prompts", "prompts-only"])
|
|
1926
|
+
).addOption(
|
|
1927
|
+
new Option(
|
|
1928
|
+
"--prompt-service-url <url>",
|
|
1929
|
+
"Remote prompt-service URL. Only meaningful for --node-type=region (default: https://prompts.opuspopuli.org). Ignored for --node-type=region-with-prompts (the overlay pins it to http://opuspopuli-prompts:3210)."
|
|
1930
|
+
)
|
|
1875
1931
|
).addOption(new Option("--skip-stack", "Stop before `docker compose pull && up`").default(false)).addOption(
|
|
1876
1932
|
new Option(
|
|
1877
1933
|
"--local-only",
|
|
@@ -1879,6 +1935,32 @@ var bootstrapCommand = new Command("bootstrap").description(
|
|
|
1879
1935
|
).default(false)
|
|
1880
1936
|
).addOption(new Option("-y, --yes", "Skip confirmation prompts").default(false)).action(async (opts) => {
|
|
1881
1937
|
p3.intro(pc2.bgCyan(pc2.black(" create-op-node bootstrap ")));
|
|
1938
|
+
const nodeType = opts.nodeType ? opts.nodeType : unwrap(
|
|
1939
|
+
await p3.select({
|
|
1940
|
+
message: "What kind of node are you provisioning?",
|
|
1941
|
+
options: [
|
|
1942
|
+
{
|
|
1943
|
+
value: "region",
|
|
1944
|
+
label: "Region node (civic data for one region, remote prompt-service)"
|
|
1945
|
+
},
|
|
1946
|
+
{
|
|
1947
|
+
value: "region-with-prompts",
|
|
1948
|
+
label: "Region + colocated prompt-service (team / dev)"
|
|
1949
|
+
},
|
|
1950
|
+
{
|
|
1951
|
+
value: "prompts-only",
|
|
1952
|
+
label: "Prompt-service node (central prompts deployment \u2014 future)"
|
|
1953
|
+
}
|
|
1954
|
+
],
|
|
1955
|
+
initialValue: "region"
|
|
1956
|
+
})
|
|
1957
|
+
);
|
|
1958
|
+
if (nodeType === "prompts-only") {
|
|
1959
|
+
p3.cancel(
|
|
1960
|
+
"prompts-only node deployment is not yet implemented. This flag exists so the CLI surface is stable for when the prompts team deploys the central prompts.opuspopuli.org instance \u2014 but the deployment stack (compose file, healthchecks, observability) is filled in later, when there is an actual central prompt-service to deploy."
|
|
1961
|
+
);
|
|
1962
|
+
process.exit(1);
|
|
1963
|
+
}
|
|
1882
1964
|
const region = opts.region ? opts.region : unwrap(
|
|
1883
1965
|
await p3.text({
|
|
1884
1966
|
message: "Region label (the slug used during init \u2014 e.g. us-ca)?",
|
|
@@ -2064,6 +2146,46 @@ var bootstrapCommand = new Command("bootstrap").description(
|
|
|
2064
2146
|
validate: (v) => URL_SAFE_PASSWORD_RE.test(v) ? void 0 : "must be base64url chars only (no + / =)",
|
|
2065
2147
|
generate: generateDashboardPassword
|
|
2066
2148
|
});
|
|
2149
|
+
if (nodeType === "region-with-prompts") {
|
|
2150
|
+
await loadSecret({
|
|
2151
|
+
region,
|
|
2152
|
+
account: "prompts-db-password",
|
|
2153
|
+
label: "prompt-service Postgres password",
|
|
2154
|
+
validate: (v) => URL_SAFE_PASSWORD_RE.test(v) ? void 0 : "must be base64url chars only (no + / =)",
|
|
2155
|
+
generate: generatePostgresPassword
|
|
2156
|
+
});
|
|
2157
|
+
await loadSecret({
|
|
2158
|
+
region,
|
|
2159
|
+
account: "prompt-service-api-key",
|
|
2160
|
+
label: "prompt-service HMAC API key",
|
|
2161
|
+
// Same URL-safe alphabet as other HMAC keys; embedded in the
|
|
2162
|
+
// `<region>:<key>` API_KEYS list and in Authorization headers.
|
|
2163
|
+
validate: (v) => URL_SAFE_PASSWORD_RE.test(v) ? void 0 : "must be base64url chars only (no + / =)",
|
|
2164
|
+
generate: generateHmacApiKey
|
|
2165
|
+
});
|
|
2166
|
+
await loadSecret({
|
|
2167
|
+
region,
|
|
2168
|
+
account: "prompt-service-admin-api-key",
|
|
2169
|
+
label: "prompt-service admin API key",
|
|
2170
|
+
validate: (v) => URL_SAFE_PASSWORD_RE.test(v) ? void 0 : "must be base64url chars only (no + / =)",
|
|
2171
|
+
generate: generateHmacApiKey
|
|
2172
|
+
});
|
|
2173
|
+
} else {
|
|
2174
|
+
await loadSecret({
|
|
2175
|
+
region,
|
|
2176
|
+
account: "prompt-service-api-key",
|
|
2177
|
+
label: "prompt-service HMAC API key (issued by the prompts team)",
|
|
2178
|
+
placeholder: "paste the region-specific HMAC key",
|
|
2179
|
+
validate: (v) => URL_SAFE_PASSWORD_RE.test(v) ? void 0 : "must be base64url chars only (no + / =)"
|
|
2180
|
+
});
|
|
2181
|
+
}
|
|
2182
|
+
const promptServiceUrl = nodeType === "region-with-prompts" ? "http://opuspopuli-prompts:3210" : opts.promptServiceUrl ?? "https://prompts.opuspopuli.org";
|
|
2183
|
+
if (!SAFE_URL_RE.test(promptServiceUrl)) {
|
|
2184
|
+
p3.cancel(
|
|
2185
|
+
`--prompt-service-url ${JSON.stringify(promptServiceUrl)} contains characters outside the allowed URL set`
|
|
2186
|
+
);
|
|
2187
|
+
process.exit(1);
|
|
2188
|
+
}
|
|
2067
2189
|
let supabaseUrl;
|
|
2068
2190
|
if (opts.supabaseUrl) {
|
|
2069
2191
|
supabaseUrl = opts.supabaseUrl;
|
|
@@ -2112,7 +2234,7 @@ var bootstrapCommand = new Command("bootstrap").description(
|
|
|
2112
2234
|
}
|
|
2113
2235
|
const wrapperSpin = p3.spinner();
|
|
2114
2236
|
wrapperSpin.start("Installing bin/op-compose wrapper\u2026");
|
|
2115
|
-
const wrapper = await installOpComposeWrapper({ repoDir: repoPath, region });
|
|
2237
|
+
const wrapper = await installOpComposeWrapper({ repoDir: repoPath, region, promptServiceUrl });
|
|
2116
2238
|
if (!wrapper.ok) {
|
|
2117
2239
|
wrapperSpin.stop(pc2.red("\u2717 op-compose wrapper install failed."));
|
|
2118
2240
|
p3.cancel(wrapper.reason ?? "op-compose wrapper install failed.");
|
|
@@ -4212,7 +4334,7 @@ async function looksLikeRegionsRepo(dir) {
|
|
|
4212
4334
|
}
|
|
4213
4335
|
|
|
4214
4336
|
// src/cli.ts
|
|
4215
|
-
var VERSION = "0.
|
|
4337
|
+
var VERSION = "0.10.0";
|
|
4216
4338
|
var program = new Command();
|
|
4217
4339
|
program.name("create-op-node").description(
|
|
4218
4340
|
"Interactive bootstrap for an Opus Populi federation node.\nCloudflare infrastructure \u2192 Mac Studio \u2192 live public API."
|