@caelo-cms/provisioning 0.11.8 → 0.11.10

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.
Files changed (261) hide show
  1. package/dist/admin-domain.d.ts +10 -1
  2. package/dist/admin-domain.d.ts.map +1 -1
  3. package/dist/admin-domain.js +13 -3
  4. package/dist/admin-domain.js.map +1 -1
  5. package/dist/adopt-existing.d.ts +6 -1
  6. package/dist/adopt-existing.d.ts.map +1 -1
  7. package/dist/adopt-existing.js +86 -0
  8. package/dist/adopt-existing.js.map +1 -1
  9. package/dist/agent/command.d.ts +20 -0
  10. package/dist/agent/command.d.ts.map +1 -1
  11. package/dist/agent/command.js +32 -9
  12. package/dist/agent/command.js.map +1 -1
  13. package/dist/agent/run.d.ts +2 -0
  14. package/dist/agent/run.d.ts.map +1 -1
  15. package/dist/agent/run.js +5 -1
  16. package/dist/agent/run.js.map +1 -1
  17. package/dist/agent-db.js +65 -3
  18. package/dist/aws-task-roles.d.ts +4 -2
  19. package/dist/aws-task-roles.d.ts.map +1 -1
  20. package/dist/aws-task-roles.js +4 -1
  21. package/dist/aws-task-roles.js.map +1 -1
  22. package/dist/caddy.d.ts +21 -4
  23. package/dist/caddy.d.ts.map +1 -1
  24. package/dist/caddy.js +27 -3
  25. package/dist/caddy.js.map +1 -1
  26. package/dist/cli.js +12 -3
  27. package/dist/cli.js.map +1 -1
  28. package/dist/cloudsql-tls.d.ts +63 -0
  29. package/dist/cloudsql-tls.d.ts.map +1 -0
  30. package/dist/cloudsql-tls.js +289 -0
  31. package/dist/cloudsql-tls.js.map +1 -0
  32. package/dist/compose.d.ts +29 -6
  33. package/dist/compose.d.ts.map +1 -1
  34. package/dist/compose.js +95 -23
  35. package/dist/compose.js.map +1 -1
  36. package/dist/converge-checkpoint.d.ts +1 -0
  37. package/dist/converge-checkpoint.d.ts.map +1 -1
  38. package/dist/converge-checkpoint.js +8 -1
  39. package/dist/converge-checkpoint.js.map +1 -1
  40. package/dist/converge-job-gcp.d.ts.map +1 -1
  41. package/dist/converge-job-gcp.js +2 -11
  42. package/dist/converge-job-gcp.js.map +1 -1
  43. package/dist/deployed-release.d.ts +4 -1
  44. package/dist/deployed-release.d.ts.map +1 -1
  45. package/dist/deployed-release.js +18 -8
  46. package/dist/deployed-release.js.map +1 -1
  47. package/dist/gcp-names.d.ts +21 -0
  48. package/dist/gcp-names.d.ts.map +1 -1
  49. package/dist/gcp-names.js +24 -0
  50. package/dist/gcp-names.js.map +1 -1
  51. package/dist/gcp-url-map.d.ts.map +1 -1
  52. package/dist/gcp-url-map.js +6 -2
  53. package/dist/gcp-url-map.js.map +1 -1
  54. package/dist/index.d.ts +1 -1
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +1 -1
  57. package/dist/index.js.map +1 -1
  58. package/dist/infra-contract.d.ts +1 -1
  59. package/dist/infra-contract.d.ts.map +1 -1
  60. package/dist/infra-contract.js +24 -1
  61. package/dist/infra-contract.js.map +1 -1
  62. package/dist/install-state.d.ts +6 -0
  63. package/dist/install-state.d.ts.map +1 -1
  64. package/dist/install-state.js +17 -4
  65. package/dist/install-state.js.map +1 -1
  66. package/dist/lifecycle.d.ts.map +1 -1
  67. package/dist/lifecycle.js +17 -8
  68. package/dist/lifecycle.js.map +1 -1
  69. package/dist/mcp-auth-proxy-setup.d.ts +48 -0
  70. package/dist/mcp-auth-proxy-setup.d.ts.map +1 -0
  71. package/dist/mcp-auth-proxy-setup.js +280 -0
  72. package/dist/mcp-auth-proxy-setup.js.map +1 -0
  73. package/dist/release-manifest.d.ts +25 -1
  74. package/dist/release-manifest.d.ts.map +1 -1
  75. package/dist/release-manifest.js +36 -3
  76. package/dist/release-manifest.js.map +1 -1
  77. package/dist/remote-mcp.d.ts +55 -18
  78. package/dist/remote-mcp.d.ts.map +1 -1
  79. package/dist/remote-mcp.js +307 -77
  80. package/dist/remote-mcp.js.map +1 -1
  81. package/dist/runtime-secrets.d.ts +9 -0
  82. package/dist/runtime-secrets.d.ts.map +1 -1
  83. package/dist/runtime-secrets.js +21 -10
  84. package/dist/runtime-secrets.js.map +1 -1
  85. package/dist/seal-key-rotation.d.ts +7 -7
  86. package/dist/seal-key-rotation.d.ts.map +1 -1
  87. package/dist/seal-key-rotation.js +22 -22
  88. package/dist/seal-key-rotation.js.map +1 -1
  89. package/dist/self-hosted-db-tls.d.ts +37 -0
  90. package/dist/self-hosted-db-tls.d.ts.map +1 -0
  91. package/dist/self-hosted-db-tls.js +220 -0
  92. package/dist/self-hosted-db-tls.js.map +1 -0
  93. package/dist/self-hosted-release.d.ts +3 -1
  94. package/dist/self-hosted-release.d.ts.map +1 -1
  95. package/dist/self-hosted-release.js +33 -9
  96. package/dist/self-hosted-release.js.map +1 -1
  97. package/dist/self-hosted-roles.d.ts +34 -0
  98. package/dist/self-hosted-roles.d.ts.map +1 -1
  99. package/dist/self-hosted-roles.js +52 -0
  100. package/dist/self-hosted-roles.js.map +1 -1
  101. package/dist/self-hosted-start.d.ts +0 -21
  102. package/dist/self-hosted-start.d.ts.map +1 -1
  103. package/dist/self-hosted-start.js +39 -1
  104. package/dist/self-hosted-start.js.map +1 -1
  105. package/dist/self-hosted-upgrade.d.ts +15 -6
  106. package/dist/self-hosted-upgrade.d.ts.map +1 -1
  107. package/dist/self-hosted-upgrade.js +72 -23
  108. package/dist/self-hosted-upgrade.js.map +1 -1
  109. package/dist/stack-contract.d.ts +170 -25
  110. package/dist/stack-contract.d.ts.map +1 -1
  111. package/dist/stack-contract.js +314 -37
  112. package/dist/stack-contract.js.map +1 -1
  113. package/dist/stack-converge.d.ts +31 -0
  114. package/dist/stack-converge.d.ts.map +1 -1
  115. package/dist/stack-converge.js +71 -30
  116. package/dist/stack-converge.js.map +1 -1
  117. package/dist/upgrade/app-roll.d.ts +25 -4
  118. package/dist/upgrade/app-roll.d.ts.map +1 -1
  119. package/dist/upgrade/app-roll.js +63 -17
  120. package/dist/upgrade/app-roll.js.map +1 -1
  121. package/dist/upgrade/converge.d.ts.map +1 -1
  122. package/dist/upgrade/converge.js +99 -12
  123. package/dist/upgrade/converge.js.map +1 -1
  124. package/dist/upgrade/health.d.ts +31 -7
  125. package/dist/upgrade/health.d.ts.map +1 -1
  126. package/dist/upgrade/health.js +41 -17
  127. package/dist/upgrade/health.js.map +1 -1
  128. package/dist/upgrade/image-forms.d.ts +1 -1
  129. package/dist/upgrade/image-forms.d.ts.map +1 -1
  130. package/dist/upgrade/image-forms.js +24 -7
  131. package/dist/upgrade/image-forms.js.map +1 -1
  132. package/dist/upgrade/release-images.d.ts +12 -3
  133. package/dist/upgrade/release-images.d.ts.map +1 -1
  134. package/dist/upgrade/release-images.js +13 -4
  135. package/dist/upgrade/release-images.js.map +1 -1
  136. package/dist/upgrade/upgrade.d.ts +13 -3
  137. package/dist/upgrade/upgrade.d.ts.map +1 -1
  138. package/dist/upgrade/upgrade.js +34 -2
  139. package/dist/upgrade/upgrade.js.map +1 -1
  140. package/dist/upgrader-gcp.d.ts +12 -0
  141. package/dist/upgrader-gcp.d.ts.map +1 -1
  142. package/dist/upgrader-gcp.js +49 -1
  143. package/dist/upgrader-gcp.js.map +1 -1
  144. package/dist/upgrader-iam-preflight.d.ts.map +1 -1
  145. package/dist/upgrader-iam-preflight.js +6 -1
  146. package/dist/upgrader-iam-preflight.js.map +1 -1
  147. package/dist/upgrader-job.d.ts +1 -1
  148. package/dist/upgrader-job.d.ts.map +1 -1
  149. package/dist/upgrader-job.js +20 -14
  150. package/dist/upgrader-job.js.map +1 -1
  151. package/dist/upgrader-records.d.ts +1 -1
  152. package/dist/upgrader-records.d.ts.map +1 -1
  153. package/dist/upgrader-records.js +26 -7
  154. package/dist/upgrader-records.js.map +1 -1
  155. package/dist/upgrader-setup.d.ts.map +1 -1
  156. package/dist/upgrader-setup.js +10 -5
  157. package/dist/upgrader-setup.js.map +1 -1
  158. package/dist/wizards/gcp-cost.d.ts.map +1 -1
  159. package/dist/wizards/gcp-cost.js +22 -7
  160. package/dist/wizards/gcp-cost.js.map +1 -1
  161. package/dist/wizards/gcp.d.ts.map +1 -1
  162. package/dist/wizards/gcp.js +43 -17
  163. package/dist/wizards/gcp.js.map +1 -1
  164. package/package.json +2 -2
  165. package/src/admin-domain.test.ts +9 -8
  166. package/src/admin-domain.ts +13 -3
  167. package/src/admin-memory.test.ts +1 -0
  168. package/src/adopt-existing.test.ts +39 -0
  169. package/src/adopt-existing.ts +83 -1
  170. package/src/agent/command.test.ts +49 -1
  171. package/src/agent/command.ts +36 -9
  172. package/src/agent/run.test.ts +19 -0
  173. package/src/agent/run.ts +11 -2
  174. package/src/aws-task-roles.ts +5 -2
  175. package/src/caddy-redirects.docker.test.ts +2 -0
  176. package/src/caddy-redirects.test.ts +3 -0
  177. package/src/caddy-validate.test.ts +7 -1
  178. package/src/caddy.test.ts +39 -1
  179. package/src/caddy.ts +36 -4
  180. package/src/checkpoint-secrets.test.ts +14 -6
  181. package/src/cli.ts +12 -1
  182. package/src/cloudsql-tls.test.ts +225 -0
  183. package/src/cloudsql-tls.ts +352 -0
  184. package/src/compose.ts +125 -24
  185. package/src/converge-checkpoint.ts +8 -1
  186. package/src/converge-job-gcp.ts +2 -11
  187. package/src/database-tls-builders.test.ts +55 -0
  188. package/src/deployed-release.test.ts +104 -40
  189. package/src/deployed-release.ts +30 -9
  190. package/src/gateway-env-isolation.test.ts +10 -5
  191. package/src/gcp-names.ts +31 -0
  192. package/src/gcp-url-map.test.ts +6 -8
  193. package/src/gcp-url-map.ts +6 -2
  194. package/src/index.test.ts +7 -1
  195. package/src/index.ts +1 -0
  196. package/src/infra-contract.test.ts +5 -3
  197. package/src/infra-contract.ts +24 -1
  198. package/src/install-state.ts +26 -4
  199. package/src/lifecycle.ts +20 -11
  200. package/src/mcp-auth-proxy-setup.test.ts +305 -0
  201. package/src/mcp-auth-proxy-setup.ts +374 -0
  202. package/src/mcp-iap-stacks.test.ts +1 -1
  203. package/src/media-volume.test.ts +1 -1
  204. package/src/operator-access-stacks.test.ts +1 -1
  205. package/src/operator-access.test.ts +5 -4
  206. package/src/pool-budget-stacks.test.ts +1 -1
  207. package/src/release-manifest.test.ts +25 -0
  208. package/src/release-manifest.ts +59 -4
  209. package/src/remote-mcp-stack-config.test.ts +19 -9
  210. package/src/remote-mcp.test.ts +252 -57
  211. package/src/remote-mcp.ts +336 -77
  212. package/src/runtime-secrets.test.ts +20 -7
  213. package/src/runtime-secrets.ts +50 -9
  214. package/src/seal-key-rotation.test.ts +2 -2
  215. package/src/seal-key-rotation.ts +25 -25
  216. package/src/self-hosted-compose.test.ts +108 -5
  217. package/src/self-hosted-db-tls.test.ts +111 -0
  218. package/src/self-hosted-db-tls.ts +287 -0
  219. package/src/self-hosted-release-resolve.test.ts +71 -0
  220. package/src/self-hosted-release.ts +41 -11
  221. package/src/self-hosted-roles.test.ts +54 -0
  222. package/src/self-hosted-roles.ts +58 -0
  223. package/src/self-hosted-start.test.ts +34 -2
  224. package/src/self-hosted-start.ts +20 -2
  225. package/src/self-hosted-upgrade.test.ts +103 -14
  226. package/src/self-hosted-upgrade.ts +115 -29
  227. package/src/stack-contract-parity.test.ts +79 -12
  228. package/src/stack-contract.test.ts +169 -29
  229. package/src/stack-contract.ts +362 -42
  230. package/src/stack-converge-secrets.test.ts +26 -13
  231. package/src/stack-converge.test.ts +199 -26
  232. package/src/stack-converge.ts +92 -30
  233. package/src/state-store.test.ts +2 -1
  234. package/src/upgrade/app-roll.ts +97 -19
  235. package/src/upgrade/converge.ts +120 -11
  236. package/src/upgrade/health.test.ts +63 -25
  237. package/src/upgrade/health.ts +54 -22
  238. package/src/upgrade/image-forms.test.ts +34 -0
  239. package/src/upgrade/image-forms.ts +23 -8
  240. package/src/upgrade/release-images.test.ts +5 -1
  241. package/src/upgrade/release-images.ts +22 -6
  242. package/src/upgrade/upgrade.test.ts +353 -44
  243. package/src/upgrade/upgrade.ts +55 -6
  244. package/src/upgrader-gcp.ts +65 -3
  245. package/src/upgrader-iam-preflight.test.ts +19 -0
  246. package/src/upgrader-iam-preflight.ts +6 -0
  247. package/src/upgrader-job.test.ts +101 -27
  248. package/src/upgrader-job.ts +27 -14
  249. package/src/upgrader-records.test.ts +97 -12
  250. package/src/upgrader-records.ts +28 -11
  251. package/src/upgrader-setup.test.ts +2 -0
  252. package/src/upgrader-setup.ts +10 -4
  253. package/src/wizards/gcp-cost.test.ts +50 -0
  254. package/src/wizards/gcp-cost.ts +24 -7
  255. package/src/wizards/gcp.ts +55 -14
  256. package/stacks/aws/index.ts +32 -14
  257. package/stacks/azure/index.ts +57 -18
  258. package/stacks/gcp/index.ts +83 -23
  259. package/stacks/gcp-firebase/index.ts +162 -33
  260. package/stacks/self-hosted/README.md +2 -1
  261. package/stacks/self-hosted/index.ts +11 -1
@@ -7,8 +7,10 @@
7
7
  * - Static site: Firebase Hosting site (no LB, no BackendBucket)
8
8
  * - Admin: Cloud Run + custom domain mapping (admin.<domain>)
9
9
  * + IAP-on-Cloud-Run (no LB IAP)
10
- * - Gateway: Cloud Run + Firebase Hosting `rewrites` (no LB,
11
- * no Cloud Armor)
10
+ * - Gateway: Cloud Run `caelo-<env>-data-gateway` + Firebase
11
+ * Hosting `rewrites` (no LB, no Cloud Armor)
12
+ * - MCP-Auth-Proxy: Cloud Run on its own run.app URL (#1328; the
13
+ * service that was the gateway, E13 a of #763)
12
14
  * - Database: Cloud SQL Postgres on private VPC IP only (same
13
15
  * as `gcp/`)
14
16
  * - Workers: share admin Cloud Run (same as `gcp/`)
@@ -35,6 +37,7 @@ import {
35
37
  gcpBucketName,
36
38
  gcpSecretId,
37
39
  MCP_IAP_SERVICE_ACCOUNT_ID,
40
+ mcpAuthProxyServiceAccountId,
38
41
  runServiceAccountId,
39
42
  } from "../../dist/gcp-names.js";
40
43
  import {
@@ -48,17 +51,27 @@ import {
48
51
  adminMediaVolume,
49
52
  adminMediaVolumeTemplate,
50
53
  CLOUD_RUN_IGNORE_CHANGES,
54
+ CLOUD_SQL_PSA_CONNECTION_TYPE,
55
+ CLOUD_SQL_SERVER_CA_MODE,
56
+ CLOUD_SQL_SSL_MODE,
51
57
  type CloudRunEnvVar,
52
58
  type CloudRunSlug,
59
+ cloudSqlDnsZoneName,
60
+ cloudSqlPrivateDnsName,
61
+ DEFAULT_MCP_AUTH_PROXY_MAX_INSTANCES,
53
62
  databaseUrls,
54
63
  defaultStagingHost,
55
- GATEWAY_TIMEOUT_SECONDS,
64
+ fixedCloudRunServiceName,
56
65
  gatewayEnvContract,
66
+ INTERIM_FORWARD_ORIGIN_ENV,
67
+ MCP_AUTH_PROXY_TIMEOUT_SECONDS,
68
+ mcpAuthProxyEnvContract,
57
69
  REMOTE_MCP_IDENTITY_CONFIG,
58
70
  type RuntimeSecret,
59
71
  remoteMcpIdentityStackEnv,
60
72
  runtimeSecretBindings,
61
73
  SERVICE_CPU_IDLE,
74
+ SERVICE_VPC_EGRESS,
62
75
  secretFileVolumesTemplate,
63
76
  } from "../../dist/stack-contract.js";
64
77
 
@@ -293,6 +306,11 @@ const sqlInstance = new gcp.sql.DatabaseInstance(
293
306
  ipv4Enabled: false,
294
307
  privateNetwork: network.id,
295
308
  enablePrivatePathForGoogleCloudServices: true,
309
+ // #1338 — the shared CA names the instance's DNS name in its server
310
+ // certificate, so every client verifies it (sslmode=verify-full);
311
+ // unencrypted connections are refused outright.
312
+ serverCaMode: CLOUD_SQL_SERVER_CA_MODE,
313
+ sslMode: CLOUD_SQL_SSL_MODE,
296
314
  },
297
315
  databaseFlags: [
298
316
  { name: "max_connections", value: maxConnections.toString() },
@@ -335,9 +353,49 @@ new gcp.sql.User(
335
353
  opts,
336
354
  );
337
355
 
356
+ // #1338 — the instance's DNS name, resolved to its private IP by a private
357
+ // zone in the VPC (Cloud SQL assigns the name, it does not resolve it).
358
+ // Every client dials this name with sslmode=verify-full; the certificate
359
+ // names no IP. `cms-provision upgrade` converges the same zone and record
360
+ // through gcloud (cloudsql-tls.ts); the installer adopts them (#1079).
361
+ const sqlDnsName = sqlInstance.dnsNames.apply((names) => {
362
+ const name = cloudSqlPrivateDnsName(names);
363
+ if (!name) {
364
+ throw new Error(
365
+ `Cloud SQL instance has no ${CLOUD_SQL_PSA_CONNECTION_TYPE} DNS name; it needs the shared server CA (${CLOUD_SQL_SERVER_CA_MODE}) for verify-full connections (#1338)`,
366
+ );
367
+ }
368
+ return name;
369
+ });
370
+ const sqlDnsZone = new gcp.dns.ManagedZone(
371
+ `${namePrefix}-pg-dns`,
372
+ {
373
+ name: cloudSqlDnsZoneName(env),
374
+ dnsName: pulumi.interpolate`${sqlDnsName}.`,
375
+ visibility: "private",
376
+ privateVisibilityConfig: { networks: [{ networkUrl: network.id }] },
377
+ description: `Caelo ${env}: the Cloud SQL instance's DNS name inside the VPC (#1338)`,
378
+ },
379
+ opts,
380
+ );
381
+ const sqlDnsRecord = new gcp.dns.RecordSet(
382
+ `${namePrefix}-pg-dns-a`,
383
+ {
384
+ // Literal names, not the zone's outputs: the installer can then adopt
385
+ // a record an upgrade created (adopt-existing.ts) from the preview.
386
+ managedZone: cloudSqlDnsZoneName(env),
387
+ name: pulumi.interpolate`${sqlDnsName}.`,
388
+ type: "A",
389
+ ttl: 300,
390
+ rrdatas: [sqlInstance.privateIpAddress],
391
+ },
392
+ { ...opts, dependsOn: [sqlDnsZone] },
393
+ );
394
+
338
395
  // Password-less connection URLs (stack-contract.ts databaseUrls); the
339
- // services get the password as a Secret Manager reference.
340
- const dbUrls = sqlInstance.privateIpAddress.apply(databaseUrls);
396
+ // services get the password as a Secret Manager reference. Tied to the
397
+ // record so no revision boots before its database host resolves.
398
+ const dbUrls = pulumi.all([sqlDnsName, sqlDnsRecord.id]).apply(([host]) => databaseUrls(host));
341
399
 
342
400
  // =========================================================================
343
401
  // Media bucket — private, signed-URL access only (same as `gcp` stack)
@@ -380,9 +438,22 @@ const gatewaySa = new gcp.serviceaccount.Account(
380
438
  },
381
439
  opts,
382
440
  );
441
+ // #1328 — the MCP-Auth-Proxy's own SA: secretAccessor on its two secrets
442
+ // below and nothing else — no project role, not even the log writer (Cloud
443
+ // Run ships a container's stdout to Cloud Logging without one).
444
+ const mcpAuthProxySa = new gcp.serviceaccount.Account(
445
+ `${namePrefix}-mcp-auth-proxy-sa`,
446
+ {
447
+ accountId: mcpAuthProxyServiceAccountId(env),
448
+ displayName: `Caelo ${env} MCP-Auth-Proxy`,
449
+ createIgnoreAlreadyExists: true,
450
+ },
451
+ opts,
452
+ );
383
453
  const serviceSa: Record<CloudRunSlug, gcp.serviceaccount.Account> = {
384
454
  admin: runSa,
385
455
  gateway: gatewaySa,
456
+ "mcp-auth-proxy": mcpAuthProxySa,
386
457
  };
387
458
 
388
459
  // secretAccessor for each service's SA on exactly the secrets its env reads.
@@ -391,6 +462,7 @@ const serviceSa: Record<CloudRunSlug, gcp.serviceaccount.Account> = {
391
462
  const secretBindings: Record<CloudRunSlug, gcp.secretmanager.SecretIamMember[]> = {
392
463
  admin: [],
393
464
  gateway: [],
465
+ "mcp-auth-proxy": [],
394
466
  };
395
467
  for (const binding of runtimeSecretBindings()) {
396
468
  secretBindings[binding.service].push(
@@ -533,11 +605,19 @@ const firebaseCustomDomain = new gcp.firebase.HostingCustomDomain(
533
605
  );
534
606
 
535
607
  // =========================================================================
536
- // Cloud Run services — admin + gateway
608
+ // Cloud Run services — admin, data gateway, MCP-Auth-Proxy
537
609
  // =========================================================================
538
610
 
539
611
  interface CloudRunArgs {
540
- readonly serviceName: "admin" | "gateway";
612
+ readonly serviceName: CloudRunSlug;
613
+ /**
614
+ * The Pulumi resource name, when it is not `${namePrefix}-<serviceName>`.
615
+ * #1328 E13 a: the proxy keeps the logical name the gateway had, so Pulumi
616
+ * keeps the same resource and its auto-generated service name — an alias
617
+ * under a new name would let the provider pick a new name, and a new
618
+ * service means a new run.app URL.
619
+ */
620
+ readonly resourceName?: string;
541
621
  readonly minInstances: number;
542
622
  readonly maxInstances: number;
543
623
  readonly memory: string;
@@ -582,9 +662,13 @@ function imageRef(service: string): string {
582
662
  const remoteMcpEnv = remoteMcpIdentityStackEnv(cfg.get(REMOTE_MCP_IDENTITY_CONFIG));
583
663
 
584
664
  function cloudRunService(args: CloudRunArgs): gcp.cloudrunv2.Service {
665
+ // #1328 — the data gateway has a fixed name: the converge creates it on
666
+ // installs whose stack predates it, and the installer adopts that one.
667
+ const fixedName = fixedCloudRunServiceName("gcp-firebase", env, args.serviceName);
585
668
  return new gcp.cloudrunv2.Service(
586
- `${namePrefix}-${args.serviceName}`,
669
+ args.resourceName ?? `${namePrefix}-${args.serviceName}`,
587
670
  {
671
+ ...(fixedName ? { name: fixedName } : {}),
588
672
  location: region,
589
673
  ingress: args.ingress,
590
674
  // v0.3.1 — Cloud Run native IAP. With this on, every request
@@ -604,8 +688,8 @@ function cloudRunService(args: CloudRunArgs): gcp.cloudrunv2.Service {
604
688
  ? { executionEnvironment: args.mediaVolume.executionEnvironment }
605
689
  : {}),
606
690
  // The admin's media bucket, plus each service's secret files (#806:
607
- // the gateway's remote-MCP OAuth client) — both from the contract
608
- // upgrade applies to existing installs.
691
+ // the MCP-Auth-Proxy's remote-MCP OAuth client) — both from the
692
+ // contract upgrade applies to existing installs.
609
693
  volumes: [
610
694
  ...(args.mediaVolume?.volumes ?? []),
611
695
  ...secretFileVolumesTemplate(args.serviceName, env).volumes,
@@ -625,10 +709,16 @@ function cloudRunService(args: CloudRunArgs): gcp.cloudrunv2.Service {
625
709
  },
626
710
  },
627
711
  ],
628
- vpcAccess: {
629
- networkInterfaces: [{ network: network.name, subnetwork: subnet.name }],
630
- egress: "PRIVATE_RANGES_ONLY",
631
- },
712
+ // #1328 — the MCP-Auth-Proxy gets no route into the VPC (and so
713
+ // none to Cloud SQL's private IP): SERVICE_VPC_EGRESS.
714
+ ...(SERVICE_VPC_EGRESS[args.serviceName]
715
+ ? {
716
+ vpcAccess: {
717
+ networkInterfaces: [{ network: network.name, subnetwork: subnet.name }],
718
+ egress: "PRIVATE_RANGES_ONLY",
719
+ },
720
+ }
721
+ : {}),
632
722
  },
633
723
  },
634
724
  {
@@ -669,30 +759,53 @@ const envContractInputs = {
669
759
  },
670
760
  } as const;
671
761
 
672
- // Gateway provisioned first so we can pass its name/region into the
673
- // admin's env (the Firebase publisher needs them to wire rewrites).
762
+ // The data gateway is provisioned first so its name/region can go into the
763
+ // admin's env (the Firebase publisher wires the /api/** rewrite to it).
764
+ // #1328 — a service of its own (`caelo-<env>-data-gateway`); before, the
765
+ // gateway was also the remote-MCP broker.
674
766
  const gatewaySvc = cloudRunService({
675
767
  serviceName: "gateway",
676
768
  minInstances: gatewayMinInstances,
677
769
  maxInstances: gatewayMaxInstances,
678
770
  memory: "512Mi",
679
- // #806 — remote MCP: chat turns stream through the gateway for minutes
680
- // (stack-contract.ts GATEWAY_TIMEOUT_SECONDS). The Firebase Hosting
681
- // rewrite keeps its own 60 s cut; MCP clients use the gateway's Cloud Run
682
- // URL directly.
683
- timeout: `${GATEWAY_TIMEOUT_SECONDS}s`,
684
- // #650 — all traffic: the gateway also serves staging.<domain> through
685
- // a Cloud Run domain mapping (`cms-provision staging-domain enable`),
686
- // and Cloud Run refuses domain-mapped requests under an internal-only
687
- // ingress. Exposure is unchanged in substance: the service was already
688
- // invokable by allUsers (gateway-public-invoker below) and every /api
689
- // request passes its rate limits, captcha and honeypot; staging requests
690
- // pass the staging access gate.
771
+ // Firebase Hosting cuts a rewritten request at 60 s anyway.
772
+ timeout: "300s",
773
+ // #650 — all traffic: `cms-provision staging-domain enable` maps the
774
+ // staging host onto the data gateway (Cloud Run refuses domain-mapped
775
+ // requests under an internal-only ingress), and it is invokable by
776
+ // allUsers anyway (data-gateway-public-invoker below): every /api request
777
+ // passes its rate limits, captcha and honeypot; staging requests pass the
778
+ // staging access gate.
779
+ ingress: "INGRESS_TRAFFIC_ALL",
780
+ contractEnv: [...gatewayEnvContract(envContractInputs), ...poolBudget.env.gateway],
781
+ });
782
+
783
+ // #1328 — the MCP-Auth-Proxy: remote MCP's sign-in broker and its stream to
784
+ // the IAP-protected admin, with no database, no VPC route and an account
785
+ // that reads two secrets. E13 (a) of #763: it is the resource that was the
786
+ // gateway (same logical name), so its service and run.app URL — the address every
787
+ // connector in claude.ai / ChatGPT holds, and the Google sign-in client's
788
+ // registered redirect URI — stay as they were. Always deployed at minimum
789
+ // zero; while remote MCP is not set up it answers a fixed 404 (E14 a).
790
+ const mcpAuthProxySvc = cloudRunService({
791
+ serviceName: "mcp-auth-proxy",
792
+ resourceName: `${namePrefix}-gateway`,
793
+ minInstances: 0,
794
+ maxInstances: DEFAULT_MCP_AUTH_PROXY_MAX_INSTANCES,
795
+ memory: "512Mi",
796
+ // #806 — chat turns stream for minutes (MCP_AUTH_PROXY_TIMEOUT_SECONDS);
797
+ // MCP clients call its run.app URL, never the Firebase rewrite (which
798
+ // buffers streams and cuts at 60 s).
799
+ timeout: `${MCP_AUTH_PROXY_TIMEOUT_SECONDS}s`,
800
+ // Public: MCP clients reach it directly; until #1331 the staging host's
801
+ // domain mapping (#650) still points at this service too, and the proxy
802
+ // forwards that traffic to the data gateway (INTERIM_FORWARD_ORIGIN_ENV).
691
803
  ingress: "INGRESS_TRAFFIC_ALL",
692
804
  contractEnv: [
693
- ...gatewayEnvContract(envContractInputs),
694
- ...remoteMcpEnv.gateway,
695
- ...poolBudget.env.gateway,
805
+ ...mcpAuthProxyEnvContract({ provider: "gcp-firebase", env }),
806
+ ...remoteMcpEnv["mcp-auth-proxy"],
807
+ // FIXME(issue #1331): goes with the staging cutover.
808
+ { name: INTERIM_FORWARD_ORIGIN_ENV, value: gatewaySvc.uri },
696
809
  ],
697
810
  });
698
811
 
@@ -751,7 +864,7 @@ const adminSvc = cloudRunService({
751
864
  // authentication remain the real security boundary, exactly as
752
865
  // designed.
753
866
  new gcp.cloudrunv2.ServiceIamMember(
754
- `${namePrefix}-gateway-public-invoker`,
867
+ `${namePrefix}-data-gateway-public-invoker`,
755
868
  {
756
869
  project,
757
870
  location: region,
@@ -762,6 +875,21 @@ new gcp.cloudrunv2.ServiceIamMember(
762
875
  opts,
763
876
  );
764
877
 
878
+ // #1328 — the MCP-Auth-Proxy: MCP clients sign in and call it without a
879
+ // Google identity of their own. The binding the service had as the gateway,
880
+ // under its old logical name, so it never lapses.
881
+ new gcp.cloudrunv2.ServiceIamMember(
882
+ `${namePrefix}-gateway-public-invoker`,
883
+ {
884
+ project,
885
+ location: region,
886
+ name: mcpAuthProxySvc.name,
887
+ role: "roles/run.invoker",
888
+ member: "allUsers",
889
+ },
890
+ opts,
891
+ );
892
+
765
893
  // v0.6.5 — `run.services.get` on the gateway for the admin runtime
766
894
  // SA. Required at DEPLOY TIME (not runtime): when the static
767
895
  // publisher creates a Firebase Hosting version with a Cloud Run
@@ -781,7 +909,7 @@ new gcp.cloudrunv2.ServiceIamMember(
781
909
  // explicit cross-service grant because the admin's runSa is a
782
910
  // DIFFERENT-resource caller from the gateway's perspective.
783
911
  new gcp.cloudrunv2.ServiceIamMember(
784
- `${namePrefix}-gateway-viewer-for-admin-sa`,
912
+ `${namePrefix}-data-gateway-viewer-for-admin-sa`,
785
913
  {
786
914
  project,
787
915
  location: region,
@@ -1110,6 +1238,7 @@ export const providerOut = out.provider;
1110
1238
  export const environmentOut = out.environment;
1111
1239
  export const adminCloudRunUrlOut = adminSvc.uri;
1112
1240
  export const gatewayCloudRunUrlOut = gatewaySvc.uri;
1241
+ export const mcpAuthProxyCloudRunUrlOut = mcpAuthProxySvc.uri;
1113
1242
  export const adminDomainOut = adminDomain;
1114
1243
  export const firebaseSiteIdOut = firebaseSiteId;
1115
1244
  export const bootstrapTokenExpiresAtOut = tokenInfo.expiresAt;
@@ -20,10 +20,11 @@ pulumi config set caelo-self-hosted:ownerEmail me@example.com
20
20
  # verified and recorded in .caelo/release.json:
21
21
  pulumi config set caelo-self-hosted:adminImage ghcr.io/caelo-cms/admin@sha256:<digest>
22
22
  pulumi config set caelo-self-hosted:gatewayImage ghcr.io/caelo-cms/gateway@sha256:<digest>
23
+ pulumi config set caelo-self-hosted:mcpAuthProxyImage ghcr.io/caelo-cms/mcp-auth-proxy@sha256:<digest>
23
24
  pulumi up
24
25
  ```
25
26
 
26
- Pulumi mints the postgres and role passwords and the KEK, generates `docker-compose.yml` + `Caddyfile` + `bootstrap.sh` + `pending-token.json` under `.caelo/`, starts Postgres, runs the migrations and runs `docker compose up -d`. Point DNS for `<domain>`, `admin.<domain>` and `staging.<domain>` at the host. After the run:
27
+ Pulumi mints the postgres and role passwords and the KEK, generates `docker-compose.yml` + `Caddyfile` + `bootstrap.sh` + `pending-token.json` under `.caelo/`, starts Postgres, runs the migrations and runs `docker compose up -d`. Point DNS for `<domain>`, `admin.<domain>` and `staging.<domain>` at the host. The MCP sign-in service (`caelo-mcp-auth-proxy`) runs without a public host: remote MCP needs an identity proxy in front of the admin, which self-hosted installs do not have yet, so it only answers 404 and Caddy does not expose it. After the run:
27
28
 
28
29
  ```bash
29
30
  pulumi stack output bootstrapUrl
@@ -29,6 +29,7 @@ import { generateCaddyfile, selfHostedAdminHost, selfHostedSeedDomains } from ".
29
29
  import {
30
30
  COMPOSE_ADMIN_UPSTREAM,
31
31
  COMPOSE_GATEWAY_UPSTREAM,
32
+ COMPOSE_MCP_AUTH_PROXY_UPSTREAM,
32
33
  composeProjectName,
33
34
  generateDockerCompose,
34
35
  SITE_OUTPUT_ROOT,
@@ -49,6 +50,9 @@ const adminMemory = cfg.get("adminMemory");
49
50
  // .caelo/release.json; copy those values here.
50
51
  const adminImage = cfg.require("adminImage");
51
52
  const gatewayImage = cfg.require("gatewayImage");
53
+ // #1328 — the MCP-Auth-Proxy (remote MCP's sign-in service, its own
54
+ // container and its own host `mcp.<domain>`).
55
+ const mcpAuthProxyImage = cfg.require("mcpAuthProxyImage");
52
56
 
53
57
  const composePath = resolve(caeloDir, "docker-compose.yml");
54
58
  const caddyPath = resolve(caeloDir, "Caddyfile");
@@ -80,7 +84,12 @@ const composeYaml = pulumi
80
84
  resendApiKey: process.env.RESEND_API_KEY,
81
85
  diskSize: "20Gi",
82
86
  ...(adminMemory ? { adminMemory } : {}),
83
- images: { mode: "release", admin: adminImage, gateway: gatewayImage },
87
+ images: {
88
+ mode: "release",
89
+ admin: adminImage,
90
+ gateway: gatewayImage,
91
+ "mcp-auth-proxy": mcpAuthProxyImage,
92
+ },
84
93
  ports: { http: 80, https: 443 },
85
94
  project: composeProjectName(domain),
86
95
  }),
@@ -95,6 +104,7 @@ const caddyConf = generateCaddyfile({
95
104
  stagingSiteRoot: `${SITE_OUTPUT_ROOT}/output/staging/current`,
96
105
  adminUpstream: COMPOSE_ADMIN_UPSTREAM,
97
106
  gatewayUpstream: COMPOSE_GATEWAY_UPSTREAM,
107
+ mcpAuthProxyUpstream: COMPOSE_MCP_AUTH_PROXY_UPSTREAM,
98
108
  domains: selfHostedSeedDomains(domain, stagingHost),
99
109
  });
100
110
  const bootstrapScript = selfHostedBootstrapScript({ agent: false });