@caelo-cms/provisioning 0.11.10 → 0.11.12
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 +6 -0
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adopt-existing.d.ts +6 -2
- package/dist/adopt-existing.d.ts.map +1 -1
- package/dist/adopt-existing.js +57 -0
- package/dist/adopt-existing.js.map +1 -1
- package/dist/agent/command.d.ts +5 -2
- package/dist/agent/command.d.ts.map +1 -1
- package/dist/agent/command.js +1 -1
- package/dist/agent/command.js.map +1 -1
- package/dist/agent-db.js +196 -23
- package/dist/cli-routing.js +1 -1
- package/dist/cli-routing.js.map +1 -1
- package/dist/cli.js +59 -7
- package/dist/cli.js.map +1 -1
- package/dist/cloudsql-tls.d.ts +30 -0
- package/dist/cloudsql-tls.d.ts.map +1 -1
- package/dist/cloudsql-tls.js +7 -1
- package/dist/cloudsql-tls.js.map +1 -1
- package/dist/compose.d.ts +14 -0
- package/dist/compose.d.ts.map +1 -1
- package/dist/compose.js +54 -1
- package/dist/compose.js.map +1 -1
- package/dist/converge-db-trust.d.ts +44 -0
- package/dist/converge-db-trust.d.ts.map +1 -0
- package/dist/converge-db-trust.js +158 -0
- package/dist/converge-db-trust.js.map +1 -0
- package/dist/converge-job-gcp.d.ts +12 -2
- package/dist/converge-job-gcp.d.ts.map +1 -1
- package/dist/converge-job-gcp.js +42 -3
- package/dist/converge-job-gcp.js.map +1 -1
- package/dist/converge-job-preflight.d.ts +34 -2
- package/dist/converge-job-preflight.d.ts.map +1 -1
- package/dist/converge-job-preflight.js +76 -1
- package/dist/converge-job-preflight.js.map +1 -1
- package/dist/converge-job-setup.d.ts.map +1 -1
- package/dist/converge-job-setup.js +4 -1
- package/dist/converge-job-setup.js.map +1 -1
- package/dist/converge-job.d.ts +44 -3
- package/dist/converge-job.d.ts.map +1 -1
- package/dist/converge-job.js +105 -11
- package/dist/converge-job.js.map +1 -1
- package/dist/gcp-names.d.ts +13 -0
- package/dist/gcp-names.d.ts.map +1 -1
- package/dist/gcp-names.js +21 -0
- package/dist/gcp-names.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/infra-contract.d.ts +1 -1
- package/dist/infra-contract.d.ts.map +1 -1
- package/dist/infra-contract.js +22 -1
- package/dist/infra-contract.js.map +1 -1
- package/dist/install-job.d.ts +10 -2
- package/dist/install-job.d.ts.map +1 -1
- package/dist/install-job.js +10 -1
- package/dist/install-job.js.map +1 -1
- package/dist/install-state.d.ts +7 -0
- package/dist/install-state.d.ts.map +1 -1
- package/dist/install-state.js.map +1 -1
- package/dist/lifecycle.d.ts.map +1 -1
- package/dist/lifecycle.js +3 -0
- package/dist/lifecycle.js.map +1 -1
- package/dist/mcp-auth-proxy-setup.d.ts +3 -1
- package/dist/mcp-auth-proxy-setup.d.ts.map +1 -1
- package/dist/mcp-auth-proxy-setup.js +9 -4
- package/dist/mcp-auth-proxy-setup.js.map +1 -1
- package/dist/migration-runner.d.ts.map +1 -1
- package/dist/migration-runner.js +8 -3
- package/dist/migration-runner.js.map +1 -1
- package/dist/migration-targets.d.ts +11 -0
- package/dist/migration-targets.d.ts.map +1 -0
- package/dist/migration-targets.js +12 -0
- package/dist/migration-targets.js.map +1 -0
- package/dist/operator-access.d.ts +10 -0
- package/dist/operator-access.d.ts.map +1 -1
- package/dist/operator-access.js +5 -0
- package/dist/operator-access.js.map +1 -1
- package/dist/pool-budget-stack.d.ts +13 -1
- package/dist/pool-budget-stack.d.ts.map +1 -1
- package/dist/pool-budget-stack.js +11 -1
- package/dist/pool-budget-stack.js.map +1 -1
- package/dist/public-role-isolation.d.ts +7 -3
- package/dist/public-role-isolation.d.ts.map +1 -1
- package/dist/public-role-isolation.js +25 -13
- package/dist/public-role-isolation.js.map +1 -1
- package/dist/release-contract.d.ts.map +1 -1
- package/dist/release-contract.js +3 -0
- package/dist/release-contract.js.map +1 -1
- package/dist/runtime-secrets.d.ts +2 -0
- package/dist/runtime-secrets.d.ts.map +1 -1
- package/dist/runtime-secrets.js +19 -15
- package/dist/runtime-secrets.js.map +1 -1
- package/dist/scheduler-setup.d.ts +75 -0
- package/dist/scheduler-setup.d.ts.map +1 -0
- package/dist/scheduler-setup.js +331 -0
- package/dist/scheduler-setup.js.map +1 -0
- package/dist/self-hosted-roles.d.ts +16 -9
- package/dist/self-hosted-roles.d.ts.map +1 -1
- package/dist/self-hosted-roles.js +49 -15
- package/dist/self-hosted-roles.js.map +1 -1
- package/dist/self-hosted-scheduler-interval.d.ts +21 -0
- package/dist/self-hosted-scheduler-interval.d.ts.map +1 -0
- package/dist/self-hosted-scheduler-interval.js +68 -0
- package/dist/self-hosted-scheduler-interval.js.map +1 -0
- package/dist/self-hosted-upgrade.d.ts +7 -4
- package/dist/self-hosted-upgrade.d.ts.map +1 -1
- package/dist/self-hosted-upgrade.js +9 -5
- package/dist/self-hosted-upgrade.js.map +1 -1
- package/dist/stack-contract.d.ts +67 -6
- package/dist/stack-contract.d.ts.map +1 -1
- package/dist/stack-contract.js +87 -6
- package/dist/stack-contract.js.map +1 -1
- package/dist/staging-database.d.ts +22 -0
- package/dist/staging-database.d.ts.map +1 -0
- package/dist/staging-database.js +117 -0
- package/dist/staging-database.js.map +1 -0
- package/dist/upgrade/converge.d.ts +5 -0
- package/dist/upgrade/converge.d.ts.map +1 -1
- package/dist/upgrade/converge.js +110 -10
- package/dist/upgrade/converge.js.map +1 -1
- package/dist/upgrade/upgrade.d.ts +6 -0
- package/dist/upgrade/upgrade.d.ts.map +1 -1
- package/dist/upgrade/upgrade.js +3 -0
- package/dist/upgrade/upgrade.js.map +1 -1
- package/dist/upgrader-gcp.d.ts +5 -4
- package/dist/upgrader-gcp.d.ts.map +1 -1
- package/dist/upgrader-gcp.js +5 -4
- package/dist/upgrader-gcp.js.map +1 -1
- package/dist/upgrader-iam-preflight.d.ts +8 -6
- package/dist/upgrader-iam-preflight.d.ts.map +1 -1
- package/dist/upgrader-iam-preflight.js +5 -1
- package/dist/upgrader-iam-preflight.js.map +1 -1
- package/dist/wizard.d.ts +7 -0
- package/dist/wizard.d.ts.map +1 -1
- package/dist/wizard.js +11 -0
- package/dist/wizard.js.map +1 -1
- package/dist/wizards/gcp-cost.d.ts +31 -0
- package/dist/wizards/gcp-cost.d.ts.map +1 -1
- package/dist/wizards/gcp-cost.js +82 -12
- package/dist/wizards/gcp-cost.js.map +1 -1
- package/dist/wizards/gcp.d.ts.map +1 -1
- package/dist/wizards/gcp.js +87 -11
- package/dist/wizards/gcp.js.map +1 -1
- package/package.json +2 -2
- package/src/adapter.ts +6 -0
- package/src/admin-memory.test.ts +1 -1
- package/src/adopt-existing.test.ts +46 -0
- package/src/adopt-existing.ts +58 -2
- package/src/agent/command.ts +6 -3
- package/src/caddy-redirects.test.ts +1 -1
- package/src/cli-routing.test.ts +1 -1
- package/src/cli-routing.ts +1 -1
- package/src/cli.ts +63 -8
- package/src/cloudsql-tls.test.ts +30 -0
- package/src/cloudsql-tls.ts +10 -3
- package/src/compose.ts +68 -0
- package/src/converge-db-trust.test.ts +237 -0
- package/src/converge-db-trust.ts +212 -0
- package/src/converge-job-gcp.ts +42 -2
- package/src/converge-job-preflight.test.ts +106 -0
- package/src/converge-job-preflight.ts +97 -3
- package/src/converge-job-setup.ts +4 -1
- package/src/converge-job.test.ts +213 -0
- package/src/converge-job.ts +175 -19
- package/src/database-tls-builders.test.ts +3 -2
- package/src/deployed-release.test.ts +3 -0
- package/src/gateway-env-isolation.test.ts +40 -7
- package/src/gcp-names.ts +25 -0
- package/src/index.test.ts +3 -3
- package/src/index.ts +6 -0
- package/src/infra-contract.test.ts +5 -3
- package/src/infra-contract.ts +22 -1
- package/src/install-job.ts +10 -1
- package/src/install-state.ts +7 -0
- package/src/lifecycle.ts +3 -0
- package/src/mcp-auth-proxy-setup.test.ts +33 -1
- package/src/mcp-auth-proxy-setup.ts +11 -4
- package/src/migration-runner.ts +14 -9
- package/src/migration-targets.ts +12 -0
- package/src/operator-access.test.ts +15 -0
- package/src/operator-access.ts +14 -0
- package/src/pool-budget-stack.ts +15 -0
- package/src/pool-budget-stacks.test.ts +38 -13
- package/src/public-role-isolation.test.ts +18 -8
- package/src/public-role-isolation.ts +31 -17
- package/src/release-contract.ts +3 -0
- package/src/runtime-secrets.test.ts +18 -8
- package/src/runtime-secrets.ts +37 -18
- package/src/scheduler-setup.test.ts +201 -0
- package/src/scheduler-setup.ts +375 -0
- package/src/self-hosted-backup.test.ts +1 -0
- package/src/self-hosted-compose.test.ts +72 -1
- package/src/self-hosted-roles.integration.test.ts +2 -0
- package/src/self-hosted-roles.test.ts +49 -19
- package/src/self-hosted-roles.ts +60 -20
- package/src/self-hosted-scheduler-interval.test.ts +66 -0
- package/src/self-hosted-scheduler-interval.ts +79 -0
- package/src/self-hosted-start.test.ts +1 -0
- package/src/self-hosted-upgrade.test.ts +2 -1
- package/src/self-hosted-upgrade.ts +14 -8
- package/src/stack-contract.test.ts +31 -3
- package/src/stack-contract.ts +113 -6
- package/src/stack-converge-secrets.test.ts +2 -0
- package/src/stack-converge.test.ts +6 -4
- package/src/staging-database.test.ts +114 -0
- package/src/staging-database.ts +148 -0
- package/src/upgrade/converge.ts +130 -9
- package/src/upgrade/upgrade.test.ts +85 -2
- package/src/upgrade/upgrade.ts +9 -0
- package/src/upgrader-gcp.ts +5 -4
- package/src/upgrader-iam-preflight.ts +15 -10
- package/src/wizard.ts +19 -0
- package/src/wizards/gcp-cost.test.ts +54 -1
- package/src/wizards/gcp-cost.ts +102 -12
- package/src/wizards/gcp.ts +109 -8
- package/stacks/aws/Pulumi.yaml +1 -1
- package/stacks/aws/index.ts +41 -1
- package/stacks/azure/index.ts +51 -3
- package/stacks/gcp/Pulumi.yaml +1 -1
- package/stacks/gcp/README.md +1 -0
- package/stacks/gcp/index.ts +50 -1
- package/stacks/gcp-firebase/Pulumi.yaml +1 -1
- package/stacks/gcp-firebase/index.ts +50 -1
- package/stacks/self-hosted/Pulumi.yaml +3 -0
- package/stacks/self-hosted/README.md +4 -0
- package/stacks/self-hosted/index.ts +29 -4
package/stacks/gcp/index.ts
CHANGED
|
@@ -33,8 +33,10 @@ import {
|
|
|
33
33
|
} from "../../dist/gcp-names.js";
|
|
34
34
|
import { createSiteUrlMap } from "../../dist/gcp-url-map.js";
|
|
35
35
|
import {
|
|
36
|
+
DEFAULT_ADMIN_MAX_INSTANCES,
|
|
36
37
|
DEFAULT_GATEWAY_MAX_INSTANCES,
|
|
37
38
|
DEFAULT_MAX_CONNECTIONS,
|
|
39
|
+
DEFAULT_STAGING_GATEWAY_MAX_INSTANCES,
|
|
38
40
|
stackPoolBudget,
|
|
39
41
|
} from "../../dist/pool-budget-stack.js";
|
|
40
42
|
import {
|
|
@@ -119,7 +121,10 @@ const adminMemory = cfg.get("adminMemory") ?? ADMIN_MEMORY_DEFAULT;
|
|
|
119
121
|
// against the original 25-slot Postgres default — connection exhaustion
|
|
120
122
|
// took the admin offline. v0.2.49 bakes the post-incident shape so fresh
|
|
121
123
|
// installs don't trip the same wall.
|
|
122
|
-
const adminMaxInstances = Number.parseInt(
|
|
124
|
+
const adminMaxInstances = Number.parseInt(
|
|
125
|
+
cfg.get("adminMaxInstances") ?? String(DEFAULT_ADMIN_MAX_INSTANCES),
|
|
126
|
+
10,
|
|
127
|
+
);
|
|
123
128
|
const gatewayMinInstances = Number.parseInt(cfg.get("gatewayMinInstances") ?? "0", 10);
|
|
124
129
|
// #1254 — the gateway's scale-out is part of the connection budget: each
|
|
125
130
|
// instance holds a cms_public pool.
|
|
@@ -137,6 +142,8 @@ const poolBudget = stackPoolBudget("gcp", {
|
|
|
137
142
|
maxConnections,
|
|
138
143
|
adminMaxInstances,
|
|
139
144
|
gatewayMaxInstances,
|
|
145
|
+
// #1329 — the staging gateway (P3b-2) is budgeted from this release on.
|
|
146
|
+
stagingGatewayMaxInstances: DEFAULT_STAGING_GATEWAY_MAX_INSTANCES,
|
|
140
147
|
});
|
|
141
148
|
const wafAdaptiveProtection = cfg.getBoolean("wafAdaptiveProtection") ?? false;
|
|
142
149
|
const backupRetentionDays = Number.parseInt(cfg.get("backupRetentionDays") ?? "7", 10);
|
|
@@ -281,6 +288,8 @@ const runtimeSecretIds: Record<RuntimeSecret, pulumi.Input<string>> = {
|
|
|
281
288
|
"tool-approval-secret": gcpSecretId(env, "tool-approval-secret"),
|
|
282
289
|
"staging-gate-secret": gcpSecretId(env, "staging-gate-secret"),
|
|
283
290
|
"public-role-password": gcpSecretId(env, "public-role-password"),
|
|
291
|
+
// #1329 — the staging gateway's login, never the live one's.
|
|
292
|
+
"public-staging-role-password": gcpSecretId(env, "public-staging-role-password"),
|
|
284
293
|
// #914 / #806 — the Owner-set Google sign-in client (a placeholder until
|
|
285
294
|
// then; IAP login, IAP programmatic access and the remote-MCP broker) and
|
|
286
295
|
// the broker's seal key, both CLI-generated.
|
|
@@ -297,6 +306,14 @@ const publicRolePassword = pulumi.secret(
|
|
|
297
306
|
{ provider: gcpProvider },
|
|
298
307
|
).secretData,
|
|
299
308
|
);
|
|
309
|
+
// #1329 — public_staging_role's own password (CLI-generated like the one
|
|
310
|
+
// above): the staging gateway's only login, on cms_public_staging only.
|
|
311
|
+
const publicStagingRolePassword = pulumi.secret(
|
|
312
|
+
gcp.secretmanager.getSecretVersionOutput(
|
|
313
|
+
{ project, secret: gcpSecretId(env, "public-staging-role-password") },
|
|
314
|
+
{ provider: gcpProvider },
|
|
315
|
+
).secretData,
|
|
316
|
+
);
|
|
300
317
|
|
|
301
318
|
// =========================================================================
|
|
302
319
|
// Tier 4 — Cloud SQL Postgres (private IP only; HA + retention configurable)
|
|
@@ -371,6 +388,14 @@ const pgPublicUser = new gcp.sql.User(
|
|
|
371
388
|
{ instance: sqlInstance.name, name: "public_role", password: publicRolePassword },
|
|
372
389
|
opts,
|
|
373
390
|
);
|
|
391
|
+
// #1329 — the staging gateway's login: an Admin API user like public_role,
|
|
392
|
+
// so the same membership revoke (public-role-isolation.ts) and the same
|
|
393
|
+
// audit apply; migration 0530 gives it CONNECT on cms_public_staging only.
|
|
394
|
+
new gcp.sql.User(
|
|
395
|
+
`${namePrefix}-pg-public-staging`,
|
|
396
|
+
{ instance: sqlInstance.name, name: "public_staging_role", password: publicStagingRolePassword },
|
|
397
|
+
opts,
|
|
398
|
+
);
|
|
374
399
|
|
|
375
400
|
const cmsAdminDb = new gcp.sql.Database(
|
|
376
401
|
`${namePrefix}-cms-admin-db`,
|
|
@@ -382,6 +407,13 @@ const cmsPublicDb = new gcp.sql.Database(
|
|
|
382
407
|
{ instance: sqlInstance.name, name: "cms_public" },
|
|
383
408
|
opts,
|
|
384
409
|
);
|
|
410
|
+
// #1329 — staging's own data: the twin of cms_public on the same instance
|
|
411
|
+
// (no extra cost), reached by the staging gateway only.
|
|
412
|
+
const cmsPublicStagingDb = new gcp.sql.Database(
|
|
413
|
+
`${namePrefix}-cms-public-staging-db`,
|
|
414
|
+
{ instance: sqlInstance.name, name: "cms_public_staging" },
|
|
415
|
+
opts,
|
|
416
|
+
);
|
|
385
417
|
|
|
386
418
|
// #1338 — the instance's DNS name, resolved to its private IP by a private
|
|
387
419
|
// zone in the VPC (Cloud SQL assigns the name, it does not resolve it).
|
|
@@ -767,6 +799,8 @@ const envContractInputs = {
|
|
|
767
799
|
admin: dbUrls.admin,
|
|
768
800
|
publicAdmin: dbUrls.publicAdmin,
|
|
769
801
|
public: dbUrls.public,
|
|
802
|
+
publicStagingAdmin: dbUrls.publicStagingAdmin,
|
|
803
|
+
publicStaging: dbUrls.publicStaging,
|
|
770
804
|
},
|
|
771
805
|
} as const;
|
|
772
806
|
|
|
@@ -841,6 +875,7 @@ function cloudRunService(args: CloudRunArgs): gcp.cloudrunv2.Service {
|
|
|
841
875
|
pgAdminUser,
|
|
842
876
|
cmsAdminDb,
|
|
843
877
|
cmsPublicDb,
|
|
878
|
+
cmsPublicStagingDb,
|
|
844
879
|
// The env contract names the buckets as strings; keep them ordered
|
|
845
880
|
// before the services that use them.
|
|
846
881
|
mediaBucket,
|
|
@@ -1155,6 +1190,18 @@ new gcp.iap.WebBackendServiceIamMember(
|
|
|
1155
1190
|
// (packages/provisioning/src/capture-job-setup.ts), for the same reason as
|
|
1156
1191
|
// the audit job above.
|
|
1157
1192
|
|
|
1193
|
+
// =========================================================================
|
|
1194
|
+
// Scheduler tick — deliberately NOT here
|
|
1195
|
+
// =========================================================================
|
|
1196
|
+
//
|
|
1197
|
+
// #1140 — one Cloud Scheduler job starts a Cloud Workflows execution every
|
|
1198
|
+
// `schedulerIntervalMinutes` (1–15, default 5); the workflow signs an IAP
|
|
1199
|
+
// credential as the scheduler's own service account and POSTs the admin's
|
|
1200
|
+
// /api/internal/scheduler/tick, where the admin runs its periodic work and
|
|
1201
|
+
// plugin workers from its due-table. The CLI owns the account, its grants,
|
|
1202
|
+
// the workflow and the job (packages/provisioning/src/scheduler-setup.ts),
|
|
1203
|
+
// for the same reason as the audit job above.
|
|
1204
|
+
|
|
1158
1205
|
// =========================================================================
|
|
1159
1206
|
// Operator access — deliberately NOT here
|
|
1160
1207
|
// =========================================================================
|
|
@@ -1325,6 +1372,7 @@ const out: CloudAdapterOutputs = {
|
|
|
1325
1372
|
// Password-less — the password lives only in Secret Manager.
|
|
1326
1373
|
adminDatabaseUrl: dbUrls.admin as unknown as string,
|
|
1327
1374
|
publicDatabaseUrl: dbUrls.public as unknown as string,
|
|
1375
|
+
publicStagingDatabaseUrl: dbUrls.publicStaging as unknown as string,
|
|
1328
1376
|
mediaStorageUrl: pulumi.interpolate`gs://${mediaBucket.name}` as unknown as string,
|
|
1329
1377
|
mediaCdnBaseUrl: pulumi.interpolate`https://${domain}/media` as unknown as string,
|
|
1330
1378
|
bootstrapUrl:
|
|
@@ -1338,6 +1386,7 @@ const out: CloudAdapterOutputs = {
|
|
|
1338
1386
|
|
|
1339
1387
|
export const adminDatabaseUrlOut = out.adminDatabaseUrl;
|
|
1340
1388
|
export const publicDatabaseUrlOut = out.publicDatabaseUrl;
|
|
1389
|
+
export const publicStagingDatabaseUrlOut = out.publicStagingDatabaseUrl;
|
|
1341
1390
|
export const mediaStorageUrlOut = out.mediaStorageUrl;
|
|
1342
1391
|
export const mediaCdnBaseUrlOut = out.mediaCdnBaseUrl;
|
|
1343
1392
|
export const bootstrapUrlOut = out.bootstrapUrl;
|
|
@@ -58,7 +58,7 @@ config:
|
|
|
58
58
|
description: Admin Cloud Run memory. 2Gi leaves room for the Lighthouse quality audit next to editor requests; billed only while an instance runs.
|
|
59
59
|
caelo-gcp-firebase:adminMaxInstances:
|
|
60
60
|
type: string
|
|
61
|
-
default: "
|
|
61
|
+
default: "4"
|
|
62
62
|
description: Admin Cloud Run max instances.
|
|
63
63
|
caelo-gcp-firebase:maxConnections:
|
|
64
64
|
type: string
|
|
@@ -41,8 +41,10 @@ import {
|
|
|
41
41
|
runServiceAccountId,
|
|
42
42
|
} from "../../dist/gcp-names.js";
|
|
43
43
|
import {
|
|
44
|
+
DEFAULT_ADMIN_MAX_INSTANCES,
|
|
44
45
|
DEFAULT_GATEWAY_MAX_INSTANCES,
|
|
45
46
|
DEFAULT_MAX_CONNECTIONS,
|
|
47
|
+
DEFAULT_STAGING_GATEWAY_MAX_INSTANCES,
|
|
46
48
|
stackPoolBudget,
|
|
47
49
|
} from "../../dist/pool-budget-stack.js";
|
|
48
50
|
import {
|
|
@@ -109,7 +111,10 @@ const adminMinInstances = Number.parseInt(cfg.get("adminMinInstances") ?? "0", 1
|
|
|
109
111
|
// #553 — the admin also runs the Lighthouse quality audit; same knob and
|
|
110
112
|
// default on every adapter (stack-contract.ts ADMIN_MEMORY_DEFAULT).
|
|
111
113
|
const adminMemory = cfg.get("adminMemory") ?? ADMIN_MEMORY_DEFAULT;
|
|
112
|
-
const adminMaxInstances = Number.parseInt(
|
|
114
|
+
const adminMaxInstances = Number.parseInt(
|
|
115
|
+
cfg.get("adminMaxInstances") ?? String(DEFAULT_ADMIN_MAX_INSTANCES),
|
|
116
|
+
10,
|
|
117
|
+
);
|
|
113
118
|
const gatewayMinInstances = Number.parseInt(cfg.get("gatewayMinInstances") ?? "0", 10);
|
|
114
119
|
// #1254 — part of the connection budget (stacks/gcp has the long comment).
|
|
115
120
|
const gatewayMaxInstances = Number.parseInt(
|
|
@@ -125,6 +130,8 @@ const poolBudget = stackPoolBudget("gcp-firebase", {
|
|
|
125
130
|
maxConnections,
|
|
126
131
|
adminMaxInstances,
|
|
127
132
|
gatewayMaxInstances,
|
|
133
|
+
// #1329 — the staging gateway (P3b-2) is budgeted from this release on.
|
|
134
|
+
stagingGatewayMaxInstances: DEFAULT_STAGING_GATEWAY_MAX_INSTANCES,
|
|
128
135
|
});
|
|
129
136
|
const backupRetentionDays = Number.parseInt(cfg.get("backupRetentionDays") ?? "7", 10);
|
|
130
137
|
const iapAllowlistRaw = cfg.get("iapAllowlist");
|
|
@@ -262,6 +269,8 @@ const runtimeSecretIds: Record<RuntimeSecret, pulumi.Input<string>> = {
|
|
|
262
269
|
"tool-approval-secret": gcpSecretId(env, "tool-approval-secret"),
|
|
263
270
|
"staging-gate-secret": gcpSecretId(env, "staging-gate-secret"),
|
|
264
271
|
"public-role-password": gcpSecretId(env, "public-role-password"),
|
|
272
|
+
// #1329 — the staging gateway's login, never the live one's.
|
|
273
|
+
"public-staging-role-password": gcpSecretId(env, "public-staging-role-password"),
|
|
265
274
|
// #914 / #806 — the Owner-set Google sign-in client (a placeholder until
|
|
266
275
|
// then; IAP login, IAP programmatic access and the remote-MCP broker) and
|
|
267
276
|
// the broker's seal key, both CLI-generated.
|
|
@@ -278,6 +287,14 @@ const publicRolePassword = pulumi.secret(
|
|
|
278
287
|
{ provider: gcpProvider },
|
|
279
288
|
).secretData,
|
|
280
289
|
);
|
|
290
|
+
// #1329 — public_staging_role's own password (CLI-generated like the one
|
|
291
|
+
// above): the staging gateway's only login, on cms_public_staging only.
|
|
292
|
+
const publicStagingRolePassword = pulumi.secret(
|
|
293
|
+
gcp.secretmanager.getSecretVersionOutput(
|
|
294
|
+
{ project, secret: gcpSecretId(env, "public-staging-role-password") },
|
|
295
|
+
{ provider: gcpProvider },
|
|
296
|
+
).secretData,
|
|
297
|
+
);
|
|
281
298
|
|
|
282
299
|
// =========================================================================
|
|
283
300
|
// Tier 4 — Cloud SQL Postgres (private IP only; HA configurable)
|
|
@@ -334,6 +351,13 @@ const cmsPublicDb = new gcp.sql.Database(
|
|
|
334
351
|
{ instance: sqlInstance.name, name: "cms_public" },
|
|
335
352
|
opts,
|
|
336
353
|
);
|
|
354
|
+
// #1329 — staging's own data: the twin of cms_public on the same instance
|
|
355
|
+
// (no extra cost), reached by the staging gateway only.
|
|
356
|
+
const cmsPublicStagingDb = new gcp.sql.Database(
|
|
357
|
+
`${namePrefix}-cms-public-staging`,
|
|
358
|
+
{ instance: sqlInstance.name, name: "cms_public_staging" },
|
|
359
|
+
opts,
|
|
360
|
+
);
|
|
337
361
|
|
|
338
362
|
const pgAdminUser = new gcp.sql.User(
|
|
339
363
|
`${namePrefix}-admin-user`,
|
|
@@ -352,6 +376,14 @@ new gcp.sql.User(
|
|
|
352
376
|
{ instance: sqlInstance.name, name: "public_role", password: publicRolePassword },
|
|
353
377
|
opts,
|
|
354
378
|
);
|
|
379
|
+
// #1329 — the staging gateway's login: an Admin API user like public_role,
|
|
380
|
+
// so the same membership revoke (public-role-isolation.ts) and the same
|
|
381
|
+
// audit apply; migration 0530 gives it CONNECT on cms_public_staging only.
|
|
382
|
+
new gcp.sql.User(
|
|
383
|
+
`${namePrefix}-public-staging-user`,
|
|
384
|
+
{ instance: sqlInstance.name, name: "public_staging_role", password: publicStagingRolePassword },
|
|
385
|
+
opts,
|
|
386
|
+
);
|
|
355
387
|
|
|
356
388
|
// #1338 — the instance's DNS name, resolved to its private IP by a private
|
|
357
389
|
// zone in the VPC (Cloud SQL assigns the name, it does not resolve it).
|
|
@@ -729,6 +761,7 @@ function cloudRunService(args: CloudRunArgs): gcp.cloudrunv2.Service {
|
|
|
729
761
|
pgAdminUser,
|
|
730
762
|
cmsAdminDb,
|
|
731
763
|
cmsPublicDb,
|
|
764
|
+
cmsPublicStagingDb,
|
|
732
765
|
// The env contract names the media bucket as a string; keep it
|
|
733
766
|
// ordered before the services that use it.
|
|
734
767
|
mediaBucket,
|
|
@@ -756,6 +789,8 @@ const envContractInputs = {
|
|
|
756
789
|
admin: dbUrls.admin,
|
|
757
790
|
publicAdmin: dbUrls.publicAdmin,
|
|
758
791
|
public: dbUrls.public,
|
|
792
|
+
publicStagingAdmin: dbUrls.publicStagingAdmin,
|
|
793
|
+
publicStaging: dbUrls.publicStaging,
|
|
759
794
|
},
|
|
760
795
|
} as const;
|
|
761
796
|
|
|
@@ -1129,6 +1164,18 @@ new gcp.iap.WebCloudRunServiceIamMember(
|
|
|
1129
1164
|
// (packages/provisioning/src/capture-job-setup.ts), for the same reason as
|
|
1130
1165
|
// the audit job above.
|
|
1131
1166
|
|
|
1167
|
+
// =========================================================================
|
|
1168
|
+
// Scheduler tick — deliberately NOT here
|
|
1169
|
+
// =========================================================================
|
|
1170
|
+
//
|
|
1171
|
+
// #1140 — one Cloud Scheduler job starts a Cloud Workflows execution every
|
|
1172
|
+
// `schedulerIntervalMinutes` (1–15, default 5); the workflow signs an IAP
|
|
1173
|
+
// credential as the scheduler's own service account and POSTs the admin's
|
|
1174
|
+
// /api/internal/scheduler/tick, where the admin runs its periodic work and
|
|
1175
|
+
// plugin workers from its due-table. The CLI owns the account, its grants,
|
|
1176
|
+
// the workflow and the job (packages/provisioning/src/scheduler-setup.ts),
|
|
1177
|
+
// for the same reason as the audit job above.
|
|
1178
|
+
|
|
1132
1179
|
// =========================================================================
|
|
1133
1180
|
// Operator access — deliberately NOT here
|
|
1134
1181
|
// =========================================================================
|
|
@@ -1213,6 +1260,7 @@ const out: CloudAdapterOutputs = {
|
|
|
1213
1260
|
// Password-less — the password lives only in Secret Manager.
|
|
1214
1261
|
adminDatabaseUrl: dbUrls.admin as unknown as string,
|
|
1215
1262
|
publicDatabaseUrl: dbUrls.public as unknown as string,
|
|
1263
|
+
publicStagingDatabaseUrl: dbUrls.publicStaging as unknown as string,
|
|
1216
1264
|
mediaStorageUrl: pulumi.interpolate`gs://${mediaBucket.name}` as unknown as string,
|
|
1217
1265
|
// Firebase Hosting serves media via its own CDN; we proxy through
|
|
1218
1266
|
// the Firebase site's apex domain for canonical URLs.
|
|
@@ -1229,6 +1277,7 @@ const out: CloudAdapterOutputs = {
|
|
|
1229
1277
|
|
|
1230
1278
|
export const adminDatabaseUrlOut = out.adminDatabaseUrl;
|
|
1231
1279
|
export const publicDatabaseUrlOut = out.publicDatabaseUrl;
|
|
1280
|
+
export const publicStagingDatabaseUrlOut = out.publicStagingDatabaseUrl;
|
|
1232
1281
|
export const mediaStorageUrlOut = out.mediaStorageUrl;
|
|
1233
1282
|
export const mediaCdnBaseUrlOut = out.mediaCdnBaseUrl;
|
|
1234
1283
|
export const bootstrapUrlOut = out.bootstrapUrl;
|
|
@@ -29,3 +29,6 @@ config:
|
|
|
29
29
|
type: string
|
|
30
30
|
default: "2Gi"
|
|
31
31
|
description: Memory reserved for the admin container (it runs the Lighthouse quality audit).
|
|
32
|
+
caelo-self-hosted:schedulerIntervalMinutes:
|
|
33
|
+
type: integer
|
|
34
|
+
description: Minutes between scheduler ticks (1-15, default 5; #1140). On an install `cms-provision init` set up, `.caelo/config.json` records it and this key must match.
|
|
@@ -33,6 +33,10 @@ pulumi stack output bootstrapUrl
|
|
|
33
33
|
|
|
34
34
|
Open that URL once Caddy has issued the cert (~30 s).
|
|
35
35
|
|
|
36
|
+
## Scheduler tick
|
|
37
|
+
|
|
38
|
+
The Compose file runs a `caelo-scheduler` container (the admin image, no database access, only the internal secret it signs with) that POSTs the admin's `/api/internal/scheduler/tick` every `schedulerIntervalMinutes` (#1140; 1–15, default 5). The admin runs its periodic work and plugin workers on that tick. Set the interval with `cms-provision init --scheduler-interval-minutes 2` (recorded in `.caelo/config.json`, which every CLI render reads) or, on an install without a `config.json`, with `pulumi config set caelo-self-hosted:schedulerIntervalMinutes 2`. The stack reads `.caelo/config.json` too, so a `pulumi up` keeps the recorded interval; a Pulumi key that disagrees with it is refused. An install from before the scheduler gets the container on its next `upgrade`, which rewrites the Compose file.
|
|
39
|
+
|
|
36
40
|
## Connection budget
|
|
37
41
|
|
|
38
42
|
The generated Compose file starts Postgres with `max_connections=100` and sizes every pool from it (#1254, docs/dev/connection-budget.md): the admin gets a cms_admin pool of 77 and a cms_public pool of 4, the gateway a pool of 2, and static builds 2 per pool.
|
|
@@ -34,7 +34,12 @@ import {
|
|
|
34
34
|
generateDockerCompose,
|
|
35
35
|
SITE_OUTPUT_ROOT,
|
|
36
36
|
} from "../../dist/compose.js";
|
|
37
|
+
import { MIGRATION_TARGETS } from "../../dist/migration-targets.js";
|
|
37
38
|
import { selfHostedBootstrapScript } from "../../dist/self-hosted-roles.js";
|
|
39
|
+
import {
|
|
40
|
+
recordedSchedulerIntervalMinutes,
|
|
41
|
+
selfHostedSchedulerIntervalMinutes,
|
|
42
|
+
} from "../../dist/self-hosted-scheduler-interval.js";
|
|
38
43
|
|
|
39
44
|
const cfg = new pulumi.Config();
|
|
40
45
|
const domain = cfg.require("domain");
|
|
@@ -44,6 +49,17 @@ const ownerEmail = cfg.require("ownerEmail");
|
|
|
44
49
|
const caeloDir = cfg.get("caeloDir") ?? "./.caelo";
|
|
45
50
|
// #553 — same knob as every adapter; undefined = stack-contract default.
|
|
46
51
|
const adminMemory = cfg.get("adminMemory");
|
|
52
|
+
// #1140 — how often the scheduler container calls the admin's tick (1–15
|
|
53
|
+
// minutes, default 5). `init --scheduler-interval-minutes` records it in
|
|
54
|
+
// <caeloDir>/config.json, which the CLI renders from; the stack reads the
|
|
55
|
+
// same record so a `pulumi up` does not put it back to the default, and
|
|
56
|
+
// refuses a Pulumi key that disagrees with it.
|
|
57
|
+
const caeloConfigPath = resolve(caeloDir, "config.json");
|
|
58
|
+
const schedulerIntervalMinutes = selfHostedSchedulerIntervalMinutes(
|
|
59
|
+
cfg.getNumber("schedulerIntervalMinutes"),
|
|
60
|
+
recordedSchedulerIntervalMinutes(caeloConfigPath),
|
|
61
|
+
caeloConfigPath,
|
|
62
|
+
);
|
|
47
63
|
// #696 — the prebuilt, cosign-verified release images, pinned by digest
|
|
48
64
|
// (`ghcr.io/caelo-cms/<svc>@sha256:…`; the generator refuses anything else).
|
|
49
65
|
// `cms-provision init` resolves + verifies them and records them in
|
|
@@ -69,21 +85,30 @@ const postgresPassword = pulumi.secret(randomHex(32));
|
|
|
69
85
|
// public_role one (#635: no cms_admin login).
|
|
70
86
|
const adminRolePassword = pulumi.secret(randomHex(32));
|
|
71
87
|
const publicRolePassword = pulumi.secret(randomHex(32));
|
|
88
|
+
// #1329 — the staging gateway's own login on cms_public_staging.
|
|
89
|
+
const publicStagingRolePassword = pulumi.secret(randomHex(32));
|
|
72
90
|
// P18 — the KEK that encrypts stored provider keys.
|
|
73
91
|
const caeloSecretKek = pulumi.secret(randomHex(32));
|
|
74
92
|
|
|
75
93
|
const composeYaml = pulumi
|
|
76
|
-
.all([
|
|
77
|
-
|
|
94
|
+
.all([
|
|
95
|
+
postgresPassword,
|
|
96
|
+
adminRolePassword,
|
|
97
|
+
publicRolePassword,
|
|
98
|
+
publicStagingRolePassword,
|
|
99
|
+
caeloSecretKek,
|
|
100
|
+
])
|
|
101
|
+
.apply(([pgPw, adminPw, publicPw, publicStagingPw, kek]) =>
|
|
78
102
|
generateDockerCompose({
|
|
79
103
|
domain,
|
|
80
104
|
postgresPassword: pgPw,
|
|
81
|
-
rolePasswords: { admin: adminPw, public: publicPw },
|
|
105
|
+
rolePasswords: { admin: adminPw, public: publicPw, publicStaging: publicStagingPw },
|
|
82
106
|
caeloSecretKek: kek,
|
|
83
107
|
anthropicApiKey: process.env.ANTHROPIC_API_KEY,
|
|
84
108
|
resendApiKey: process.env.RESEND_API_KEY,
|
|
85
109
|
diskSize: "20Gi",
|
|
86
110
|
...(adminMemory ? { adminMemory } : {}),
|
|
111
|
+
...(schedulerIntervalMinutes !== undefined ? { schedulerIntervalMinutes } : {}),
|
|
87
112
|
images: {
|
|
88
113
|
mode: "release",
|
|
89
114
|
admin: adminImage,
|
|
@@ -144,7 +169,7 @@ const composeUp = new local.Command(
|
|
|
144
169
|
// install's admin image, then everything else.
|
|
145
170
|
create: pulumi.interpolate`set -e
|
|
146
171
|
docker compose -f ${composePath} up -d --wait postgres
|
|
147
|
-
for target in
|
|
172
|
+
for target in ${MIGRATION_TARGETS.join(" ")}; do
|
|
148
173
|
docker compose -f ${composePath} run --rm --no-deps -T caelo-admin bun --bun /app/packages/migrations/src/migrate.ts "$target"
|
|
149
174
|
done
|
|
150
175
|
docker compose -f ${composePath} up -d --remove-orphans`,
|