@caelo-cms/provisioning 0.1.0 → 0.1.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/adapter.d.ts +95 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +3 -0
- package/dist/adapter.js.map +1 -0
- package/dist/bootstrap-token.d.ts +11 -0
- package/dist/bootstrap-token.d.ts.map +1 -0
- package/dist/bootstrap-token.js +9 -0
- package/dist/bootstrap-token.js.map +1 -0
- package/dist/caddy.d.ts +34 -0
- package/dist/caddy.d.ts.map +1 -0
- package/dist/caddy.js +53 -0
- package/dist/caddy.js.map +1 -0
- package/{src/cdn-copy.ts → dist/cdn-copy.d.ts} +11 -42
- package/dist/cdn-copy.d.ts.map +1 -0
- package/dist/cdn-copy.js +48 -0
- package/dist/cdn-copy.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +585 -0
- package/dist/cli.js.map +1 -0
- package/dist/compose.d.ts +20 -0
- package/dist/compose.d.ts.map +1 -0
- package/{src/compose.ts → dist/compose.js} +7 -28
- package/dist/compose.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/redirects-emit.d.ts +65 -0
- package/dist/redirects-emit.d.ts.map +1 -0
- package/dist/redirects-emit.js +89 -0
- package/dist/redirects-emit.js.map +1 -0
- package/package.json +22 -6
- package/src/adapter.ts +0 -103
- package/src/bootstrap-token.ts +0 -20
- package/src/caddy.ts +0 -93
- package/src/cli.ts +0 -674
- package/src/index.test.ts +0 -246
- package/src/index.ts +0 -52
- package/src/redirects-emit.ts +0 -166
- package/tsconfig.json +0 -16
|
@@ -1,28 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
*
|
|
6
|
-
* Emits the production stack: Postgres + pgBackRest sidecar + Caddy +
|
|
7
|
-
* MinIO + four Caelo services (admin, gateway, orchestrator, runner).
|
|
8
|
-
* Idempotent: same input → byte-identical output. The CLI's `up`
|
|
9
|
-
* sub-command writes this to `<repo>/.caelo/docker-compose.yml` and
|
|
10
|
-
* runs `docker compose up -d`.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
export interface ComposeSpec {
|
|
14
|
-
readonly domain: string;
|
|
15
|
-
readonly postgresPassword: string; // generated by cms-provision; persisted in .caelo/secrets
|
|
16
|
-
readonly minioRootUser: string;
|
|
17
|
-
readonly minioRootPassword: string;
|
|
18
|
-
readonly anthropicApiKey?: string;
|
|
19
|
-
readonly resendApiKey?: string;
|
|
20
|
-
readonly diskSize: string;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function generateDockerCompose(spec: ComposeSpec): string {
|
|
24
|
-
const env = (k: string, v: string | undefined): string => (v ? ` ${k}: "${escape(v)}"` : "");
|
|
25
|
-
return `# SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
export function generateDockerCompose(spec) {
|
|
3
|
+
const env = (k, v) => (v ? ` ${k}: "${escape(v)}"` : "");
|
|
4
|
+
return `# SPDX-License-Identifier: MPL-2.0
|
|
26
5
|
# Generated by cms-provision — do not edit; re-run \`bunx cms-provision up\`.
|
|
27
6
|
# Domain: ${spec.domain}
|
|
28
7
|
|
|
@@ -116,8 +95,8 @@ volumes:
|
|
|
116
95
|
caelo-caddy-config:
|
|
117
96
|
`;
|
|
118
97
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
98
|
+
function escape(s) {
|
|
99
|
+
// Conservative escape for double-quoted YAML strings.
|
|
100
|
+
return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
123
101
|
}
|
|
102
|
+
//# sourceMappingURL=compose.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compose.js","sourceRoot":"","sources":["../src/compose.ts"],"names":[],"mappings":"AAAA,mCAAmC;AAsBnC,MAAM,UAAU,qBAAqB,CAAC,IAAiB;IACrD,MAAM,GAAG,GAAG,CAAC,CAAS,EAAE,CAAqB,EAAU,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAClG,OAAO;;YAEG,IAAI,CAAC,MAAM;;;;;;;;4BAQK,MAAM,CAAC,IAAI,CAAC,gBAAgB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;0BA2B/B,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC;8BACtB,MAAM,CAAC,IAAI,CAAC,iBAAiB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;8CA0Bd,MAAM,CAAC,IAAI,CAAC,gBAAgB,CAAC;qDACtB,MAAM,CAAC,IAAI,CAAC,gBAAgB,CAAC;;EAEhF,GAAG,CAAC,mBAAmB,EAAE,IAAI,CAAC,eAAe,CAAC;EAC9C,GAAG,CAAC,gBAAgB,EAAE,IAAI,CAAC,YAAY,CAAC;;;;;;;;;;;;;8CAaI,MAAM,CAAC,IAAI,CAAC,gBAAgB,CAAC;+CAC5B,MAAM,CAAC,IAAI,CAAC,gBAAgB,CAAC;;;;;;;;;;CAU3E,CAAC;AACF,CAAC;AAED,SAAS,MAAM,CAAC,CAAS;IACvB,sDAAsD;IACtD,OAAO,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AACvD,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @caelo-cms/provisioning — P14.
|
|
3
|
+
*
|
|
4
|
+
* Pulumi-driven self-hosted stack + cms-provision CLI helpers.
|
|
5
|
+
*
|
|
6
|
+
* Public surface:
|
|
7
|
+
* - generateCaddyfile(spec) → string
|
|
8
|
+
* - generateDockerCompose(spec) → string
|
|
9
|
+
* - generateBootstrapToken() → { token, expiresAt }
|
|
10
|
+
*
|
|
11
|
+
* The CLI (cli.ts) wires these into init / up / regenerate-caddy /
|
|
12
|
+
* backup / restore / status sub-commands. The Pulumi stack files
|
|
13
|
+
* (stacks/self-hosted/*) are imported by the CLI's `up` path and
|
|
14
|
+
* declare the actual Docker resources.
|
|
15
|
+
*/
|
|
16
|
+
export type { CloudAdapterInputs, CloudAdapterOutputs, DnsRecord, Environment, LocaleConfig, LocaleStrategy, ProvisioningOutputsJson, SupportedProvider, } from "./adapter.js";
|
|
17
|
+
export { type BootstrapToken, generateBootstrapToken, } from "./bootstrap-token.js";
|
|
18
|
+
export { type CaddyDomainSpec, type CaddyfileSpec, generateCaddyfile, } from "./caddy.js";
|
|
19
|
+
export { type CdnCopyAdapter, loadCdnCopyAdapter, selfHostedCdnCopy, } from "./cdn-copy.js";
|
|
20
|
+
export { type ComposeSpec, generateDockerCompose } from "./compose.js";
|
|
21
|
+
export { type CloudFrontRedirectArtifact, emitRedirectsAzureFrontDoor, emitRedirectsCloudFront, emitRedirectsCloudflare, type FrontDoorRule, type RedirectRow, type RedirectStatusCode, } from "./redirects-emit.js";
|
|
22
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;GAcG;AAEH,YAAY,EACV,kBAAkB,EAClB,mBAAmB,EACnB,SAAS,EACT,WAAW,EACX,YAAY,EACZ,cAAc,EACd,uBAAuB,EACvB,iBAAiB,GAClB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,KAAK,cAAc,EACnB,sBAAsB,GACvB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,iBAAiB,GAClB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,KAAK,cAAc,EACnB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,KAAK,WAAW,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACvE,OAAO,EACL,KAAK,0BAA0B,EAC/B,2BAA2B,EAC3B,uBAAuB,EACvB,uBAAuB,EACvB,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,kBAAkB,GACxB,MAAM,qBAAqB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
export { generateBootstrapToken, } from "./bootstrap-token.js";
|
|
3
|
+
export { generateCaddyfile, } from "./caddy.js";
|
|
4
|
+
export { loadCdnCopyAdapter, selfHostedCdnCopy, } from "./cdn-copy.js";
|
|
5
|
+
export { generateDockerCompose } from "./compose.js";
|
|
6
|
+
export { emitRedirectsAzureFrontDoor, emitRedirectsCloudFront, emitRedirectsCloudflare, } from "./redirects-emit.js";
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,mCAAmC;AA4BnC,OAAO,EAEL,sBAAsB,GACvB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAGL,iBAAiB,GAClB,MAAM,YAAY,CAAC;AACpB,OAAO,EAEL,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAoB,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACvE,OAAO,EAEL,2BAA2B,EAC3B,uBAAuB,EACvB,uBAAuB,GAIxB,MAAM,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* P15 — per-provider redirect file generators.
|
|
3
|
+
*
|
|
4
|
+
* P14's self-hosted Caddy consumes a `_redirects.caddy` file. Cloud
|
|
5
|
+
* providers want different formats: Cloudflare Pages reads `_redirects`
|
|
6
|
+
* (also a fine fit for any platform that consumes that format),
|
|
7
|
+
* CloudFront wants a JSON config a Lambda@Edge function reads at
|
|
8
|
+
* startup, and Azure Front Door wants a rules-engine RuleEngineRule[].
|
|
9
|
+
*
|
|
10
|
+
* The deploy step calls the right emitter based on `process.env.CAELO_PROVIDER`
|
|
11
|
+
* and uploads the result to the right place. Each emitter is pure +
|
|
12
|
+
* idempotent — same input → same byte output.
|
|
13
|
+
*/
|
|
14
|
+
export type RedirectStatusCode = 301 | 302 | 307 | 308;
|
|
15
|
+
export interface RedirectRow {
|
|
16
|
+
readonly fromPath: string;
|
|
17
|
+
readonly toPath: string;
|
|
18
|
+
readonly statusCode: RedirectStatusCode;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Cloudflare Pages `_redirects` format (also consumed by Netlify, Vercel,
|
|
22
|
+
* Render, etc.). One redirect per line: `<from> <to> <status>`. Wildcards
|
|
23
|
+
* use `:splat`; we only support exact-path redirects in v1 (matches what
|
|
24
|
+
* Caelo's redirects table allows).
|
|
25
|
+
*/
|
|
26
|
+
export declare function emitRedirectsCloudflare(rows: ReadonlyArray<RedirectRow>): string;
|
|
27
|
+
/**
|
|
28
|
+
* CloudFront via Lambda@Edge. Returns a JSON config the L@E function
|
|
29
|
+
* reads at startup + a tiny Lambda source that consumes it. The L@E
|
|
30
|
+
* function is deployed to `us-east-1` (CloudFront constraint); the
|
|
31
|
+
* config blob is bundled into the Lambda deployment artifact.
|
|
32
|
+
*
|
|
33
|
+
* Returning both halves lets the AWS stack ship a single asset.
|
|
34
|
+
*/
|
|
35
|
+
export interface CloudFrontRedirectArtifact {
|
|
36
|
+
readonly jsonConfig: string;
|
|
37
|
+
readonly lambdaSource: string;
|
|
38
|
+
}
|
|
39
|
+
export declare function emitRedirectsCloudFront(rows: ReadonlyArray<RedirectRow>): CloudFrontRedirectArtifact;
|
|
40
|
+
/**
|
|
41
|
+
* Azure Front Door rules engine. Returns a Pulumi-compatible
|
|
42
|
+
* RuleEngineRule[]-shaped array (untyped here so callers don't need to
|
|
43
|
+
* import @pulumi/azure-native). Each rule fires on a path-equals match
|
|
44
|
+
* + executes a 301/302/etc. response.
|
|
45
|
+
*/
|
|
46
|
+
export interface FrontDoorRule {
|
|
47
|
+
readonly name: string;
|
|
48
|
+
readonly order: number;
|
|
49
|
+
readonly conditions: ReadonlyArray<{
|
|
50
|
+
readonly name: "RequestUri";
|
|
51
|
+
readonly parameters: {
|
|
52
|
+
readonly operator: "Equal";
|
|
53
|
+
readonly matchValues: ReadonlyArray<string>;
|
|
54
|
+
};
|
|
55
|
+
}>;
|
|
56
|
+
readonly actions: ReadonlyArray<{
|
|
57
|
+
readonly name: "UrlRedirect";
|
|
58
|
+
readonly parameters: {
|
|
59
|
+
readonly redirectType: "Moved" | "Found" | "TemporaryRedirect" | "PermanentRedirect";
|
|
60
|
+
readonly destinationPath: string;
|
|
61
|
+
};
|
|
62
|
+
}>;
|
|
63
|
+
}
|
|
64
|
+
export declare function emitRedirectsAzureFrontDoor(rows: ReadonlyArray<RedirectRow>): ReadonlyArray<FrontDoorRule>;
|
|
65
|
+
//# sourceMappingURL=redirects-emit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redirects-emit.d.ts","sourceRoot":"","sources":["../src/redirects-emit.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;GAYG;AAEH,MAAM,MAAM,kBAAkB,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AAEvD,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,kBAAkB,CAAC;CACzC;AA0BD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,aAAa,CAAC,WAAW,CAAC,GAAG,MAAM,CAKhF;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,aAAa,CAAC,WAAW,CAAC,GAC/B,0BAA0B,CAwB5B;AAED;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAC;QACjC,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;QAC5B,QAAQ,CAAC,UAAU,EAAE;YACnB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;YAC3B,QAAQ,CAAC,WAAW,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;SAC7C,CAAC;KACH,CAAC,CAAC;IACH,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;QAC9B,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;QAC7B,QAAQ,CAAC,UAAU,EAAE;YACnB,QAAQ,CAAC,YAAY,EAAE,OAAO,GAAG,OAAO,GAAG,mBAAmB,GAAG,mBAAmB,CAAC;YACrF,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;SAClC,CAAC;KACH,CAAC,CAAC;CACJ;AAED,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,aAAa,CAAC,WAAW,CAAC,GAC/B,aAAa,CAAC,aAAa,CAAC,CAqB9B"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
/**
|
|
3
|
+
* Reject whitespace + control chars in either path before any emitter
|
|
4
|
+
* touches them. The Cloudflare `_redirects` format is space-delimited;
|
|
5
|
+
* a `fromPath` containing a space ambiguates the line (CF parses
|
|
6
|
+
* `/old foo /new 301` as from=`/old`, to=`foo`, status=`/new`, then
|
|
7
|
+
* fails on `301`). Caelo's redirects table normally rejects these
|
|
8
|
+
* upstream but the emitter shouldn't trust that — pure functions
|
|
9
|
+
* defend their own contract.
|
|
10
|
+
*/
|
|
11
|
+
function assertPathClean(label, path) {
|
|
12
|
+
if (/[\s\x00-\x1f]/.test(path)) {
|
|
13
|
+
throw new Error(`redirects-emit: ${label}=${JSON.stringify(path)} contains whitespace or control chars; reject upstream`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function assertRowsClean(rows) {
|
|
17
|
+
for (const r of rows) {
|
|
18
|
+
assertPathClean("fromPath", r.fromPath);
|
|
19
|
+
assertPathClean("toPath", r.toPath);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Cloudflare Pages `_redirects` format (also consumed by Netlify, Vercel,
|
|
24
|
+
* Render, etc.). One redirect per line: `<from> <to> <status>`. Wildcards
|
|
25
|
+
* use `:splat`; we only support exact-path redirects in v1 (matches what
|
|
26
|
+
* Caelo's redirects table allows).
|
|
27
|
+
*/
|
|
28
|
+
export function emitRedirectsCloudflare(rows) {
|
|
29
|
+
assertRowsClean(rows);
|
|
30
|
+
const header = "# Generated by @caelo-cms/provisioning — do not edit by hand.\n";
|
|
31
|
+
const lines = rows.map((r) => `${r.fromPath} ${r.toPath} ${r.statusCode}`);
|
|
32
|
+
return header + lines.join("\n") + (rows.length > 0 ? "\n" : "");
|
|
33
|
+
}
|
|
34
|
+
export function emitRedirectsCloudFront(rows) {
|
|
35
|
+
assertRowsClean(rows);
|
|
36
|
+
const jsonConfig = JSON.stringify({ redirects: rows.map((r) => ({ from: r.fromPath, to: r.toPath, status: r.statusCode })) }, null, 2);
|
|
37
|
+
// Tiny Lambda@Edge handler. v1 is exact-path match (matches Caelo's
|
|
38
|
+
// redirects table). Wildcard support lands when the table grows it.
|
|
39
|
+
const lambdaSource = `// SPDX-License-Identifier: MPL-2.0
|
|
40
|
+
// Generated by @caelo-cms/provisioning. Bundled with redirects.json.
|
|
41
|
+
const REDIRECTS = require("./redirects.json").redirects;
|
|
42
|
+
const TABLE = new Map(REDIRECTS.map((r) => [r.from, r]));
|
|
43
|
+
exports.handler = (event, _ctx, callback) => {
|
|
44
|
+
const req = event.Records[0].cf.request;
|
|
45
|
+
const m = TABLE.get(req.uri);
|
|
46
|
+
if (!m) return callback(null, req);
|
|
47
|
+
callback(null, {
|
|
48
|
+
status: String(m.status),
|
|
49
|
+
statusDescription: "Redirect",
|
|
50
|
+
headers: { location: [{ key: "Location", value: m.to }] },
|
|
51
|
+
});
|
|
52
|
+
};`;
|
|
53
|
+
return { jsonConfig, lambdaSource };
|
|
54
|
+
}
|
|
55
|
+
export function emitRedirectsAzureFrontDoor(rows) {
|
|
56
|
+
assertRowsClean(rows);
|
|
57
|
+
return rows.map((r, i) => ({
|
|
58
|
+
name: `redirect-${i + 1}`,
|
|
59
|
+
order: i + 1,
|
|
60
|
+
conditions: [
|
|
61
|
+
{
|
|
62
|
+
name: "RequestUri",
|
|
63
|
+
parameters: { operator: "Equal", matchValues: [r.fromPath] },
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
actions: [
|
|
67
|
+
{
|
|
68
|
+
name: "UrlRedirect",
|
|
69
|
+
parameters: {
|
|
70
|
+
redirectType: statusToFrontDoor(r.statusCode),
|
|
71
|
+
destinationPath: r.toPath,
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
}));
|
|
76
|
+
}
|
|
77
|
+
function statusToFrontDoor(status) {
|
|
78
|
+
switch (status) {
|
|
79
|
+
case 301:
|
|
80
|
+
return "Moved";
|
|
81
|
+
case 302:
|
|
82
|
+
return "Found";
|
|
83
|
+
case 307:
|
|
84
|
+
return "TemporaryRedirect";
|
|
85
|
+
case 308:
|
|
86
|
+
return "PermanentRedirect";
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=redirects-emit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redirects-emit.js","sourceRoot":"","sources":["../src/redirects-emit.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,20 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@caelo-cms/provisioning",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "MPL-2.0",
|
|
6
6
|
"description": "Pulumi-driven provisioning for Caelo CMS — `bunx @caelo-cms/provisioning --provider <self-hosted|gcp|aws|azure>` brings up a TLS-served install with Postgres + storage + secrets manager + the admin + the API gateway.",
|
|
7
7
|
"type": "module",
|
|
8
|
-
"main": "./
|
|
9
|
-
"types": "./
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
10
|
"bin": {
|
|
11
|
-
"cms-provision": "./
|
|
11
|
+
"cms-provision": "./dist/cli.js"
|
|
12
12
|
},
|
|
13
13
|
"exports": {
|
|
14
|
-
".":
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"import": "./dist/index.js",
|
|
17
|
+
"default": "./dist/index.js"
|
|
18
|
+
}
|
|
15
19
|
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist/",
|
|
22
|
+
"stacks/",
|
|
23
|
+
"caddy/",
|
|
24
|
+
"README.md",
|
|
25
|
+
"package.json"
|
|
26
|
+
],
|
|
16
27
|
"scripts": {
|
|
17
|
-
"typecheck": "tsc -b"
|
|
28
|
+
"typecheck": "tsc -b",
|
|
29
|
+
"build": "tsc -b --force",
|
|
30
|
+
"prepublishOnly": "tsc -b --force"
|
|
18
31
|
},
|
|
19
32
|
"dependencies": {
|
|
20
33
|
"@pulumi/aws": "^7",
|
|
@@ -23,5 +36,8 @@
|
|
|
23
36
|
"@pulumi/command": "^1",
|
|
24
37
|
"@pulumi/gcp": "^9",
|
|
25
38
|
"@pulumi/pulumi": "^3"
|
|
39
|
+
},
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public"
|
|
26
42
|
}
|
|
27
43
|
}
|
package/src/adapter.ts
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* P15 — shared cloud-provider adapter contract.
|
|
5
|
-
*
|
|
6
|
-
* Every per-provider Pulumi stack at `packages/provisioning/stacks/<provider>/`
|
|
7
|
-
* exports a `provision(inputs: CloudAdapterInputs): CloudAdapterOutputs`
|
|
8
|
-
* function. The Caelo runtime never knows which provider it's running
|
|
9
|
-
* on — it just consumes the connection strings + URLs the adapter
|
|
10
|
-
* publishes via `CloudAdapterOutputs`. This keeps the per-provider
|
|
11
|
-
* surface small (~6 capabilities, see master plan) and the runtime
|
|
12
|
-
* provider-agnostic.
|
|
13
|
-
*
|
|
14
|
-
* Pulumi types are intentionally NOT imported here so that callers
|
|
15
|
-
* outside the Pulumi runtime (e.g. the cms-provision CLI, the admin
|
|
16
|
-
* app's DNS-guidance page) can consume the *plain* shape without
|
|
17
|
-
* dragging in the @pulumi/pulumi peer dep. Per-stack `index.ts` files
|
|
18
|
-
* narrow the output type to `pulumi.Output<T>` at their boundary.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
export type Environment = "dev" | "staging" | "production";
|
|
22
|
-
export type LocaleStrategy = "subdirectory" | "subdomain" | "domain";
|
|
23
|
-
|
|
24
|
-
export interface LocaleConfig {
|
|
25
|
-
/** ISO code, e.g. "en", "de", "fr-CA". */
|
|
26
|
-
readonly code: string;
|
|
27
|
-
/** URL strategy. Mixed strategies in one install are explicitly supported. */
|
|
28
|
-
readonly strategy: LocaleStrategy;
|
|
29
|
-
/** Required when strategy is "subdomain" or "domain"; ignored for "subdirectory". */
|
|
30
|
-
readonly host?: string;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface CloudAdapterInputs {
|
|
34
|
-
/** Primary domain (e.g. example.com). Admin + production public both bind here. */
|
|
35
|
-
readonly domain: string;
|
|
36
|
-
/** Operator email used for ACME / cert provisioning + Pulumi notifications. */
|
|
37
|
-
readonly ownerEmail: string;
|
|
38
|
-
/** Three-env model (CMS_REQUIREMENTS §16.5). Cloud installs always provision all three. */
|
|
39
|
-
readonly environments: ReadonlyArray<Environment>;
|
|
40
|
-
/** Per-locale routing config — drives per-domain cert + DNS guidance + edge routing. */
|
|
41
|
-
readonly locales: ReadonlyArray<LocaleConfig>;
|
|
42
|
-
/** Optional pre-existing secret references (e.g. from a CI secrets manager). */
|
|
43
|
-
readonly preProvisionedSecrets?: {
|
|
44
|
-
readonly anthropicApiKey?: string;
|
|
45
|
-
readonly resendApiKey?: string;
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* DNS records the operator must create at their registrar to make the
|
|
51
|
-
* install reachable. Surfaced in the admin's /security/dns page with
|
|
52
|
-
* live resolver status badges.
|
|
53
|
-
*/
|
|
54
|
-
export interface DnsRecord {
|
|
55
|
-
readonly hostname: string; // e.g. "de.example.com"
|
|
56
|
-
readonly type: "A" | "AAAA" | "CNAME" | "TXT";
|
|
57
|
-
readonly value: string; // the value the operator's registrar must hold
|
|
58
|
-
readonly purpose: string; // human-readable description
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Plain-data adapter outputs. Per-stack code wraps each field in
|
|
63
|
-
* `pulumi.Output<…>` at the Pulumi boundary, but the *shape* is shared
|
|
64
|
-
* across providers so the cms-provision CLI + the DNS UI can consume
|
|
65
|
-
* any provider's outputs uniformly.
|
|
66
|
-
*/
|
|
67
|
-
export interface CloudAdapterOutputs {
|
|
68
|
-
/** DSN for cms_admin role (encrypted in Pulumi state). */
|
|
69
|
-
readonly adminDatabaseUrl: string;
|
|
70
|
-
/** DSN for cms_public role (encrypted in Pulumi state). */
|
|
71
|
-
readonly publicDatabaseUrl: string;
|
|
72
|
-
/** Provider-native blob URL (s3://, gs://, https://<account>.blob.core.windows.net/<container>). */
|
|
73
|
-
readonly mediaStorageUrl: string;
|
|
74
|
-
/** Public-facing URL the admin reaches for media reads. */
|
|
75
|
-
readonly mediaCdnBaseUrl: string;
|
|
76
|
-
/** Bootstrap-token URL — operator opens this once after `pulumi up`. */
|
|
77
|
-
readonly bootstrapUrl: string;
|
|
78
|
-
/** DNS records consumed by the admin's DNS-guidance page. */
|
|
79
|
-
readonly dnsRecordsRequired: ReadonlyArray<DnsRecord>;
|
|
80
|
-
/**
|
|
81
|
-
* Where edge-A/B assignment logs land — read by the P12A analytics plugin's
|
|
82
|
-
* provider-specific log adapter. Provider-native sink URL (BigQuery dataset,
|
|
83
|
-
* Athena database, Log Analytics workspace).
|
|
84
|
-
*/
|
|
85
|
-
readonly edgeLogSinkUrl: string;
|
|
86
|
-
/** Which provider produced these outputs — drives the analytics plugin's adapter dispatch. */
|
|
87
|
-
readonly provider: SupportedProvider;
|
|
88
|
-
/** Which environment this output snapshot represents. */
|
|
89
|
-
readonly environment: Environment;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export type SupportedProvider = "self-hosted" | "gcp" | "aws" | "azure";
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Convenience: the shape persisted in `cms_admin.provisioning_outputs.outputs_json`.
|
|
96
|
-
* Pulumi runs the adapter, the CLI's `pulumi-output-sync` subcommand reads
|
|
97
|
-
* `pulumi stack output --json`, hashes the result, and writes a row keyed on
|
|
98
|
-
* (provider, environment) so the admin UI can read without provider creds.
|
|
99
|
-
*/
|
|
100
|
-
export interface ProvisioningOutputsJson {
|
|
101
|
-
readonly outputs: CloudAdapterOutputs;
|
|
102
|
-
readonly syncedAt: string; // ISO timestamp
|
|
103
|
-
}
|
package/src/bootstrap-token.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* P14 — owner bootstrap token. cms-provision generates one at first
|
|
5
|
-
* `up`; the operator visits /setup?token=<…> to create the first
|
|
6
|
-
* Owner. Single-use, 24h TTL.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
export interface BootstrapToken {
|
|
10
|
-
readonly token: string;
|
|
11
|
-
readonly expiresAt: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function generateBootstrapToken(): BootstrapToken {
|
|
15
|
-
const bytes = new Uint8Array(32);
|
|
16
|
-
crypto.getRandomValues(bytes);
|
|
17
|
-
const token = [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
18
|
-
const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString();
|
|
19
|
-
return { token, expiresAt };
|
|
20
|
-
}
|
package/src/caddy.ts
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* P14 — Caddyfile generator.
|
|
5
|
-
*
|
|
6
|
-
* Reads the `domains` table + the deploy targets and emits a
|
|
7
|
-
* deterministic Caddyfile string. The CLI's `regenerate-caddy`
|
|
8
|
-
* sub-command writes this to `/etc/caddy/Caddyfile` and runs
|
|
9
|
-
* `caddy reload`.
|
|
10
|
-
*
|
|
11
|
-
* Per-vhost shape:
|
|
12
|
-
* <hostname> {
|
|
13
|
-
* # admin → reverse_proxy localhost:5173
|
|
14
|
-
* # public → root + try_files (static), with /api/* → gateway
|
|
15
|
-
* # locale → same as public, scoped to a per-locale dist dir
|
|
16
|
-
* tls <ownerEmail>
|
|
17
|
-
* }
|
|
18
|
-
*
|
|
19
|
-
* Staging vhosts force `X-Robots-Tag: noindex`.
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
export interface CaddyDomainSpec {
|
|
23
|
-
readonly hostname: string;
|
|
24
|
-
readonly kind: "admin" | "public" | "locale-public";
|
|
25
|
-
readonly localeCode?: string;
|
|
26
|
-
readonly env: "production" | "staging";
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface CaddyfileSpec {
|
|
30
|
-
readonly ownerEmail: string;
|
|
31
|
-
readonly publicSiteRoot: string; // absolute path on disk
|
|
32
|
-
readonly stagingSiteRoot: string;
|
|
33
|
-
readonly adminPort: number;
|
|
34
|
-
readonly gatewayPort: number;
|
|
35
|
-
readonly domains: ReadonlyArray<CaddyDomainSpec>;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function generateCaddyfile(spec: CaddyfileSpec): string {
|
|
39
|
-
const blocks: string[] = [];
|
|
40
|
-
|
|
41
|
-
// Global options.
|
|
42
|
-
blocks.push(`{
|
|
43
|
-
email ${spec.ownerEmail}
|
|
44
|
-
}
|
|
45
|
-
`);
|
|
46
|
-
|
|
47
|
-
for (const d of spec.domains) {
|
|
48
|
-
blocks.push(vhost(d, spec));
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Localhost dev fallback when no domains are configured (so a fresh
|
|
52
|
-
// `cms-provision` produces a working Caddyfile even pre-DNS).
|
|
53
|
-
if (spec.domains.length === 0) {
|
|
54
|
-
blocks.push(`# No domains configured yet — cms-provision regenerate-caddy
|
|
55
|
-
# will overwrite this file when you add one at /security/domains.
|
|
56
|
-
:8081 {
|
|
57
|
-
reverse_proxy localhost:${spec.adminPort}
|
|
58
|
-
}
|
|
59
|
-
`);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return blocks.join("\n");
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function vhost(d: CaddyDomainSpec, spec: CaddyfileSpec): string {
|
|
66
|
-
const noindex = d.env === "staging" ? `\n header X-Robots-Tag "noindex"` : "";
|
|
67
|
-
if (d.kind === "admin") {
|
|
68
|
-
return `${d.hostname} {${noindex}
|
|
69
|
-
reverse_proxy localhost:${spec.adminPort}
|
|
70
|
-
}
|
|
71
|
-
`;
|
|
72
|
-
}
|
|
73
|
-
// public / locale-public — same shape: API routes go to the gateway,
|
|
74
|
-
// the rest serves static files. Locale variants serve from a
|
|
75
|
-
// per-locale subdirectory.
|
|
76
|
-
const root = d.env === "staging" ? spec.stagingSiteRoot : spec.publicSiteRoot;
|
|
77
|
-
const localeSubdir = d.kind === "locale-public" && d.localeCode ? `/${d.localeCode}` : "";
|
|
78
|
-
return `${d.hostname} {${noindex}
|
|
79
|
-
root * ${root}${localeSubdir}
|
|
80
|
-
handle /api/* {
|
|
81
|
-
reverse_proxy localhost:${spec.gatewayPort}
|
|
82
|
-
}
|
|
83
|
-
handle /admin/* {
|
|
84
|
-
reverse_proxy localhost:${spec.adminPort}
|
|
85
|
-
}
|
|
86
|
-
handle {
|
|
87
|
-
file_server {
|
|
88
|
-
try_files {path} {path}/index.html /index.html
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
`;
|
|
93
|
-
}
|