@caelo-cms/provisioning 0.11.9 → 0.11.11
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/admin-domain.d.ts +10 -1
- package/dist/admin-domain.d.ts.map +1 -1
- package/dist/admin-domain.js +13 -3
- package/dist/admin-domain.js.map +1 -1
- package/dist/adopt-existing.d.ts +12 -3
- package/dist/adopt-existing.d.ts.map +1 -1
- package/dist/adopt-existing.js +143 -0
- package/dist/adopt-existing.js.map +1 -1
- package/dist/agent/command.d.ts +23 -0
- package/dist/agent/command.d.ts.map +1 -1
- package/dist/agent/command.js +32 -9
- package/dist/agent/command.js.map +1 -1
- package/dist/agent/run.d.ts +2 -0
- package/dist/agent/run.d.ts.map +1 -1
- package/dist/agent/run.js +5 -1
- package/dist/agent/run.js.map +1 -1
- package/dist/agent-db.js +69 -10
- package/dist/aws-task-roles.d.ts +4 -2
- package/dist/aws-task-roles.d.ts.map +1 -1
- package/dist/aws-task-roles.js +4 -1
- package/dist/aws-task-roles.js.map +1 -1
- package/dist/caddy.d.ts +21 -4
- package/dist/caddy.d.ts.map +1 -1
- package/dist/caddy.js +27 -3
- package/dist/caddy.js.map +1 -1
- package/dist/cli-routing.js +1 -1
- package/dist/cli-routing.js.map +1 -1
- package/dist/cli.js +68 -7
- package/dist/cli.js.map +1 -1
- package/dist/cloudsql-tls.d.ts +93 -0
- package/dist/cloudsql-tls.d.ts.map +1 -0
- package/dist/cloudsql-tls.js +295 -0
- package/dist/cloudsql-tls.js.map +1 -0
- package/dist/compose.d.ts +43 -6
- package/dist/compose.d.ts.map +1 -1
- package/dist/compose.js +148 -23
- package/dist/compose.js.map +1 -1
- package/dist/converge-checkpoint.d.ts +1 -0
- package/dist/converge-checkpoint.d.ts.map +1 -1
- package/dist/converge-checkpoint.js +8 -1
- package/dist/converge-checkpoint.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 +5 -2
- package/dist/converge-job-gcp.d.ts.map +1 -1
- package/dist/converge-job-gcp.js +11 -13
- package/dist/converge-job-gcp.js.map +1 -1
- package/dist/converge-job-preflight.d.ts.map +1 -1
- package/dist/converge-job-preflight.js +8 -0
- package/dist/converge-job-preflight.js.map +1 -1
- package/dist/converge-job.d.ts +17 -0
- package/dist/converge-job.d.ts.map +1 -1
- package/dist/converge-job.js +12 -0
- package/dist/converge-job.js.map +1 -1
- package/dist/deployed-release.d.ts +4 -1
- package/dist/deployed-release.d.ts.map +1 -1
- package/dist/deployed-release.js +18 -8
- package/dist/deployed-release.js.map +1 -1
- package/dist/gcp-names.d.ts +34 -0
- package/dist/gcp-names.d.ts.map +1 -1
- package/dist/gcp-names.js +45 -0
- package/dist/gcp-names.js.map +1 -1
- package/dist/gcp-url-map.d.ts.map +1 -1
- package/dist/gcp-url-map.js +6 -2
- package/dist/gcp-url-map.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- 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 +45 -1
- package/dist/infra-contract.js.map +1 -1
- package/dist/install-job.d.ts +4 -2
- package/dist/install-job.d.ts.map +1 -1
- package/dist/install-job.js +4 -1
- package/dist/install-job.js.map +1 -1
- package/dist/install-state.d.ts +13 -0
- package/dist/install-state.d.ts.map +1 -1
- package/dist/install-state.js +17 -4
- package/dist/install-state.js.map +1 -1
- package/dist/lifecycle.d.ts.map +1 -1
- package/dist/lifecycle.js +20 -8
- package/dist/lifecycle.js.map +1 -1
- package/dist/mcp-auth-proxy-setup.d.ts +48 -0
- package/dist/mcp-auth-proxy-setup.d.ts.map +1 -0
- package/dist/mcp-auth-proxy-setup.js +280 -0
- package/dist/mcp-auth-proxy-setup.js.map +1 -0
- 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/release-manifest.d.ts +25 -1
- package/dist/release-manifest.d.ts.map +1 -1
- package/dist/release-manifest.js +36 -3
- package/dist/release-manifest.js.map +1 -1
- package/dist/remote-mcp.d.ts +55 -18
- package/dist/remote-mcp.d.ts.map +1 -1
- package/dist/remote-mcp.js +307 -77
- package/dist/remote-mcp.js.map +1 -1
- package/dist/runtime-secrets.d.ts +11 -0
- package/dist/runtime-secrets.d.ts.map +1 -1
- package/dist/runtime-secrets.js +40 -25
- 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/seal-key-rotation.d.ts +7 -7
- package/dist/seal-key-rotation.d.ts.map +1 -1
- package/dist/seal-key-rotation.js +22 -22
- package/dist/seal-key-rotation.js.map +1 -1
- package/dist/self-hosted-db-tls.d.ts +37 -0
- package/dist/self-hosted-db-tls.d.ts.map +1 -0
- package/dist/self-hosted-db-tls.js +220 -0
- package/dist/self-hosted-db-tls.js.map +1 -0
- package/dist/self-hosted-release.d.ts +3 -1
- package/dist/self-hosted-release.d.ts.map +1 -1
- package/dist/self-hosted-release.js +33 -9
- package/dist/self-hosted-release.js.map +1 -1
- package/dist/self-hosted-roles.d.ts +43 -2
- package/dist/self-hosted-roles.d.ts.map +1 -1
- package/dist/self-hosted-roles.js +87 -1
- 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-start.d.ts +0 -21
- package/dist/self-hosted-start.d.ts.map +1 -1
- package/dist/self-hosted-start.js +39 -1
- package/dist/self-hosted-start.js.map +1 -1
- package/dist/self-hosted-upgrade.d.ts +19 -7
- package/dist/self-hosted-upgrade.d.ts.map +1 -1
- package/dist/self-hosted-upgrade.js +78 -25
- package/dist/self-hosted-upgrade.js.map +1 -1
- package/dist/stack-contract.d.ts +234 -28
- package/dist/stack-contract.d.ts.map +1 -1
- package/dist/stack-contract.js +397 -39
- package/dist/stack-contract.js.map +1 -1
- package/dist/stack-converge.d.ts +31 -0
- package/dist/stack-converge.d.ts.map +1 -1
- package/dist/stack-converge.js +71 -30
- package/dist/stack-converge.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/app-roll.d.ts +25 -4
- package/dist/upgrade/app-roll.d.ts.map +1 -1
- package/dist/upgrade/app-roll.js +63 -17
- package/dist/upgrade/app-roll.js.map +1 -1
- package/dist/upgrade/converge.d.ts +5 -0
- package/dist/upgrade/converge.d.ts.map +1 -1
- package/dist/upgrade/converge.js +207 -20
- package/dist/upgrade/converge.js.map +1 -1
- package/dist/upgrade/health.d.ts +31 -7
- package/dist/upgrade/health.d.ts.map +1 -1
- package/dist/upgrade/health.js +41 -17
- package/dist/upgrade/health.js.map +1 -1
- package/dist/upgrade/image-forms.d.ts +1 -1
- package/dist/upgrade/image-forms.d.ts.map +1 -1
- package/dist/upgrade/image-forms.js +24 -7
- package/dist/upgrade/image-forms.js.map +1 -1
- package/dist/upgrade/release-images.d.ts +12 -3
- package/dist/upgrade/release-images.d.ts.map +1 -1
- package/dist/upgrade/release-images.js +13 -4
- package/dist/upgrade/release-images.js.map +1 -1
- package/dist/upgrade/upgrade.d.ts +19 -3
- package/dist/upgrade/upgrade.d.ts.map +1 -1
- package/dist/upgrade/upgrade.js +37 -2
- package/dist/upgrade/upgrade.js.map +1 -1
- package/dist/upgrader-gcp.d.ts +13 -0
- package/dist/upgrader-gcp.d.ts.map +1 -1
- package/dist/upgrader-gcp.js +50 -1
- package/dist/upgrader-gcp.js.map +1 -1
- package/dist/upgrader-iam-preflight.d.ts.map +1 -1
- package/dist/upgrader-iam-preflight.js +6 -1
- package/dist/upgrader-iam-preflight.js.map +1 -1
- package/dist/upgrader-job.d.ts +1 -1
- package/dist/upgrader-job.d.ts.map +1 -1
- package/dist/upgrader-job.js +20 -14
- package/dist/upgrader-job.js.map +1 -1
- package/dist/upgrader-records.d.ts +1 -1
- package/dist/upgrader-records.d.ts.map +1 -1
- package/dist/upgrader-records.js +26 -7
- package/dist/upgrader-records.js.map +1 -1
- package/dist/upgrader-setup.d.ts.map +1 -1
- package/dist/upgrader-setup.js +10 -5
- package/dist/upgrader-setup.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 +103 -18
- package/dist/wizards/gcp-cost.js.map +1 -1
- package/dist/wizards/gcp.d.ts.map +1 -1
- package/dist/wizards/gcp.js +129 -27
- package/dist/wizards/gcp.js.map +1 -1
- package/package.json +2 -2
- package/src/adapter.ts +6 -0
- package/src/admin-domain.test.ts +9 -8
- package/src/admin-domain.ts +13 -3
- package/src/admin-memory.test.ts +2 -1
- package/src/adopt-existing.test.ts +85 -0
- package/src/adopt-existing.ts +141 -3
- package/src/agent/command.test.ts +49 -1
- package/src/agent/command.ts +39 -9
- package/src/agent/run.test.ts +19 -0
- package/src/agent/run.ts +11 -2
- package/src/aws-task-roles.ts +5 -2
- package/src/caddy-redirects.docker.test.ts +2 -0
- package/src/caddy-redirects.test.ts +4 -1
- package/src/caddy-validate.test.ts +7 -1
- package/src/caddy.test.ts +39 -1
- package/src/caddy.ts +36 -4
- package/src/checkpoint-secrets.test.ts +14 -6
- package/src/cli-routing.test.ts +1 -1
- package/src/cli-routing.ts +1 -1
- package/src/cli.ts +71 -5
- package/src/cloudsql-tls.test.ts +255 -0
- package/src/cloudsql-tls.ts +359 -0
- package/src/compose.ts +193 -24
- package/src/converge-checkpoint.ts +8 -1
- package/src/converge-db-trust.test.ts +237 -0
- package/src/converge-db-trust.ts +212 -0
- package/src/converge-job-gcp.ts +11 -12
- package/src/converge-job-preflight.test.ts +5 -0
- package/src/converge-job-preflight.ts +8 -0
- package/src/converge-job.test.ts +70 -0
- package/src/converge-job.ts +32 -0
- package/src/database-tls-builders.test.ts +56 -0
- package/src/deployed-release.test.ts +107 -40
- package/src/deployed-release.ts +30 -9
- package/src/gateway-env-isolation.test.ts +50 -12
- package/src/gcp-names.ts +56 -0
- package/src/gcp-url-map.test.ts +6 -8
- package/src/gcp-url-map.ts +6 -2
- package/src/index.test.ts +10 -4
- package/src/index.ts +7 -0
- package/src/infra-contract.test.ts +7 -3
- package/src/infra-contract.ts +45 -1
- package/src/install-job.ts +4 -1
- package/src/install-state.ts +33 -4
- package/src/lifecycle.ts +23 -11
- package/src/mcp-auth-proxy-setup.test.ts +305 -0
- package/src/mcp-auth-proxy-setup.ts +374 -0
- package/src/mcp-iap-stacks.test.ts +1 -1
- package/src/media-volume.test.ts +1 -1
- package/src/migration-runner.ts +14 -9
- package/src/migration-targets.ts +12 -0
- package/src/operator-access-stacks.test.ts +1 -1
- package/src/operator-access.test.ts +20 -4
- package/src/operator-access.ts +14 -0
- package/src/pool-budget-stack.ts +15 -0
- package/src/pool-budget-stacks.test.ts +39 -14
- 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/release-manifest.test.ts +25 -0
- package/src/release-manifest.ts +59 -4
- package/src/remote-mcp-stack-config.test.ts +19 -9
- package/src/remote-mcp.test.ts +252 -57
- package/src/remote-mcp.ts +336 -77
- package/src/runtime-secrets.test.ts +37 -14
- package/src/runtime-secrets.ts +87 -27
- package/src/scheduler-setup.test.ts +201 -0
- package/src/scheduler-setup.ts +375 -0
- package/src/seal-key-rotation.test.ts +2 -2
- package/src/seal-key-rotation.ts +25 -25
- package/src/self-hosted-backup.test.ts +1 -0
- package/src/self-hosted-compose.test.ts +180 -6
- package/src/self-hosted-db-tls.test.ts +111 -0
- package/src/self-hosted-db-tls.ts +287 -0
- package/src/self-hosted-release-resolve.test.ts +71 -0
- package/src/self-hosted-release.ts +41 -11
- package/src/self-hosted-roles.integration.test.ts +2 -0
- package/src/self-hosted-roles.test.ts +87 -3
- package/src/self-hosted-roles.ts +101 -3
- 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 +35 -2
- package/src/self-hosted-start.ts +20 -2
- package/src/self-hosted-upgrade.test.ts +104 -14
- package/src/self-hosted-upgrade.ts +123 -31
- package/src/stack-contract-parity.test.ts +79 -12
- package/src/stack-contract.test.ts +199 -31
- package/src/stack-contract.ts +472 -45
- package/src/stack-converge-secrets.test.ts +28 -13
- package/src/stack-converge.test.ts +203 -28
- package/src/stack-converge.ts +92 -30
- package/src/staging-database.test.ts +114 -0
- package/src/staging-database.ts +148 -0
- package/src/state-store.test.ts +2 -1
- package/src/upgrade/app-roll.ts +97 -19
- package/src/upgrade/converge.ts +249 -19
- package/src/upgrade/health.test.ts +63 -25
- package/src/upgrade/health.ts +54 -22
- package/src/upgrade/image-forms.test.ts +34 -0
- package/src/upgrade/image-forms.ts +23 -8
- package/src/upgrade/release-images.test.ts +5 -1
- package/src/upgrade/release-images.ts +22 -6
- package/src/upgrade/upgrade.test.ts +438 -46
- package/src/upgrade/upgrade.ts +64 -6
- package/src/upgrader-gcp.ts +66 -3
- package/src/upgrader-iam-preflight.test.ts +19 -0
- package/src/upgrader-iam-preflight.ts +6 -0
- package/src/upgrader-job.test.ts +101 -27
- package/src/upgrader-job.ts +27 -14
- package/src/upgrader-records.test.ts +97 -12
- package/src/upgrader-records.ts +28 -11
- package/src/upgrader-setup.test.ts +2 -0
- package/src/upgrader-setup.ts +10 -4
- package/src/wizard.ts +19 -0
- package/src/wizards/gcp-cost.test.ts +103 -0
- package/src/wizards/gcp-cost.ts +125 -18
- package/src/wizards/gcp.ts +164 -22
- package/stacks/aws/Pulumi.yaml +1 -1
- package/stacks/aws/index.ts +72 -14
- package/stacks/azure/index.ts +108 -21
- package/stacks/gcp/Pulumi.yaml +1 -1
- package/stacks/gcp/README.md +1 -0
- package/stacks/gcp/index.ts +133 -24
- package/stacks/gcp-firebase/Pulumi.yaml +1 -1
- package/stacks/gcp-firebase/index.ts +212 -34
- package/stacks/self-hosted/Pulumi.yaml +3 -0
- package/stacks/self-hosted/README.md +6 -1
- package/stacks/self-hosted/index.ts +40 -5
package/src/wizards/gcp-cost.ts
CHANGED
|
@@ -11,7 +11,12 @@
|
|
|
11
11
|
* + AI calls + storage growth on top.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
ADMIN_MEMORY_DEFAULT,
|
|
16
|
+
memoryQuantityMiB,
|
|
17
|
+
parseSchedulerIntervalMinutes,
|
|
18
|
+
SERVICE_CPU_IDLE,
|
|
19
|
+
} from "../stack-contract.js";
|
|
15
20
|
|
|
16
21
|
export interface CostLine {
|
|
17
22
|
name: string;
|
|
@@ -41,6 +46,82 @@ export interface CostEstimateInputs {
|
|
|
41
46
|
provider?: "gcp" | "gcp-firebase";
|
|
42
47
|
/** #607 — the install region; shown in the table (prices are {@link PRICED_REGION} rates). */
|
|
43
48
|
region: string;
|
|
49
|
+
/** #1140 — the `schedulerIntervalMinutes` knob (1–15); undefined: the default. */
|
|
50
|
+
schedulerIntervalMinutes?: number;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* #1140 — Workflows steps one scheduler tick costs (scheduler-setup.ts
|
|
55
|
+
* schedulerWorkflowSource): the claims `assign`, the signJwt call (a Google
|
|
56
|
+
* API, billed as an internal step), the `return` — and the POST to the
|
|
57
|
+
* admin, the one external HTTP step.
|
|
58
|
+
*/
|
|
59
|
+
export const SCHEDULER_TICK_STEPS = { internal: 3, external: 1 } as const;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Workflows list prices (USD, cloud.google.com/workflows/pricing, as of
|
|
63
|
+
* 2026-11) and the monthly free tier per billing account.
|
|
64
|
+
*/
|
|
65
|
+
const WORKFLOWS_PRICE = {
|
|
66
|
+
internalPer1000: 0.01,
|
|
67
|
+
externalPer1000: 0.025,
|
|
68
|
+
freeInternal: 5_000,
|
|
69
|
+
freeExternal: 2_000,
|
|
70
|
+
} as const;
|
|
71
|
+
|
|
72
|
+
/** Cloud Scheduler: $0.10 per job a month; the first 3 per billing account are free. */
|
|
73
|
+
const SCHEDULER_JOB_USD = 0.1;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* #1140 — what the scheduler tick costs a month at `minutes` between ticks:
|
|
77
|
+
* the Cloud Scheduler job (priced as paid — the 3 free jobs are shared
|
|
78
|
+
* with every other project on the billing account) plus the Workflows
|
|
79
|
+
* steps past the free tier. What the tick costs on the admin's side is
|
|
80
|
+
* priced on the admin's own line ({@link adminKeptWarmByTicks}).
|
|
81
|
+
*/
|
|
82
|
+
export function schedulerTickMonthlyUsd(minutes: number): number {
|
|
83
|
+
const ticks = Math.ceil((30 * 24 * 60) / parseSchedulerIntervalMinutes(minutes));
|
|
84
|
+
const internal = Math.max(
|
|
85
|
+
0,
|
|
86
|
+
ticks * SCHEDULER_TICK_STEPS.internal - WORKFLOWS_PRICE.freeInternal,
|
|
87
|
+
);
|
|
88
|
+
const external = Math.max(
|
|
89
|
+
0,
|
|
90
|
+
ticks * SCHEDULER_TICK_STEPS.external - WORKFLOWS_PRICE.freeExternal,
|
|
91
|
+
);
|
|
92
|
+
const usd =
|
|
93
|
+
SCHEDULER_JOB_USD +
|
|
94
|
+
(internal / 1000) * WORKFLOWS_PRICE.internalPer1000 +
|
|
95
|
+
(external / 1000) * WORKFLOWS_PRICE.externalPer1000;
|
|
96
|
+
return Math.round(usd * 100) / 100;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Cloud Run instance-based billing (CPU always allocated) list rates, USD,
|
|
101
|
+
* tier-1 region, cloud.google.com/run/pricing as of 2026-11. The free tier
|
|
102
|
+
* is left out: it is shared by every service on the billing account.
|
|
103
|
+
*/
|
|
104
|
+
const CLOUD_RUN_INSTANCE_PRICE = { vcpuSecond: 0.000018, gibSecond: 0.000002 } as const;
|
|
105
|
+
|
|
106
|
+
/** Seconds in the 30-day month the table prices. */
|
|
107
|
+
const MONTH_SECONDS = 30 * 24 * 60 * 60;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* #1140 — whether the scheduler tick keeps a scale-to-zero admin instance
|
|
111
|
+
* up around the clock. Cloud Run keeps an idle instance for up to 15
|
|
112
|
+
* minutes after its last request, and the tick comes at most every 15
|
|
113
|
+
* minutes, so with CPU always allocated (`SERVICE_CPU_IDLE.admin` false
|
|
114
|
+
* until #1361) the instance never stops and is billed every second. With
|
|
115
|
+
* request-billed CPU an idle instance costs nothing between ticks.
|
|
116
|
+
*/
|
|
117
|
+
export function adminKeptWarmByTicks(adminMinInstances: number): boolean {
|
|
118
|
+
return adminMinInstances === 0 && !SERVICE_CPU_IDLE.admin;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** One always-on admin instance (1 vCPU, `gib` memory) a month at instance-based list rates. */
|
|
122
|
+
export function alwaysOnAdminInstanceUsd(gib: number): number {
|
|
123
|
+
const perSecond = CLOUD_RUN_INSTANCE_PRICE.vcpuSecond + gib * CLOUD_RUN_INSTANCE_PRICE.gibSecond;
|
|
124
|
+
return Math.round(perSecond * MONTH_SECONDS);
|
|
44
125
|
}
|
|
45
126
|
|
|
46
127
|
/** The region the price table's list rates are for. */
|
|
@@ -71,6 +152,7 @@ export function estimateGcpCost(inputs: CostEstimateInputs): {
|
|
|
71
152
|
const sqlMonthly = Math.round(sqlBase * sqlMultiplier);
|
|
72
153
|
|
|
73
154
|
const provider = inputs.provider ?? "gcp";
|
|
155
|
+
const schedulerMinutes = parseSchedulerIntervalMinutes(inputs.schedulerIntervalMinutes);
|
|
74
156
|
const adminMemory = inputs.adminMemory ?? ADMIN_MEMORY_DEFAULT;
|
|
75
157
|
const adminMiB = memoryQuantityMiB(adminMemory);
|
|
76
158
|
if (adminMiB === null) {
|
|
@@ -91,17 +173,20 @@ export function estimateGcpCost(inputs: CostEstimateInputs): {
|
|
|
91
173
|
},
|
|
92
174
|
{
|
|
93
175
|
name: `Cloud Run admin (${adminMemory})`,
|
|
94
|
-
// FIXME(issue #
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
|
|
98
|
-
|
|
176
|
+
// FIXME(issue #1361): with request-billed CPU (SERVICE_CPU_IDLE) the
|
|
177
|
+
// ticks stop keeping the instance up and this line drops to ~$7 of
|
|
178
|
+
// editing sessions. Until then the scheduler tick (#1140) keeps one
|
|
179
|
+
// instance up around the clock, billed at the always-on rate.
|
|
180
|
+
monthlyUsd: adminKeptWarmByTicks(inputs.adminMinInstances)
|
|
181
|
+
? alwaysOnAdminInstanceUsd(adminGiB)
|
|
182
|
+
: inputs.adminMinInstances === 0
|
|
99
183
|
? 7
|
|
100
184
|
: Math.round(1 + inputs.adminMinInstances * perMinInstanceUsd),
|
|
101
|
-
notes:
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
185
|
+
notes: adminKeptWarmByTicks(inputs.adminMinInstances)
|
|
186
|
+
? `scale-to-zero, but CPU is always allocated and the scheduler tick (every ${schedulerMinutes} min) comes before Cloud Run's 15-minute idle shutdown, so one instance (1 vCPU, ${adminMemory}) runs around the clock until request-billed CPU lands (#1361)`
|
|
187
|
+
: inputs.adminMinInstances === 0
|
|
188
|
+
? "scale-to-zero, request-billed CPU; ~$7/mo light editorial use, ticks inside the free tier"
|
|
189
|
+
: `${inputs.adminMinInstances} min-instance${inputs.adminMinInstances > 1 ? "s" : ""} (no cold start); the scheduler tick adds nothing, the instance is up anyway`,
|
|
105
190
|
},
|
|
106
191
|
{
|
|
107
192
|
name: "Quality checks (Cloud Run audit job)",
|
|
@@ -116,13 +201,24 @@ export function estimateGcpCost(inputs: CostEstimateInputs): {
|
|
|
116
201
|
"one short container per server-side screenshot or page render (~20-40 s at 1 vCPU / 2 GiB): ~$0.001 each, a 50-page site import ~$0.05; mostly in the free tier; capture bucket ~$0",
|
|
117
202
|
},
|
|
118
203
|
{
|
|
119
|
-
|
|
204
|
+
// #1328 — on gcp-firebase the visitor gateway is the new
|
|
205
|
+
// `caelo-<env>-data-gateway` service; same billing either way.
|
|
206
|
+
name: provider === "gcp" ? "Cloud Run gateway" : "Cloud Run data gateway",
|
|
120
207
|
monthlyUsd: inputs.gatewayMinInstances === 0 ? 1 : 1 + inputs.gatewayMinInstances * 15,
|
|
121
208
|
notes:
|
|
122
209
|
inputs.gatewayMinInstances === 0
|
|
123
210
|
? "scale-to-zero; ~$1/mo light traffic"
|
|
124
211
|
: `${inputs.gatewayMinInstances} min-instance${inputs.gatewayMinInstances > 1 ? "s" : ""}`,
|
|
125
212
|
},
|
|
213
|
+
{
|
|
214
|
+
// #1328 — remote MCP's sign-in service, its own service on every
|
|
215
|
+
// install (E14 a of #763): always provisioned, zero minimum instances,
|
|
216
|
+
// CPU only during requests, so it costs nothing while nobody connects.
|
|
217
|
+
name: "Cloud Run MCP-Auth-Proxy",
|
|
218
|
+
monthlyUsd: 0,
|
|
219
|
+
notes:
|
|
220
|
+
"scale-to-zero, request-based CPU; only external AI apps' sign-in and MCP calls wake it, mostly in the free tier",
|
|
221
|
+
},
|
|
126
222
|
// v0.3.3 — LB / Cloud CDN / Cloud Armor lines apply only to the
|
|
127
223
|
// gcp variant. gcp-firebase has no LB (Firebase Hosting is the
|
|
128
224
|
// edge); no Cloud CDN line (Firebase Hosting has its own CDN
|
|
@@ -148,12 +244,18 @@ export function estimateGcpCost(inputs: CostEstimateInputs): {
|
|
|
148
244
|
: "Free tier — rate limit + OWASP basic rules",
|
|
149
245
|
},
|
|
150
246
|
{
|
|
151
|
-
// #
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
247
|
+
// #1328 — the MCP-Auth-Proxy's own backend (serverless NEG +
|
|
248
|
+
// backend service, no extra LB fee: same URL map) with its own
|
|
249
|
+
// Cloud Armor policy carrying the remote-MCP throttle
|
|
250
|
+
// (src/remote-mcp-edge.ts) and the default rule. Cloud Armor
|
|
251
|
+
// Standard: $0.006849315/policy-hour (~$5/mo) and
|
|
252
|
+
// $0.001369863/rule-hour (~$1/mo), plus $0.75 per million
|
|
253
|
+
// requests on a global policy — cloud.google.com/armor/pricing,
|
|
254
|
+
// checked 2026-11-02.
|
|
255
|
+
name: "MCP-Auth-Proxy backend + Cloud Armor policy",
|
|
256
|
+
monthlyUsd: 7,
|
|
257
|
+
notes:
|
|
258
|
+
"own policy ($5) with 2 rules ($2): per-IP throttle for remote MCP clients; requests ~$0",
|
|
157
259
|
},
|
|
158
260
|
] satisfies CostLine[])
|
|
159
261
|
: ([
|
|
@@ -175,7 +277,7 @@ export function estimateGcpCost(inputs: CostEstimateInputs): {
|
|
|
175
277
|
name: "Secret Manager (runtime secrets)",
|
|
176
278
|
monthlyUsd: 0.2,
|
|
177
279
|
notes:
|
|
178
|
-
"6 active versions free, then ~$0.06 each; the Google sign-in client (#914)
|
|
280
|
+
"6 active versions free, then ~$0.06 each; the Google sign-in client (#914), the remote-MCP seal key (#806) and the staging database login's password (#1329; the staging database itself shares the Cloud SQL instance at no extra cost)",
|
|
179
281
|
},
|
|
180
282
|
{
|
|
181
283
|
name: "BigQuery edge log sink",
|
|
@@ -194,6 +296,11 @@ export function estimateGcpCost(inputs: CostEstimateInputs): {
|
|
|
194
296
|
notes:
|
|
195
297
|
"Hourly ~20 s job keeps Google IAP in step with the user list; within the Cloud Run free tier, 1 of 3 free scheduler jobs",
|
|
196
298
|
},
|
|
299
|
+
{
|
|
300
|
+
name: `Scheduler tick (Cloud Scheduler + Workflows, every ${schedulerMinutes} min)`,
|
|
301
|
+
monthlyUsd: schedulerTickMonthlyUsd(schedulerMinutes),
|
|
302
|
+
notes: `one job starts a ${SCHEDULER_TICK_STEPS.internal + SCHEDULER_TICK_STEPS.external}-step workflow that calls the admin's periodic work and plugin workers; ~${Math.ceil((30 * 24 * 60) / schedulerMinutes)} ticks/mo; what the ticks cost on the admin is on the admin line`,
|
|
303
|
+
},
|
|
197
304
|
];
|
|
198
305
|
|
|
199
306
|
// Cents, so a sub-dollar line (the KMS key) doesn't print float noise.
|
package/src/wizards/gcp.ts
CHANGED
|
@@ -30,6 +30,7 @@ import { randomBytes } from "node:crypto";
|
|
|
30
30
|
import { existsSync, readFileSync } from "node:fs";
|
|
31
31
|
import { homedir } from "node:os";
|
|
32
32
|
import { join } from "node:path";
|
|
33
|
+
import { CLOUD_SQL_CA_BUNDLE, verifyFullQuery } from "@caelo-cms/shared";
|
|
33
34
|
import { cancel, confirm, isCancel, log, note, select, spinner } from "@clack/prompts";
|
|
34
35
|
import { bold, cyan, dim, green, red, yellow } from "kleur/colors";
|
|
35
36
|
import { checkAdcImpersonation } from "../adc-impersonation.js";
|
|
@@ -67,7 +68,11 @@ import {
|
|
|
67
68
|
REQUIRED_API_LIST,
|
|
68
69
|
serviceAccountExists,
|
|
69
70
|
} from "../gcloud.js";
|
|
70
|
-
import {
|
|
71
|
+
import {
|
|
72
|
+
GCP_STACK_ENV,
|
|
73
|
+
schedulerServiceAccountEmail,
|
|
74
|
+
upgraderServiceAccountEmail,
|
|
75
|
+
} from "../gcp-names.js";
|
|
71
76
|
import { INFRA_CONTRACT_VERSION } from "../infra-contract.js";
|
|
72
77
|
import {
|
|
73
78
|
INSTALL_JOB_NAME,
|
|
@@ -115,9 +120,12 @@ import {
|
|
|
115
120
|
resolveStackRemoteMcpIdentity,
|
|
116
121
|
} from "../remote-mcp.js";
|
|
117
122
|
import type { RunMode } from "../run-mode.js";
|
|
123
|
+
import { ensureSchedulerTick } from "../scheduler-setup.js";
|
|
118
124
|
import { selfCommand } from "../self-command.js";
|
|
119
125
|
import {
|
|
120
126
|
ABSENT_DEFAULT_FIREWALL_RULES,
|
|
127
|
+
cloudSqlPrivateDnsName,
|
|
128
|
+
parseSchedulerIntervalMinutes,
|
|
121
129
|
type RemoteMcpIdentity,
|
|
122
130
|
sameRemoteMcpIdentity,
|
|
123
131
|
} from "../stack-contract.js";
|
|
@@ -134,6 +142,7 @@ import {
|
|
|
134
142
|
import { acquireStateLock, GcsStateStore } from "../state-store.js";
|
|
135
143
|
import { createReleaseImageForms, platformDigestsOf } from "../upgrade/image-forms.js";
|
|
136
144
|
import {
|
|
145
|
+
releaseImageRef,
|
|
137
146
|
releaseVersionForDigest,
|
|
138
147
|
resolveOpsImageRef,
|
|
139
148
|
verifyCosignAll,
|
|
@@ -231,7 +240,15 @@ export async function runGcpWizard(opts: GcpWizardOpts): Promise<void> {
|
|
|
231
240
|
// drops the LB / Cloud CDN / Cloud Armor lines (saves ~$19/mo
|
|
232
241
|
// vs gcp). Default 'gcp' for backwards compatibility with
|
|
233
242
|
// installs that don't set the field.
|
|
234
|
-
const estimate = estimateGcpCost({
|
|
243
|
+
const estimate = estimateGcpCost({
|
|
244
|
+
...GCP_STACK_KNOB_DEFAULTS,
|
|
245
|
+
provider,
|
|
246
|
+
region,
|
|
247
|
+
// #1140 — the tick's line follows the knob the wizard recorded.
|
|
248
|
+
...(meta?.schedulerIntervalMinutes !== undefined
|
|
249
|
+
? { schedulerIntervalMinutes: meta.schedulerIntervalMinutes }
|
|
250
|
+
: {}),
|
|
251
|
+
});
|
|
235
252
|
note(
|
|
236
253
|
[
|
|
237
254
|
bold(
|
|
@@ -357,7 +374,7 @@ export async function runGcpConverge(opts: GcpConvergeOpts): Promise<void> {
|
|
|
357
374
|
const deployed = isStepDone(installId, `pulumi-up-${projectId}`);
|
|
358
375
|
if (deployed) {
|
|
359
376
|
// The kept release must be able to use the env the stack deploys.
|
|
360
|
-
const runtimeEnv = await checkDeployedRuntimeEnv({ projectId, region });
|
|
377
|
+
const runtimeEnv = await checkDeployedRuntimeEnv({ provider, projectId, region });
|
|
361
378
|
if (!runtimeEnv.ok) {
|
|
362
379
|
cancel(runtimeEnv.error);
|
|
363
380
|
process.exit(1);
|
|
@@ -366,7 +383,7 @@ export async function runGcpConverge(opts: GcpConvergeOpts): Promise<void> {
|
|
|
366
383
|
const choice = await chooseImageDigests({
|
|
367
384
|
recorded: recordedImageDigests(meta),
|
|
368
385
|
deployed,
|
|
369
|
-
readLive: () => readDeployedImageDigests({ projectId, region }),
|
|
386
|
+
readLive: () => readDeployedImageDigests({ provider, projectId, region }),
|
|
370
387
|
resolveLatest: resolveImageDigests,
|
|
371
388
|
});
|
|
372
389
|
if (!choice.ok) {
|
|
@@ -520,6 +537,18 @@ export async function runGcpConverge(opts: GcpConvergeOpts): Promise<void> {
|
|
|
520
537
|
// === 11.45. The converge job: releases that change the cloud setup (#819) ===
|
|
521
538
|
const convergeReady = await stepConvergeJob(projectId, region, releaseVersion);
|
|
522
539
|
|
|
540
|
+
// === 11.47. The scheduler tick: periodic work + plugin workers (#1140) ===
|
|
541
|
+
// Before operator access, which keeps the scheduler's account on IAP.
|
|
542
|
+
const schedulerReady = await stepSchedulerTick(
|
|
543
|
+
projectId,
|
|
544
|
+
region,
|
|
545
|
+
provider,
|
|
546
|
+
ownerEmail,
|
|
547
|
+
identity?.identity.adminUrl ?? null,
|
|
548
|
+
meta.schedulerIntervalMinutes,
|
|
549
|
+
);
|
|
550
|
+
const keepScheduler = schedulerReady || (await schedulerExists(projectId));
|
|
551
|
+
|
|
523
552
|
// === 11.5. Operator access: IAP follows the Caelo user list ===
|
|
524
553
|
await stepOperatorAccessSync(
|
|
525
554
|
projectId,
|
|
@@ -527,18 +556,27 @@ export async function runGcpConverge(opts: GcpConvergeOpts): Promise<void> {
|
|
|
527
556
|
provider,
|
|
528
557
|
ownerEmail,
|
|
529
558
|
keepSigner ? [`serviceAccount:${upgraderServiceAccountEmail(projectId, GCP_STACK_ENV)}`] : [],
|
|
559
|
+
keepScheduler ? [schedulerServiceAccountEmail(projectId, GCP_STACK_ENV)] : [],
|
|
530
560
|
);
|
|
531
561
|
|
|
532
562
|
// === 11.6. Remote MCP (#806): LB rules (gcp) + a check of the identity env ===
|
|
533
563
|
// The env itself came with `pulumi up` (step 10.05, #1082).
|
|
534
|
-
const remoteMcpReady = await stepRemoteMcp(
|
|
564
|
+
const remoteMcpReady = await stepRemoteMcp(
|
|
565
|
+
projectId,
|
|
566
|
+
region,
|
|
567
|
+
provider,
|
|
568
|
+
domain,
|
|
569
|
+
identity,
|
|
570
|
+
imageDigests,
|
|
571
|
+
);
|
|
535
572
|
|
|
536
573
|
// A fresh install ran every converge step above, so it is at this CLI's
|
|
537
574
|
// infrastructure contract — recorded only when the upgrader and the
|
|
538
575
|
// converge job are in place (the steps contract bumps 2 and 9 added) and
|
|
539
|
-
// remote MCP's steps (#806, bump 10)
|
|
540
|
-
// the admin can install the next
|
|
541
|
-
|
|
576
|
+
// remote MCP's steps (#806, bump 10) and the scheduler tick (#1140, bump
|
|
577
|
+
// 18) succeeded. With the release version, the admin can install the next
|
|
578
|
+
// update itself.
|
|
579
|
+
if (upgraderReady && convergeReady && remoteMcpReady && schedulerReady) {
|
|
542
580
|
const version = releaseVersion;
|
|
543
581
|
// #1025 — the linux/amd64 forms Cloud Run reports, next to the indexes,
|
|
544
582
|
// so the upgrader recognises what serves without a registry lookup.
|
|
@@ -603,8 +641,12 @@ async function resolveImageDigests(): Promise<ImageDigests> {
|
|
|
603
641
|
const project = "caelo-website";
|
|
604
642
|
const registryRegion = "europe-west1";
|
|
605
643
|
const repo = "caelo-cms-images";
|
|
606
|
-
const out
|
|
607
|
-
|
|
644
|
+
const out: Record<"admin" | "gateway" | "mcp-auth-proxy", string> = {
|
|
645
|
+
admin: "",
|
|
646
|
+
gateway: "",
|
|
647
|
+
"mcp-auth-proxy": "",
|
|
648
|
+
};
|
|
649
|
+
for (const service of ["admin", "gateway", "mcp-auth-proxy"] as const) {
|
|
608
650
|
const s = spinner();
|
|
609
651
|
s.start(`Resolving ${service}:latest → digest...`);
|
|
610
652
|
// gcloud's --filter='tag=latest' is in a transitional state (warns +
|
|
@@ -984,7 +1026,7 @@ async function stepGrantBaseRoles(
|
|
|
984
1026
|
*/
|
|
985
1027
|
async function stepPublicRoleIsolation(projectId: string): Promise<void> {
|
|
986
1028
|
const s = spinner();
|
|
987
|
-
s.start("Removing
|
|
1029
|
+
s.start("Removing the gateway logins from the Cloud SQL superuser group...");
|
|
988
1030
|
const r = await revokePublicRoleMemberships({
|
|
989
1031
|
projectId,
|
|
990
1032
|
env: GCP_STACK_ENV,
|
|
@@ -996,7 +1038,7 @@ async function stepPublicRoleIsolation(projectId: string): Promise<void> {
|
|
|
996
1038
|
cancel("Aborted.");
|
|
997
1039
|
process.exit(1);
|
|
998
1040
|
}
|
|
999
|
-
s.stop(green("public_role
|
|
1041
|
+
s.stop(green("The gateway logins (public_role, public_staging_role) hold no database roles"));
|
|
1000
1042
|
}
|
|
1001
1043
|
|
|
1002
1044
|
/**
|
|
@@ -1202,6 +1244,78 @@ async function upgraderExists(projectId: string): Promise<boolean> {
|
|
|
1202
1244
|
return r.ok;
|
|
1203
1245
|
}
|
|
1204
1246
|
|
|
1247
|
+
/** Whether the scheduler tick's account exists from an earlier run (it keeps its IAP access). */
|
|
1248
|
+
async function schedulerExists(projectId: string): Promise<boolean> {
|
|
1249
|
+
const r = await gcloud([
|
|
1250
|
+
"iam",
|
|
1251
|
+
"service-accounts",
|
|
1252
|
+
"describe",
|
|
1253
|
+
schedulerServiceAccountEmail(projectId, GCP_STACK_ENV),
|
|
1254
|
+
`--project=${projectId}`,
|
|
1255
|
+
"--format=value(email)",
|
|
1256
|
+
]);
|
|
1257
|
+
return r.ok;
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
/**
|
|
1261
|
+
* #1140 — set up the scheduler tick (scheduler-setup.ts): the one Cloud
|
|
1262
|
+
* Scheduler job that calls the admin's tick every `schedulerIntervalMinutes`
|
|
1263
|
+
* through IAP. After migrations (the admin answers the tick once its tables
|
|
1264
|
+
* exist) and before operator access. Not fatal: the site works without it,
|
|
1265
|
+
* but plugin workers, update checks and clean-up do not run, so a failure
|
|
1266
|
+
* warns loudly and the install is not recorded at this contract — the next
|
|
1267
|
+
* wizard pass or `upgrade` retries. Idempotent.
|
|
1268
|
+
*
|
|
1269
|
+
* @returns whether the tick is in place
|
|
1270
|
+
*/
|
|
1271
|
+
async function stepSchedulerTick(
|
|
1272
|
+
projectId: string,
|
|
1273
|
+
region: string,
|
|
1274
|
+
provider: "gcp" | "gcp-firebase",
|
|
1275
|
+
ownerEmail: string,
|
|
1276
|
+
adminUrl: string | null,
|
|
1277
|
+
recordedInterval: number | undefined,
|
|
1278
|
+
): Promise<boolean> {
|
|
1279
|
+
const s = spinner();
|
|
1280
|
+
s.start("Setting up the scheduler tick (periodic work and plugin workers)...");
|
|
1281
|
+
const failed = (error: string) => {
|
|
1282
|
+
s.stop(yellow(`Scheduler tick not set up: ${error}`));
|
|
1283
|
+
log.warn(
|
|
1284
|
+
`The install works, but plugin workers (newsletter sends, rating aggregates), update checks and clean-up do not run until this succeeds. Re-run the installer or ${bold(selfCommand("upgrade"))} once the cause is fixed.`,
|
|
1285
|
+
);
|
|
1286
|
+
return false;
|
|
1287
|
+
};
|
|
1288
|
+
let intervalMinutes: number;
|
|
1289
|
+
try {
|
|
1290
|
+
intervalMinutes = parseSchedulerIntervalMinutes(
|
|
1291
|
+
recordedInterval,
|
|
1292
|
+
"install.json schedulerIntervalMinutes",
|
|
1293
|
+
);
|
|
1294
|
+
} catch (e) {
|
|
1295
|
+
return failed((e as Error).message);
|
|
1296
|
+
}
|
|
1297
|
+
if (!adminUrl) return failed("the admin's URL could not be read (see the remote-MCP step above)");
|
|
1298
|
+
const resolved = await resolveOperatorAccessTarget({
|
|
1299
|
+
provider,
|
|
1300
|
+
projectId,
|
|
1301
|
+
region,
|
|
1302
|
+
env: GCP_STACK_ENV,
|
|
1303
|
+
ownerEmail,
|
|
1304
|
+
});
|
|
1305
|
+
if (!resolved.ok) return failed(resolved.error);
|
|
1306
|
+
const r = await ensureSchedulerTick({
|
|
1307
|
+
projectId,
|
|
1308
|
+
region,
|
|
1309
|
+
env: GCP_STACK_ENV,
|
|
1310
|
+
adminUrl,
|
|
1311
|
+
resource: resolved.target.resource,
|
|
1312
|
+
intervalMinutes,
|
|
1313
|
+
});
|
|
1314
|
+
if (!r.ok) return failed(r.error);
|
|
1315
|
+
s.stop(green(`Scheduler tick ready (every ${intervalMinutes} min, ${r.done.length} steps)`));
|
|
1316
|
+
return true;
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1205
1319
|
/**
|
|
1206
1320
|
* Set up the operator-access sync job (operator-access.ts): the only
|
|
1207
1321
|
* principal that may change who passes IAP, run as the operator's gcloud
|
|
@@ -1214,6 +1328,7 @@ async function stepOperatorAccessSync(
|
|
|
1214
1328
|
provider: "gcp" | "gcp-firebase",
|
|
1215
1329
|
ownerEmail: string,
|
|
1216
1330
|
mcpSigners: readonly string[],
|
|
1331
|
+
iapServiceAccounts: readonly string[],
|
|
1217
1332
|
): Promise<void> {
|
|
1218
1333
|
const s = spinner();
|
|
1219
1334
|
s.start("Setting up the operator-access sync job (Google IAP follows the user list)...");
|
|
@@ -1225,7 +1340,7 @@ async function stepOperatorAccessSync(
|
|
|
1225
1340
|
ownerEmail,
|
|
1226
1341
|
});
|
|
1227
1342
|
const r = resolved.ok
|
|
1228
|
-
? await ensureOperatorAccessSync({ ...resolved.target, mcpSigners })
|
|
1343
|
+
? await ensureOperatorAccessSync({ ...resolved.target, mcpSigners, iapServiceAccounts })
|
|
1229
1344
|
: { ok: false as const, done: [], error: resolved.error };
|
|
1230
1345
|
if (!r.ok) {
|
|
1231
1346
|
s.stop(red(`Failed: ${r.error}`));
|
|
@@ -1309,9 +1424,11 @@ async function stepResolveRemoteMcpIdentity(
|
|
|
1309
1424
|
*/
|
|
1310
1425
|
async function stepRemoteMcp(
|
|
1311
1426
|
projectId: string,
|
|
1427
|
+
region: string,
|
|
1312
1428
|
provider: "gcp" | "gcp-firebase",
|
|
1313
1429
|
domain: string,
|
|
1314
1430
|
identity: ResolvedRemoteMcpIdentity | null,
|
|
1431
|
+
imageDigests: ImageDigests,
|
|
1315
1432
|
): Promise<boolean> {
|
|
1316
1433
|
const s = spinner();
|
|
1317
1434
|
s.start(
|
|
@@ -1325,11 +1442,22 @@ async function stepRemoteMcp(
|
|
|
1325
1442
|
return false;
|
|
1326
1443
|
};
|
|
1327
1444
|
if (!identity) return failed("its URLs and IAP audience could not be read (see above)");
|
|
1328
|
-
const
|
|
1445
|
+
const proxyDigest = imageDigests["mcp-auth-proxy"];
|
|
1446
|
+
if (!proxyDigest) return failed("the release records no MCP-Auth-Proxy image");
|
|
1447
|
+
const edge = await ensureRemoteMcpEdge({
|
|
1448
|
+
provider,
|
|
1449
|
+
projectId,
|
|
1450
|
+
region,
|
|
1451
|
+
env: GCP_STACK_ENV,
|
|
1452
|
+
domain,
|
|
1453
|
+
mcpAuthProxyImageRef: releaseImageRef("mcp-auth-proxy", proxyDigest),
|
|
1454
|
+
});
|
|
1329
1455
|
if (!edge.ok) return failed(edge.error);
|
|
1330
1456
|
const check = await checkRemoteMcpIdentityEnv(identity.target, identity.identity);
|
|
1331
1457
|
if (!check.ok) return failed(check.error);
|
|
1332
|
-
const missing =
|
|
1458
|
+
const missing = Object.values(check.drift)
|
|
1459
|
+
.flat()
|
|
1460
|
+
.map((c) => c.name);
|
|
1333
1461
|
if (missing.length > 0) {
|
|
1334
1462
|
return failed(`pulumi up left ${[...new Set(missing)].join(", ")} out of the services' env`);
|
|
1335
1463
|
}
|
|
@@ -1905,8 +2033,10 @@ async function stepDrainLegacyCaeloAdminUser(projectId: string, region: string):
|
|
|
1905
2033
|
pgPass,
|
|
1906
2034
|
]);
|
|
1907
2035
|
|
|
1908
|
-
// Resolve the SQL instance's
|
|
1909
|
-
// Pulumi outputs from inside the wizard).
|
|
2036
|
+
// Resolve the SQL instance's DNS name from gcloud (avoids reading
|
|
2037
|
+
// Pulumi outputs from inside the wizard). #1338: the job connects with
|
|
2038
|
+
// verify-full, and the shared-CA certificate names the DNS name, not the
|
|
2039
|
+
// private IP.
|
|
1910
2040
|
const sqlDescr = await gcloud([
|
|
1911
2041
|
"sql",
|
|
1912
2042
|
"instances",
|
|
@@ -1914,16 +2044,28 @@ async function stepDrainLegacyCaeloAdminUser(projectId: string, region: string):
|
|
|
1914
2044
|
"caelo-production-pg-1d65811",
|
|
1915
2045
|
"--project",
|
|
1916
2046
|
projectId,
|
|
1917
|
-
"--format=
|
|
2047
|
+
"--format=json(dnsNames)",
|
|
1918
2048
|
]);
|
|
1919
|
-
|
|
2049
|
+
let pgHost: string | null = null;
|
|
2050
|
+
try {
|
|
2051
|
+
pgHost = cloudSqlPrivateDnsName(
|
|
2052
|
+
(
|
|
2053
|
+
JSON.parse(sqlDescr.stdout || "{}") as {
|
|
2054
|
+
dnsNames?: { name?: string; connectionType?: string }[];
|
|
2055
|
+
}
|
|
2056
|
+
).dnsNames,
|
|
2057
|
+
);
|
|
2058
|
+
} catch {
|
|
2059
|
+
pgHost = null;
|
|
2060
|
+
}
|
|
1920
2061
|
if (!pgHost) {
|
|
1921
|
-
s.stop(yellow("Could not resolve Cloud SQL
|
|
2062
|
+
s.stop(yellow("Could not resolve the Cloud SQL instance's DNS name — skipping drain"));
|
|
1922
2063
|
return;
|
|
1923
2064
|
}
|
|
1924
2065
|
|
|
1925
|
-
const
|
|
1926
|
-
const
|
|
2066
|
+
const tls = verifyFullQuery(CLOUD_SQL_CA_BUNDLE);
|
|
2067
|
+
const adminPg = `postgres://postgres:${pgPass}@${pgHost}:5432/cms_admin?${tls}`;
|
|
2068
|
+
const publicPg = `postgres://postgres:${pgPass}@${pgHost}:5432/cms_public?${tls}`;
|
|
1927
2069
|
|
|
1928
2070
|
// The cleanup script — uses bun's globalThis.Bun.SQL (already proven
|
|
1929
2071
|
// pattern in hooks.server.ts + the migration runner). DO block makes
|
package/stacks/aws/Pulumi.yaml
CHANGED
|
@@ -34,7 +34,7 @@ config:
|
|
|
34
34
|
description: RDS max_connections (parameter group); every service's pool size is computed from it (docs/dev/connection-budget.md).
|
|
35
35
|
caelo-aws:adminMaxInstances:
|
|
36
36
|
type: string
|
|
37
|
-
default: "
|
|
37
|
+
default: "4"
|
|
38
38
|
description: Most admin tasks at once; part of the connection budget.
|
|
39
39
|
caelo-aws:gatewayMaxInstances:
|
|
40
40
|
type: string
|