@caelo-cms/provisioning 0.1.1 → 0.2.1
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 +92 -7
- package/dist/cli.js.map +1 -1
- package/dist/compose.d.ts +7 -0
- package/dist/compose.d.ts.map +1 -1
- package/dist/compose.js +10 -9
- package/dist/compose.js.map +1 -1
- package/dist/dns/cloudflare.d.ts +9 -0
- package/dist/dns/cloudflare.d.ts.map +1 -0
- package/dist/dns/cloudflare.js +160 -0
- package/dist/dns/cloudflare.js.map +1 -0
- package/dist/dns/index.d.ts +12 -0
- package/dist/dns/index.d.ts.map +1 -0
- package/dist/dns/index.js +42 -0
- package/dist/dns/index.js.map +1 -0
- package/dist/dns/manual.d.ts +5 -0
- package/dist/dns/manual.d.ts.map +1 -0
- package/dist/dns/manual.js +96 -0
- package/dist/dns/manual.js.map +1 -0
- package/dist/dns/types.d.ts +23 -0
- package/dist/dns/types.d.ts.map +1 -0
- package/dist/dns/types.js +3 -0
- package/dist/dns/types.js.map +1 -0
- package/dist/gcloud.d.ts +42 -0
- package/dist/gcloud.d.ts.map +1 -0
- package/dist/gcloud.js +187 -0
- package/dist/gcloud.js.map +1 -0
- package/dist/install-state.d.ts +54 -0
- package/dist/install-state.d.ts.map +1 -0
- package/dist/install-state.js +118 -0
- package/dist/install-state.js.map +1 -0
- package/dist/lifecycle.d.ts +19 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +589 -0
- package/dist/lifecycle.js.map +1 -0
- package/dist/migration-runner.d.ts +15 -0
- package/dist/migration-runner.d.ts.map +1 -0
- package/dist/migration-runner.js +174 -0
- package/dist/migration-runner.js.map +1 -0
- package/dist/redirects-emit.d.ts.map +1 -1
- package/dist/redirects-emit.js +4 -1
- package/dist/redirects-emit.js.map +1 -1
- package/dist/wizard.d.ts +35 -0
- package/dist/wizard.d.ts.map +1 -0
- package/dist/wizard.js +160 -0
- package/dist/wizard.js.map +1 -0
- package/dist/wizards/gcp-cost.d.ts +27 -0
- package/dist/wizards/gcp-cost.d.ts.map +1 -0
- package/dist/wizards/gcp-cost.js +77 -0
- package/dist/wizards/gcp-cost.js.map +1 -0
- package/dist/wizards/gcp-pulumi.d.ts +37 -0
- package/dist/wizards/gcp-pulumi.d.ts.map +1 -0
- package/dist/wizards/gcp-pulumi.js +100 -0
- package/dist/wizards/gcp-pulumi.js.map +1 -0
- package/dist/wizards/gcp.d.ts +9 -0
- package/dist/wizards/gcp.d.ts.map +1 -0
- package/dist/wizards/gcp.js +895 -0
- package/dist/wizards/gcp.js.map +1 -0
- package/package.json +13 -2
- package/stacks/aws/index.ts +6 -7
- package/stacks/azure/index.ts +11 -11
- package/stacks/gcp/Pulumi.production.yaml +16 -0
- package/stacks/gcp/Pulumi.yaml +52 -6
- package/stacks/gcp/index.ts +569 -188
- package/stacks/self-hosted/index.ts +3 -3
- package/static/welcome.html +155 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
/**
|
|
3
|
+
* Manual DNS adapter — fallback for registrars without an API
|
|
4
|
+
* integration (Namecheap, GoDaddy, Porkbun, etc.). Prints the records,
|
|
5
|
+
* waits for the operator to paste them at the registrar, polls public
|
|
6
|
+
* DNS until resolution succeeds.
|
|
7
|
+
*
|
|
8
|
+
* Per CLAUDE.md §11.C: this IS one of the few human-required steps
|
|
9
|
+
* the wizard can't skip. The polling makes it close to zero-friction —
|
|
10
|
+
* the operator pastes records, presses Enter, the wizard waits.
|
|
11
|
+
*/
|
|
12
|
+
import { promises as dns } from "node:dns";
|
|
13
|
+
import { setTimeout as sleep } from "node:timers/promises";
|
|
14
|
+
import { confirm, isCancel, log, note, spinner } from "@clack/prompts";
|
|
15
|
+
import { bold, cyan, dim, green, red } from "kleur/colors";
|
|
16
|
+
const POLL_INTERVAL_MS = 5000;
|
|
17
|
+
const POLL_TIMEOUT_MS = 30 * 60 * 1000; // 30 min — managed certs need ~15 min to issue after DNS
|
|
18
|
+
export function makeManualAdapter(opts) {
|
|
19
|
+
return {
|
|
20
|
+
name: `Manual (paste at your registrar for ${opts.domain})`,
|
|
21
|
+
async applyRecords(records) {
|
|
22
|
+
note([
|
|
23
|
+
bold("Paste these DNS records at your registrar:"),
|
|
24
|
+
"",
|
|
25
|
+
...records.map((r) => ` ${dim(r.type.padEnd(6))} ${r.hostname.padEnd(35)} → ${bold(r.value)}`),
|
|
26
|
+
"",
|
|
27
|
+
dim("After pasting, press Enter to verify resolution."),
|
|
28
|
+
].join("\n"), "DNS records");
|
|
29
|
+
const ack = await confirm({
|
|
30
|
+
message: "Records pasted at your registrar?",
|
|
31
|
+
initialValue: true,
|
|
32
|
+
});
|
|
33
|
+
if (isCancel(ack) || !ack) {
|
|
34
|
+
log.error(red("Aborted at DNS step."));
|
|
35
|
+
throw new Error("DNS records not pasted; aborted by operator.");
|
|
36
|
+
}
|
|
37
|
+
const s = spinner();
|
|
38
|
+
s.start(`Polling DNS — up to 30 min while propagation + records settle...`);
|
|
39
|
+
const start = Date.now();
|
|
40
|
+
const remaining = new Set(records.map((r) => recordKey(r)));
|
|
41
|
+
while (remaining.size > 0) {
|
|
42
|
+
if (Date.now() - start > POLL_TIMEOUT_MS) {
|
|
43
|
+
s.stop(red("DNS verify timeout (30 min)."));
|
|
44
|
+
throw new Error(`Records still unresolved after 30 min: ${[...remaining].join(", ")}. Re-run after fixing.`);
|
|
45
|
+
}
|
|
46
|
+
for (const key of [...remaining]) {
|
|
47
|
+
const record = records.find((r) => recordKey(r) === key);
|
|
48
|
+
if (!record) {
|
|
49
|
+
remaining.delete(key);
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (await checkRecord(record)) {
|
|
53
|
+
remaining.delete(key);
|
|
54
|
+
log.success(green(`✓ ${record.type} ${record.hostname} resolves to expected ${record.value}`));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (remaining.size > 0)
|
|
58
|
+
await sleep(POLL_INTERVAL_MS);
|
|
59
|
+
}
|
|
60
|
+
s.stop(green("All DNS records resolve."));
|
|
61
|
+
void cyan;
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function recordKey(r) {
|
|
66
|
+
return `${r.type}:${r.hostname}`;
|
|
67
|
+
}
|
|
68
|
+
async function checkRecord(record) {
|
|
69
|
+
try {
|
|
70
|
+
if (record.type === "A") {
|
|
71
|
+
const result = await dns.resolve4(record.hostname);
|
|
72
|
+
return result.includes(record.value);
|
|
73
|
+
}
|
|
74
|
+
if (record.type === "AAAA") {
|
|
75
|
+
const result = await dns.resolve6(record.hostname);
|
|
76
|
+
return result.includes(record.value);
|
|
77
|
+
}
|
|
78
|
+
if (record.type === "CNAME") {
|
|
79
|
+
const result = await dns.resolveCname(record.hostname);
|
|
80
|
+
// CNAME values often have a trailing dot; normalize
|
|
81
|
+
const expected = record.value.replace(/\.$/, "");
|
|
82
|
+
return result.some((v) => v.replace(/\.$/, "") === expected);
|
|
83
|
+
}
|
|
84
|
+
if (record.type === "TXT") {
|
|
85
|
+
const result = await dns.resolveTxt(record.hostname);
|
|
86
|
+
const expected = record.value.replace(/^"|"$/g, "");
|
|
87
|
+
return result.some((arr) => arr.join("") === expected);
|
|
88
|
+
}
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// ENOTFOUND / SERVFAIL — record not propagated yet
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=manual.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manual.js","sourceRoot":"","sources":["../../src/dns/manual.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared DNS-adapter interface. Every adapter (Cloudflare / Route53 /
|
|
3
|
+
* Cloud DNS / manual) implements `applyRecords` end-to-end: takes the
|
|
4
|
+
* desired records, makes them real (via API or operator paste), and
|
|
5
|
+
* resolves only when public DNS lookups succeed.
|
|
6
|
+
*/
|
|
7
|
+
export interface DnsRecord {
|
|
8
|
+
hostname: string;
|
|
9
|
+
type: "A" | "AAAA" | "CNAME" | "TXT";
|
|
10
|
+
value: string;
|
|
11
|
+
ttl?: number;
|
|
12
|
+
}
|
|
13
|
+
export interface DnsAdapter {
|
|
14
|
+
/** Human-readable name shown in wizard output. */
|
|
15
|
+
readonly name: string;
|
|
16
|
+
/**
|
|
17
|
+
* Make the records exist + verify they resolve. Resolves when
|
|
18
|
+
* `dig <hostname> <type>` returns the expected value globally.
|
|
19
|
+
* Throws on terminal failure (operator should fix + re-run).
|
|
20
|
+
*/
|
|
21
|
+
applyRecords(records: DnsRecord[]): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/dns/types.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AAEH,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,GAAG,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,UAAU;IACzB,kDAAkD;IAClD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB;;;;OAIG;IACH,YAAY,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/dns/types.ts"],"names":[],"mappings":"AAAA,mCAAmC"}
|
package/dist/gcloud.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export interface GcloudResult {
|
|
2
|
+
ok: boolean;
|
|
3
|
+
stdout: string;
|
|
4
|
+
stderr: string;
|
|
5
|
+
exitCode: number;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Run a gcloud command. Buffered stdout + stderr (max 4 MB each).
|
|
9
|
+
* Throws on spawn failure but NOT on non-zero exit — caller checks `ok`.
|
|
10
|
+
*/
|
|
11
|
+
export declare function gcloud(args: string[], opts?: {
|
|
12
|
+
stdin?: string;
|
|
13
|
+
}): Promise<GcloudResult>;
|
|
14
|
+
/**
|
|
15
|
+
* Active gcloud account. Returns `null` when no account is logged in
|
|
16
|
+
* — caller prompts `gcloud auth login`.
|
|
17
|
+
*/
|
|
18
|
+
export declare function activeAccount(): Promise<string | null>;
|
|
19
|
+
export interface BillingAccount {
|
|
20
|
+
id: string;
|
|
21
|
+
displayName: string;
|
|
22
|
+
open: boolean;
|
|
23
|
+
}
|
|
24
|
+
export declare function listBillingAccounts(): Promise<BillingAccount[]>;
|
|
25
|
+
export declare function projectExists(projectId: string): Promise<boolean>;
|
|
26
|
+
export declare function createProject(projectId: string, displayName: string): Promise<GcloudResult>;
|
|
27
|
+
export declare function linkBilling(projectId: string, billingAccountId: string): Promise<GcloudResult>;
|
|
28
|
+
export declare function enableApis(projectId: string): Promise<GcloudResult>;
|
|
29
|
+
export declare function serviceAccountExists(projectId: string, saEmail: string): Promise<boolean>;
|
|
30
|
+
export declare function createServiceAccount(projectId: string, accountId: string, displayName: string): Promise<GcloudResult>;
|
|
31
|
+
/**
|
|
32
|
+
* Bind every role the GCP stack provisioner SA needs. Idempotent —
|
|
33
|
+
* gcloud silently no-ops a binding that already exists.
|
|
34
|
+
*/
|
|
35
|
+
export declare function grantProvisionerRoles(projectId: string, saEmail: string): Promise<{
|
|
36
|
+
granted: number;
|
|
37
|
+
failed: string[];
|
|
38
|
+
}>;
|
|
39
|
+
export declare function createServiceAccountKey(saEmail: string, outputPath: string): Promise<GcloudResult>;
|
|
40
|
+
export declare const REQUIRED_API_LIST: readonly string[];
|
|
41
|
+
export declare const PROVISIONER_ROLE_LIST: readonly string[];
|
|
42
|
+
//# sourceMappingURL=gcloud.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gcloud.d.ts","sourceRoot":"","sources":["../src/gcloud.ts"],"names":[],"mappings":"AAkBA,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,OAAO,CAAC,YAAY,CAAC,CAqBjG;AAED;;;GAGG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAK5D;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,OAAO,CAAC;CACf;AAED,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC,CAiBrE;AAED,wBAAsB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAGvE;AAED,wBAAsB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAEjG;AAED,wBAAsB,WAAW,CAC/B,SAAS,EAAE,MAAM,EACjB,gBAAgB,EAAE,MAAM,GACvB,OAAO,CAAC,YAAY,CAAC,CAEvB;AAqBD,wBAAsB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAEzE;AAED,wBAAsB,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAW/F;AAED,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,YAAY,CAAC,CAWvB;AA6BD;;;GAGG;AACH,wBAAsB,qBAAqB,CACzC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAmBhD;AAED,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,YAAY,CAAC,CAUvB;AAED,eAAO,MAAM,iBAAiB,mBAAgB,CAAC;AAC/C,eAAO,MAAM,qBAAqB,mBAAoB,CAAC"}
|
package/dist/gcloud.js
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
/**
|
|
3
|
+
* Thin gcloud shell-out wrapper. The wizard composes high-level
|
|
4
|
+
* "create the project + link billing + enable APIs + create SA +
|
|
5
|
+
* grant roles + mint key" flows on top of these primitives.
|
|
6
|
+
*
|
|
7
|
+
* Why shell out instead of using the GCP SDK directly:
|
|
8
|
+
* - the user's gcloud auth state IS the auth (no separate
|
|
9
|
+
* credentials handling — `gcloud` already knows the user)
|
|
10
|
+
* - project create + billing link must happen as the user, NOT as
|
|
11
|
+
* a service account (the SA doesn't exist yet at bootstrap time)
|
|
12
|
+
* - error messages from gcloud are operator-readable; the SDK's
|
|
13
|
+
* gRPC errors aren't
|
|
14
|
+
*/
|
|
15
|
+
import { spawn } from "node:child_process";
|
|
16
|
+
/**
|
|
17
|
+
* Run a gcloud command. Buffered stdout + stderr (max 4 MB each).
|
|
18
|
+
* Throws on spawn failure but NOT on non-zero exit — caller checks `ok`.
|
|
19
|
+
*/
|
|
20
|
+
export async function gcloud(args, opts = {}) {
|
|
21
|
+
return new Promise((resolveResult, reject) => {
|
|
22
|
+
const child = spawn("gcloud", args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
23
|
+
const stdout = [];
|
|
24
|
+
const stderr = [];
|
|
25
|
+
child.stdout.on("data", (chunk) => stdout.push(chunk));
|
|
26
|
+
child.stderr.on("data", (chunk) => stderr.push(chunk));
|
|
27
|
+
child.on("error", (e) => reject(e));
|
|
28
|
+
child.on("close", (code) => resolveResult({
|
|
29
|
+
ok: code === 0,
|
|
30
|
+
stdout: Buffer.concat(stdout).toString("utf8"),
|
|
31
|
+
stderr: Buffer.concat(stderr).toString("utf8"),
|
|
32
|
+
exitCode: code ?? 1,
|
|
33
|
+
}));
|
|
34
|
+
if (opts.stdin !== undefined) {
|
|
35
|
+
child.stdin.write(opts.stdin);
|
|
36
|
+
child.stdin.end();
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Active gcloud account. Returns `null` when no account is logged in
|
|
42
|
+
* — caller prompts `gcloud auth login`.
|
|
43
|
+
*/
|
|
44
|
+
export async function activeAccount() {
|
|
45
|
+
const r = await gcloud(["auth", "list", "--format=value(account)", "--filter=status:ACTIVE"]);
|
|
46
|
+
if (!r.ok)
|
|
47
|
+
return null;
|
|
48
|
+
const value = r.stdout.trim();
|
|
49
|
+
return value.length > 0 ? value : null;
|
|
50
|
+
}
|
|
51
|
+
export async function listBillingAccounts() {
|
|
52
|
+
const r = await gcloud(["billing", "accounts", "list", "--format=json"]);
|
|
53
|
+
if (!r.ok)
|
|
54
|
+
return [];
|
|
55
|
+
try {
|
|
56
|
+
const rows = JSON.parse(r.stdout);
|
|
57
|
+
return rows.map((row) => ({
|
|
58
|
+
id: row.name.replace(/^billingAccounts\//, ""),
|
|
59
|
+
displayName: row.displayName,
|
|
60
|
+
open: row.open,
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
export async function projectExists(projectId) {
|
|
68
|
+
const r = await gcloud(["projects", "describe", projectId, "--format=value(projectId)"]);
|
|
69
|
+
return r.ok && r.stdout.trim() === projectId;
|
|
70
|
+
}
|
|
71
|
+
export async function createProject(projectId, displayName) {
|
|
72
|
+
return gcloud(["projects", "create", projectId, "--name", displayName]);
|
|
73
|
+
}
|
|
74
|
+
export async function linkBilling(projectId, billingAccountId) {
|
|
75
|
+
return gcloud(["billing", "projects", "link", projectId, "--billing-account", billingAccountId]);
|
|
76
|
+
}
|
|
77
|
+
const REQUIRED_APIS = [
|
|
78
|
+
"compute.googleapis.com",
|
|
79
|
+
"sqladmin.googleapis.com",
|
|
80
|
+
"run.googleapis.com",
|
|
81
|
+
"secretmanager.googleapis.com",
|
|
82
|
+
"servicenetworking.googleapis.com",
|
|
83
|
+
"dns.googleapis.com",
|
|
84
|
+
"storage.googleapis.com",
|
|
85
|
+
"cloudresourcemanager.googleapis.com",
|
|
86
|
+
"iam.googleapis.com",
|
|
87
|
+
"cloudbuild.googleapis.com",
|
|
88
|
+
"artifactregistry.googleapis.com",
|
|
89
|
+
"bigquery.googleapis.com",
|
|
90
|
+
"iap.googleapis.com",
|
|
91
|
+
// Used by gcp.projects.ServiceIdentity to provision the IAP-managed
|
|
92
|
+
// service account that forwards authenticated requests to Cloud Run.
|
|
93
|
+
"serviceusage.googleapis.com",
|
|
94
|
+
];
|
|
95
|
+
export async function enableApis(projectId) {
|
|
96
|
+
return gcloud(["services", "enable", ...REQUIRED_APIS, "--project", projectId]);
|
|
97
|
+
}
|
|
98
|
+
export async function serviceAccountExists(projectId, saEmail) {
|
|
99
|
+
const r = await gcloud([
|
|
100
|
+
"iam",
|
|
101
|
+
"service-accounts",
|
|
102
|
+
"describe",
|
|
103
|
+
saEmail,
|
|
104
|
+
"--project",
|
|
105
|
+
projectId,
|
|
106
|
+
"--format=value(email)",
|
|
107
|
+
]);
|
|
108
|
+
return r.ok && r.stdout.trim() === saEmail;
|
|
109
|
+
}
|
|
110
|
+
export async function createServiceAccount(projectId, accountId, displayName) {
|
|
111
|
+
return gcloud([
|
|
112
|
+
"iam",
|
|
113
|
+
"service-accounts",
|
|
114
|
+
"create",
|
|
115
|
+
accountId,
|
|
116
|
+
"--display-name",
|
|
117
|
+
displayName,
|
|
118
|
+
"--project",
|
|
119
|
+
projectId,
|
|
120
|
+
]);
|
|
121
|
+
}
|
|
122
|
+
const PROVISIONER_ROLES = [
|
|
123
|
+
"roles/run.admin",
|
|
124
|
+
"roles/cloudsql.admin",
|
|
125
|
+
"roles/storage.admin",
|
|
126
|
+
"roles/secretmanager.admin",
|
|
127
|
+
"roles/iam.serviceAccountUser",
|
|
128
|
+
"roles/compute.networkAdmin",
|
|
129
|
+
"roles/dns.admin",
|
|
130
|
+
"roles/servicenetworking.networksAdmin",
|
|
131
|
+
"roles/iam.serviceAccountTokenCreator",
|
|
132
|
+
"roles/cloudbuild.builds.editor",
|
|
133
|
+
"roles/artifactregistry.admin",
|
|
134
|
+
"roles/compute.securityAdmin",
|
|
135
|
+
"roles/iam.serviceAccountAdmin",
|
|
136
|
+
"roles/bigquery.admin",
|
|
137
|
+
"roles/iap.admin",
|
|
138
|
+
// compute.admin includes RegionNetworkEndpointGroups + URL maps +
|
|
139
|
+
// BackendService variants the LB needs. compute.networkAdmin alone
|
|
140
|
+
// doesn't cover NEG create.
|
|
141
|
+
"roles/compute.admin",
|
|
142
|
+
// logging.configWriter creates ProjectSink (BigQuery edge logs).
|
|
143
|
+
"roles/logging.configWriter",
|
|
144
|
+
// serviceusage.serviceUsageAdmin lets us trigger the IAP managed
|
|
145
|
+
// service identity (gcp.projects.ServiceIdentity).
|
|
146
|
+
"roles/serviceusage.serviceUsageAdmin",
|
|
147
|
+
];
|
|
148
|
+
/**
|
|
149
|
+
* Bind every role the GCP stack provisioner SA needs. Idempotent —
|
|
150
|
+
* gcloud silently no-ops a binding that already exists.
|
|
151
|
+
*/
|
|
152
|
+
export async function grantProvisionerRoles(projectId, saEmail) {
|
|
153
|
+
let granted = 0;
|
|
154
|
+
const failed = [];
|
|
155
|
+
for (const role of PROVISIONER_ROLES) {
|
|
156
|
+
const r = await gcloud([
|
|
157
|
+
"projects",
|
|
158
|
+
"add-iam-policy-binding",
|
|
159
|
+
projectId,
|
|
160
|
+
"--member",
|
|
161
|
+
`serviceAccount:${saEmail}`,
|
|
162
|
+
"--role",
|
|
163
|
+
role,
|
|
164
|
+
"--condition=None",
|
|
165
|
+
"--quiet",
|
|
166
|
+
]);
|
|
167
|
+
if (r.ok)
|
|
168
|
+
granted++;
|
|
169
|
+
else
|
|
170
|
+
failed.push(role);
|
|
171
|
+
}
|
|
172
|
+
return { granted, failed };
|
|
173
|
+
}
|
|
174
|
+
export async function createServiceAccountKey(saEmail, outputPath) {
|
|
175
|
+
return gcloud([
|
|
176
|
+
"iam",
|
|
177
|
+
"service-accounts",
|
|
178
|
+
"keys",
|
|
179
|
+
"create",
|
|
180
|
+
outputPath,
|
|
181
|
+
"--iam-account",
|
|
182
|
+
saEmail,
|
|
183
|
+
]);
|
|
184
|
+
}
|
|
185
|
+
export const REQUIRED_API_LIST = REQUIRED_APIS;
|
|
186
|
+
export const PROVISIONER_ROLE_LIST = PROVISIONER_ROLES;
|
|
187
|
+
//# sourceMappingURL=gcloud.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gcloud.js","sourceRoot":"","sources":["../src/gcloud.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export type Provider = "self-hosted" | "gcp" | "aws" | "azure";
|
|
2
|
+
export interface InstallMetadata {
|
|
3
|
+
installId: string;
|
|
4
|
+
provider: Provider;
|
|
5
|
+
/** Cloud-side project / account / subscription id. NULL for self-hosted. */
|
|
6
|
+
projectId: string | null;
|
|
7
|
+
domain: string;
|
|
8
|
+
ownerEmail: string;
|
|
9
|
+
region: string | null;
|
|
10
|
+
createdAt: string;
|
|
11
|
+
}
|
|
12
|
+
export interface ProgressCheckpoint {
|
|
13
|
+
/** Last completed wizard step. Used for resume-after-failure. */
|
|
14
|
+
lastCompletedStep: string | null;
|
|
15
|
+
/** Per-step state (e.g. createdProjectId, mintedSaKeyAt, etc.). */
|
|
16
|
+
steps: Record<string, unknown>;
|
|
17
|
+
/** ISO timestamp of last successful update. */
|
|
18
|
+
updatedAt: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function installRoot(installId: string): string;
|
|
21
|
+
/**
|
|
22
|
+
* Stable install id from the (provider, projectId-or-domain) pair so a re-run
|
|
23
|
+
* with the same inputs always lands on the same `~/.caelo-<id>/` directory.
|
|
24
|
+
* The id is short + readable — `gcp-caelo-website` / `self-hosted-mysite-com`.
|
|
25
|
+
*/
|
|
26
|
+
export declare function deriveInstallId(provider: Provider, projectIdOrDomain: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* Ensure the install directory exists with the right mode + sub-dirs.
|
|
29
|
+
* Idempotent.
|
|
30
|
+
*/
|
|
31
|
+
export declare function ensureInstallDir(installId: string): {
|
|
32
|
+
root: string;
|
|
33
|
+
secretsDir: string;
|
|
34
|
+
stateDir: string;
|
|
35
|
+
};
|
|
36
|
+
export declare function readMetadata(installId: string): InstallMetadata | null;
|
|
37
|
+
export declare function writeMetadata(installId: string, meta: InstallMetadata): void;
|
|
38
|
+
export declare function readProgress(installId: string): ProgressCheckpoint;
|
|
39
|
+
export declare function writeProgress(installId: string, checkpoint: ProgressCheckpoint): void;
|
|
40
|
+
/**
|
|
41
|
+
* Mark a step complete. Wizard re-runs check `isStepDone(installId, name)` to
|
|
42
|
+
* skip already-done steps.
|
|
43
|
+
*/
|
|
44
|
+
export declare function markStepDone(installId: string, stepName: string, payload?: unknown): void;
|
|
45
|
+
export declare function isStepDone(installId: string, stepName: string): boolean;
|
|
46
|
+
export declare function getStepPayload<T>(installId: string, stepName: string): T | null;
|
|
47
|
+
/**
|
|
48
|
+
* Read a secret file from the install's `secrets/` dir. Returns null if the
|
|
49
|
+
* file doesn't exist; throws if the file exists but has the wrong mode (a
|
|
50
|
+
* defence against the user copy-pasting a key into a 644 file by mistake).
|
|
51
|
+
*/
|
|
52
|
+
export declare function readSecret(installId: string, name: string): string | null;
|
|
53
|
+
export declare function writeSecret(installId: string, name: string, value: string): void;
|
|
54
|
+
//# sourceMappingURL=install-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install-state.d.ts","sourceRoot":"","sources":["../src/install-state.ts"],"names":[],"mappings":"AAyBA,MAAM,MAAM,QAAQ,GAAG,aAAa,GAAG,KAAK,GAAG,KAAK,GAAG,OAAO,CAAC;AAE/D,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,QAAQ,CAAC;IACnB,4EAA4E;IAC5E,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,kBAAkB;IACjC,iEAAiE;IACjE,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,mEAAmE;IACnE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAC;CACnB;AAID,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAErD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,GAAG,MAAM,CAOrF;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAeA;AAED,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAItE;AAED,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,GAAG,IAAI,CAG5E;AAED,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,kBAAkB,CAMlE;AAED,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,kBAAkB,GAAG,IAAI,CAOrF;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAKzF;AAED,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEvE;AAED,wBAAgB,cAAc,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI,CAG/E;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMzE;AAED,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAIhF"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
/**
|
|
3
|
+
* Per-install state — every Caelo install gets its own
|
|
4
|
+
* `~/.caelo-<install-id>/` directory with:
|
|
5
|
+
*
|
|
6
|
+
* secrets/ — mode-700 dir for the install's secrets
|
|
7
|
+
* anthropic-api-key (mode 600) — Anthropic API key (one-time prompt)
|
|
8
|
+
* pulumi-passphrase (mode 600) — Pulumi local-backend passphrase
|
|
9
|
+
* sa-key.json (mode 600) — GCP SA key (when local-deploy; absent
|
|
10
|
+
* when Workload Identity Federation
|
|
11
|
+
* deploys from CI)
|
|
12
|
+
* state/ — Pulumi local backend state (per-install isolated)
|
|
13
|
+
* progress.json — wizard checkpoint so re-runs resume cleanly
|
|
14
|
+
* install.json — install metadata (provider, project id, region,
|
|
15
|
+
* domain, owner email, install id, created_at)
|
|
16
|
+
*
|
|
17
|
+
* The CLAUDE.md §11.C contract: end-users never reach into this directory
|
|
18
|
+
* by hand. The wizard + lifecycle commands wrap every read + write.
|
|
19
|
+
*/
|
|
20
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
21
|
+
import { homedir } from "node:os";
|
|
22
|
+
import { join } from "node:path";
|
|
23
|
+
const ROOT_PREFIX = ".caelo-";
|
|
24
|
+
export function installRoot(installId) {
|
|
25
|
+
return join(homedir(), `${ROOT_PREFIX}${installId}`);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Stable install id from the (provider, projectId-or-domain) pair so a re-run
|
|
29
|
+
* with the same inputs always lands on the same `~/.caelo-<id>/` directory.
|
|
30
|
+
* The id is short + readable — `gcp-caelo-website` / `self-hosted-mysite-com`.
|
|
31
|
+
*/
|
|
32
|
+
export function deriveInstallId(provider, projectIdOrDomain) {
|
|
33
|
+
const slug = projectIdOrDomain
|
|
34
|
+
.toLowerCase()
|
|
35
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
36
|
+
.replace(/^-+|-+$/g, "")
|
|
37
|
+
.slice(0, 40);
|
|
38
|
+
return `${provider}-${slug}`;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Ensure the install directory exists with the right mode + sub-dirs.
|
|
42
|
+
* Idempotent.
|
|
43
|
+
*/
|
|
44
|
+
export function ensureInstallDir(installId) {
|
|
45
|
+
const root = installRoot(installId);
|
|
46
|
+
const secretsDir = join(root, "secrets");
|
|
47
|
+
const stateDir = join(root, "state");
|
|
48
|
+
if (!existsSync(root))
|
|
49
|
+
mkdirSync(root, { recursive: true, mode: 0o700 });
|
|
50
|
+
if (!existsSync(secretsDir))
|
|
51
|
+
mkdirSync(secretsDir, { recursive: true, mode: 0o700 });
|
|
52
|
+
if (!existsSync(stateDir))
|
|
53
|
+
mkdirSync(stateDir, { recursive: true, mode: 0o700 });
|
|
54
|
+
// chmod every time in case the dirs existed with looser perms.
|
|
55
|
+
chmodSync(root, 0o700);
|
|
56
|
+
chmodSync(secretsDir, 0o700);
|
|
57
|
+
chmodSync(stateDir, 0o700);
|
|
58
|
+
return { root, secretsDir, stateDir };
|
|
59
|
+
}
|
|
60
|
+
export function readMetadata(installId) {
|
|
61
|
+
const path = join(installRoot(installId), "install.json");
|
|
62
|
+
if (!existsSync(path))
|
|
63
|
+
return null;
|
|
64
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
65
|
+
}
|
|
66
|
+
export function writeMetadata(installId, meta) {
|
|
67
|
+
const path = join(installRoot(installId), "install.json");
|
|
68
|
+
writeFileSync(path, `${JSON.stringify(meta, null, 2)}\n`, { mode: 0o600 });
|
|
69
|
+
}
|
|
70
|
+
export function readProgress(installId) {
|
|
71
|
+
const path = join(installRoot(installId), "progress.json");
|
|
72
|
+
if (!existsSync(path)) {
|
|
73
|
+
return { lastCompletedStep: null, steps: {}, updatedAt: new Date().toISOString() };
|
|
74
|
+
}
|
|
75
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
76
|
+
}
|
|
77
|
+
export function writeProgress(installId, checkpoint) {
|
|
78
|
+
const path = join(installRoot(installId), "progress.json");
|
|
79
|
+
writeFileSync(path, `${JSON.stringify({ ...checkpoint, updatedAt: new Date().toISOString() }, null, 2)}\n`, { mode: 0o600 });
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Mark a step complete. Wizard re-runs check `isStepDone(installId, name)` to
|
|
83
|
+
* skip already-done steps.
|
|
84
|
+
*/
|
|
85
|
+
export function markStepDone(installId, stepName, payload) {
|
|
86
|
+
const cur = readProgress(installId);
|
|
87
|
+
cur.lastCompletedStep = stepName;
|
|
88
|
+
if (payload !== undefined)
|
|
89
|
+
cur.steps[stepName] = payload;
|
|
90
|
+
writeProgress(installId, cur);
|
|
91
|
+
}
|
|
92
|
+
export function isStepDone(installId, stepName) {
|
|
93
|
+
return readProgress(installId).steps[stepName] !== undefined;
|
|
94
|
+
}
|
|
95
|
+
export function getStepPayload(installId, stepName) {
|
|
96
|
+
const v = readProgress(installId).steps[stepName];
|
|
97
|
+
return (v ?? null);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Read a secret file from the install's `secrets/` dir. Returns null if the
|
|
101
|
+
* file doesn't exist; throws if the file exists but has the wrong mode (a
|
|
102
|
+
* defence against the user copy-pasting a key into a 644 file by mistake).
|
|
103
|
+
*/
|
|
104
|
+
export function readSecret(installId, name) {
|
|
105
|
+
const path = join(installRoot(installId), "secrets", name);
|
|
106
|
+
if (!existsSync(path))
|
|
107
|
+
return null;
|
|
108
|
+
const contents = readFileSync(path, "utf8").trim();
|
|
109
|
+
if (contents.length === 0)
|
|
110
|
+
return null;
|
|
111
|
+
return contents;
|
|
112
|
+
}
|
|
113
|
+
export function writeSecret(installId, name, value) {
|
|
114
|
+
ensureInstallDir(installId);
|
|
115
|
+
const path = join(installRoot(installId), "secrets", name);
|
|
116
|
+
writeFileSync(path, value.endsWith("\n") ? value : `${value}\n`, { mode: 0o600 });
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=install-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install-state.js","sourceRoot":"","sources":["../src/install-state.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare function statusCommand(): Promise<void>;
|
|
2
|
+
interface UpgradeOpts {
|
|
3
|
+
/** Explicit semver to roll to (e.g. "0.5.3"). Defaults to "latest". */
|
|
4
|
+
readonly version?: string;
|
|
5
|
+
/** Pre-release channel: "stable" (default), "rc", "beta". */
|
|
6
|
+
readonly channel?: "stable" | "rc" | "beta";
|
|
7
|
+
/**
|
|
8
|
+
* P21 ship 4 — escape hatch for forks / staging environments using
|
|
9
|
+
* unsigned images. Default = verify with cosign; refuse to roll on
|
|
10
|
+
* mismatch.
|
|
11
|
+
*/
|
|
12
|
+
readonly skipVerify?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare function upgradeCommand(opts?: UpgradeOpts): Promise<void>;
|
|
15
|
+
export declare function backupCommand(): Promise<void>;
|
|
16
|
+
export declare function rotateSecretCommand(name: string | undefined): Promise<void>;
|
|
17
|
+
export declare function destroyCommand(): Promise<void>;
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=lifecycle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../src/lifecycle.ts"],"names":[],"mappings":"AAyGA,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAWnD;AA0GD,UAAU,WAAW;IACnB,uEAAuE;IACvE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,6DAA6D;IAC7D,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,GAAG,IAAI,GAAG,MAAM,CAAC;IAC5C;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;CAC/B;AAqGD,wBAAsB,cAAc,CAAC,IAAI,GAAE,WAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsL1E;AAwGD,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAmCnD;AAMD,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAoCjF;AAMD,wBAAsB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CA6CpD"}
|