@caelo-cms/provisioning 0.10.28 → 0.10.29

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.
@@ -12,6 +12,22 @@
12
12
 
13
13
  :80 {
14
14
  root * /srv/current
15
+ # Content-hashed build outputs (fonts, plugin bundles) never change
16
+ # under the same URL — cache them for a year. Keep this pattern in
17
+ # sync with CONTENT_HASHED_PATH_PATTERN in @caelo-cms/shared
18
+ # (enforced by packages/provisioning/src/index.test.ts). Pages and
19
+ # slug-addressed media keep file_server's ETag/Last-Modified default.
20
+ # `file` gates the header on the file existing: a 404 for a hashed
21
+ # URL (old page after a deploy switch) must not be cached for a year.
22
+ @content_hashed {
23
+ path_regexp content_hashed ^/(?:_app/immutable/.+|_assets/fonts/[^/]+/[0-9a-f]{16,64}\.(?:woff2|woff|ttf|otf)|_caelo/plugin/[^/]+/[^/]+\.[0-9a-f]{12}\.(?:js|css))$
24
+ file
25
+ }
26
+ header @content_hashed Cache-Control "public, max-age=31536000, immutable"
27
+ # Favicons ship as the one icon MIME the media library stores and the
28
+ # page's <link rel="icon" type> declares, not the host mime.types name.
29
+ @ico path *.ico
30
+ header @ico Content-Type "image/x-icon"
15
31
  encode gzip
16
32
  try_files {path} {path}/index.html
17
33
  file_server
@@ -14,6 +14,22 @@
14
14
 
15
15
  :80 {
16
16
  root * /srv/current
17
+ # Content-hashed build outputs (fonts, plugin bundles) never change
18
+ # under the same URL — cache them for a year. Keep this pattern in
19
+ # sync with CONTENT_HASHED_PATH_PATTERN in @caelo-cms/shared
20
+ # (enforced by packages/provisioning/src/index.test.ts). Pages and
21
+ # slug-addressed media keep file_server's ETag/Last-Modified default.
22
+ # `file` gates the header on the file existing: a 404 for a hashed
23
+ # URL (old page after a deploy switch) must not be cached for a year.
24
+ @content_hashed {
25
+ path_regexp content_hashed ^/(?:_app/immutable/.+|_assets/fonts/[^/]+/[0-9a-f]{16,64}\.(?:woff2|woff|ttf|otf)|_caelo/plugin/[^/]+/[^/]+\.[0-9a-f]{12}\.(?:js|css))$
26
+ file
27
+ }
28
+ header @content_hashed Cache-Control "public, max-age=31536000, immutable"
29
+ # Favicons ship as the one icon MIME the media library stores and the
30
+ # page's <link rel="icon" type> declares, not the host mime.types name.
31
+ @ico path *.ico
32
+ header @ico Content-Type "image/x-icon"
17
33
  encode gzip
18
34
  header X-Robots-Tag "noindex, nofollow"
19
35
  try_files {path} {path}/index.html
package/dist/caddy.d.ts CHANGED
@@ -1,20 +1,3 @@
1
- /**
2
- * P14 — Caddyfile generator.
3
- *
4
- * Reads the `domains` table + the deploy targets and emits a
5
- * deterministic Caddyfile string. The CLI's `regenerate-caddy`
6
- * sub-command writes this to `/etc/caddy/Caddyfile` and runs
7
- * `caddy reload`.
8
- *
9
- * Per-vhost shape:
10
- * <hostname> {
11
- * # admin → reverse_proxy localhost:5173
12
- * # public → root + try_files (static), with /api/* → gateway
13
- * tls <ownerEmail>
14
- * }
15
- *
16
- * Staging vhosts force `X-Robots-Tag: noindex`.
17
- */
18
1
  export interface CaddyDomainSpec {
19
2
  readonly hostname: string;
20
3
  readonly kind: "admin" | "public";
@@ -1 +1 @@
1
- {"version":3,"file":"caddy.d.ts","sourceRoot":"","sources":["../src/caddy.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,QAAQ,CAAC;IAClC,QAAQ,CAAC,GAAG,EAAE,YAAY,GAAG,SAAS,CAAC;CACxC;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;CAClD;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,CAyB7D"}
1
+ {"version":3,"file":"caddy.d.ts","sourceRoot":"","sources":["../src/caddy.ts"],"names":[],"mappings":"AAoCA,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,QAAQ,CAAC;IAClC,QAAQ,CAAC,GAAG,EAAE,YAAY,GAAG,SAAS,CAAC;CACxC;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;CAClD;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,CAyB7D"}
package/dist/caddy.js CHANGED
@@ -1,4 +1,36 @@
1
1
  // SPDX-License-Identifier: MPL-2.0
2
+ /**
3
+ * P14 — Caddyfile generator.
4
+ *
5
+ * Reads the `domains` table + the deploy targets and emits a
6
+ * deterministic Caddyfile string. The CLI's `regenerate-caddy`
7
+ * sub-command writes this to `/etc/caddy/Caddyfile` and runs
8
+ * `caddy reload`.
9
+ *
10
+ * Per-vhost shape:
11
+ * <hostname> {
12
+ * # admin → reverse_proxy localhost:5173
13
+ * # public → root + try_files (static), with /api/* → gateway
14
+ * tls <ownerEmail>
15
+ * }
16
+ *
17
+ * Staging vhosts force `X-Robots-Tag: noindex`.
18
+ *
19
+ * Public vhosts mark content-hashed build outputs (fonts, plugin
20
+ * bundles — `CONTENT_HASHED_PATH_PATTERN` in @caelo-cms/shared)
21
+ * `immutable` for a year. Everything else keeps Caddy's file_server
22
+ * default (no Cache-Control; ETag + Last-Modified for revalidation),
23
+ * so pages and slug-addressed media are never pinned in caches. The
24
+ * matcher also requires the file to exist (`file`), so a 404 for a
25
+ * hashed URL (an old page requesting an asset the new build dropped)
26
+ * is never cached for a year.
27
+ *
28
+ * `.ico` files (theme favicons) are served as `image/x-icon`, the one
29
+ * icon MIME the media library stores and `<link rel="icon" type>`
30
+ * declares. file_server only fills Content-Type when no earlier handler
31
+ * set it, so the `header` line wins over the host's mime.types name.
32
+ */
33
+ import { CONTENT_HASHED_PATH_PATTERN, IMMUTABLE_CACHE_CONTROL } from "@caelo-cms/shared";
2
34
  export function generateCaddyfile(spec) {
3
35
  const blocks = [];
4
36
  // Global options.
@@ -34,6 +66,13 @@ function vhost(d, spec) {
34
66
  const root = d.env === "staging" ? spec.stagingSiteRoot : spec.publicSiteRoot;
35
67
  return `${d.hostname} {${noindex}
36
68
  root * ${root}
69
+ @content_hashed {
70
+ path_regexp content_hashed ${CONTENT_HASHED_PATH_PATTERN}
71
+ file
72
+ }
73
+ header @content_hashed Cache-Control "${IMMUTABLE_CACHE_CONTROL}"
74
+ @ico path *.ico
75
+ header @ico Content-Type "image/x-icon"
37
76
  handle /api/* {
38
77
  reverse_proxy localhost:${spec.gatewayPort}
39
78
  }
package/dist/caddy.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"caddy.js","sourceRoot":"","sources":["../src/caddy.ts"],"names":[],"mappings":"AAAA,mCAAmC;AAmCnC,MAAM,UAAU,iBAAiB,CAAC,IAAmB;IACnD,MAAM,MAAM,GAAa,EAAE,CAAC;IAE5B,kBAAkB;IAClB,MAAM,CAAC,IAAI,CAAC;UACJ,IAAI,CAAC,UAAU;;CAExB,CAAC,CAAC;IAED,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;QAC7B,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;IAC9B,CAAC;IAED,oEAAoE;IACpE,8DAA8D;IAC9D,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9B,MAAM,CAAC,IAAI,CAAC;;;4BAGY,IAAI,CAAC,SAAS;;CAEzC,CAAC,CAAC;IACD,CAAC;IAED,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC3B,CAAC;AAED,SAAS,KAAK,CAAC,CAAkB,EAAE,IAAmB;IACpD,MAAM,OAAO,GAAG,CAAC,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,mCAAmC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC/E,IAAI,CAAC,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QACvB,OAAO,GAAG,CAAC,CAAC,QAAQ,KAAK,OAAO;4BACR,IAAI,CAAC,SAAS;;CAEzC,CAAC;IACA,CAAC;IACD,yCAAyC;IACzC,6DAA6D;IAC7D,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC;IAC9E,OAAO,GAAG,CAAC,CAAC,QAAQ,KAAK,OAAO;WACvB,IAAI;;8BAEe,IAAI,CAAC,WAAW;;;8BAGhB,IAAI,CAAC,SAAS;;;;;;;;CAQ3C,CAAC;AACF,CAAC"}
1
+ {"version":3,"file":"caddy.js","sourceRoot":"","sources":["../src/caddy.ts"],"names":[],"mappings":"AAAA,mCAAmC;AAEnC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EAAE,2BAA2B,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAiBzF,MAAM,UAAU,iBAAiB,CAAC,IAAmB;IACnD,MAAM,MAAM,GAAa,EAAE,CAAC;IAE5B,kBAAkB;IAClB,MAAM,CAAC,IAAI,CAAC;UACJ,IAAI,CAAC,UAAU;;CAExB,CAAC,CAAC;IAED,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;QAC7B,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;IAC9B,CAAC;IAED,oEAAoE;IACpE,8DAA8D;IAC9D,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9B,MAAM,CAAC,IAAI,CAAC;;;4BAGY,IAAI,CAAC,SAAS;;CAEzC,CAAC,CAAC;IACD,CAAC;IAED,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC3B,CAAC;AAED,SAAS,KAAK,CAAC,CAAkB,EAAE,IAAmB;IACpD,MAAM,OAAO,GAAG,CAAC,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,mCAAmC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC/E,IAAI,CAAC,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QACvB,OAAO,GAAG,CAAC,CAAC,QAAQ,KAAK,OAAO;4BACR,IAAI,CAAC,SAAS;;CAEzC,CAAC;IACA,CAAC;IACD,yCAAyC;IACzC,6DAA6D;IAC7D,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC;IAC9E,OAAO,GAAG,CAAC,CAAC,QAAQ,KAAK,OAAO;WACvB,IAAI;;iCAEkB,2BAA2B;;;0CAGlB,uBAAuB;;;;8BAInC,IAAI,CAAC,WAAW;;;8BAGhB,IAAI,CAAC,SAAS;;;;;;;;CAQ3C,CAAC;AACF,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../src/compose.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AAEH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC;;;;;OAKG;IACH,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,CAiG/D"}
1
+ {"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../src/compose.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AAEH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC;;;;;OAKG;IACH,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,CAkG/D"}
package/dist/compose.js CHANGED
@@ -68,6 +68,7 @@ services:
68
68
  ADMIN_DATABASE_URL: "postgres://caelo:${escapeYaml(spec.postgresPassword)}@postgres:5432/cms_admin"
69
69
  PUBLIC_ADMIN_DATABASE_URL: "postgres://caelo:${escapeYaml(spec.postgresPassword)}@postgres:5432/cms_public"
70
70
  CAELO_SECRET_KEK: "${escapeYaml(spec.caeloSecretKek)}"
71
+ CAELO_SITE_URL: "https://${escapeYaml(spec.domain)}"
71
72
  NODE_ENV: production
72
73
  ${env("ANTHROPIC_API_KEY", spec.anthropicApiKey)}
73
74
  ${env("RESEND_API_KEY", spec.resendApiKey)}
@@ -1 +1 @@
1
- {"version":3,"file":"compose.js","sourceRoot":"","sources":["../src/compose.ts"],"names":[],"mappings":"AAAA,mCAAmC;AA6BnC,MAAM,UAAU,qBAAqB,CAAC,IAAiB;IACrD,MAAM,GAAG,GAAG,CAAC,CAAS,EAAE,CAAqB,EAAU,EAAE,CACvD,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IAC5C,OAAO;;YAEG,IAAI,CAAC,MAAM;;;;;;;;4BAQK,UAAU,CAAC,IAAI,CAAC,gBAAgB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;0BA2BnC,UAAU,CAAC,IAAI,CAAC,aAAa,CAAC;8BAC1B,UAAU,CAAC,IAAI,CAAC,iBAAiB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;8CA0BlB,UAAU,CAAC,IAAI,CAAC,gBAAgB,CAAC;qDAC1B,UAAU,CAAC,IAAI,CAAC,gBAAgB,CAAC;2BAC3D,UAAU,CAAC,IAAI,CAAC,cAAc,CAAC;;EAExD,GAAG,CAAC,mBAAmB,EAAE,IAAI,CAAC,eAAe,CAAC;EAC9C,GAAG,CAAC,gBAAgB,EAAE,IAAI,CAAC,YAAY,CAAC;;;;;;;;;;;;;8CAaI,UAAU,CAAC,IAAI,CAAC,gBAAgB,CAAC;+CAChC,UAAU,CAAC,IAAI,CAAC,gBAAgB,CAAC;;;;;;;;;;CAU/E,CAAC;AACF,CAAC;AAED,SAAS,UAAU,CAAC,CAAS;IAC3B,sDAAsD;IACtD,OAAO,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AACvD,CAAC"}
1
+ {"version":3,"file":"compose.js","sourceRoot":"","sources":["../src/compose.ts"],"names":[],"mappings":"AAAA,mCAAmC;AA6BnC,MAAM,UAAU,qBAAqB,CAAC,IAAiB;IACrD,MAAM,GAAG,GAAG,CAAC,CAAS,EAAE,CAAqB,EAAU,EAAE,CACvD,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IAC5C,OAAO;;YAEG,IAAI,CAAC,MAAM;;;;;;;;4BAQK,UAAU,CAAC,IAAI,CAAC,gBAAgB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;0BA2BnC,UAAU,CAAC,IAAI,CAAC,aAAa,CAAC;8BAC1B,UAAU,CAAC,IAAI,CAAC,iBAAiB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;8CA0BlB,UAAU,CAAC,IAAI,CAAC,gBAAgB,CAAC;qDAC1B,UAAU,CAAC,IAAI,CAAC,gBAAgB,CAAC;2BAC3D,UAAU,CAAC,IAAI,CAAC,cAAc,CAAC;iCACzB,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC;;EAEtD,GAAG,CAAC,mBAAmB,EAAE,IAAI,CAAC,eAAe,CAAC;EAC9C,GAAG,CAAC,gBAAgB,EAAE,IAAI,CAAC,YAAY,CAAC;;;;;;;;;;;;;8CAaI,UAAU,CAAC,IAAI,CAAC,gBAAgB,CAAC;+CAChC,UAAU,CAAC,IAAI,CAAC,gBAAgB,CAAC;;;;;;;;;;CAU/E,CAAC;AACF,CAAC;AAED,SAAS,UAAU,CAAC,CAAS;IAC3B,sDAAsD;IACtD,OAAO,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AACvD,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caelo-cms/provisioning",
3
- "version": "0.10.28",
3
+ "version": "0.10.29",
4
4
  "private": false,
5
5
  "license": "MPL-2.0",
6
6
  "description": "Pulumi-driven provisioning for Caelo CMS — `bunx @caelo-cms/provisioning --provider <self-hosted|gcp|aws|azure>` brings up a TLS-served install with Postgres + storage + secrets manager + the admin + the API gateway.",
@@ -38,8 +38,8 @@
38
38
  "prepublishOnly": "tsc -b --force && tsc --project stacks/gcp/tsconfig.json && tsc --project stacks/gcp-firebase/tsconfig.json"
39
39
  },
40
40
  "dependencies": {
41
- "@caelo-cms/edge-router": "^0.10.28",
42
- "@caelo-cms/shared": "^0.10.28",
41
+ "@caelo-cms/edge-router": "^0.10.29",
42
+ "@caelo-cms/shared": "^0.10.29",
43
43
  "@clack/prompts": "1.8.1",
44
44
  "@pulumi/aws": "^7",
45
45
  "@pulumi/awsx": "^3",
@@ -243,7 +243,12 @@ function containerApp(args: ContainerAppArgs): azure.app.ContainerApp {
243
243
  });
244
244
  }
245
245
 
246
- const adminApp = containerApp({ serviceName: "admin" });
246
+ const adminApp = containerApp({
247
+ serviceName: "admin",
248
+ // #551 — the public site URL; the admin seeds site_defaults.site_base_url
249
+ // from it (canonical, og:url, sitemap) when it is not configured yet.
250
+ extraEnv: [{ name: "CAELO_SITE_URL", value: `https://${domain}` }],
251
+ });
247
252
  const _gatewayApp = containerApp({ serviceName: "gateway" });
248
253
  const _orchestratorApp = containerApp({ serviceName: "orchestrator" });
249
254
  const _runnerApp = containerApp({ serviceName: "runner" });
@@ -630,6 +630,9 @@ const adminSvc = cloudRunService({
630
630
  // sit where the walk expects.
631
631
  extraEnv: [
632
632
  { name: "PUBLIC_ADMIN_DATABASE_URL", value: publicAdminDatabaseUrl },
633
+ // #551 — the public site URL; the admin seeds site_defaults.site_base_url
634
+ // from it (canonical, og:url, sitemap) when it is not configured yet.
635
+ { name: "CAELO_SITE_URL", value: `https://${domain}` },
633
636
  { name: "CAELO_STATIC_BUCKET", value: staticBucket.name },
634
637
  { name: "CAELO_STAGING_BUCKET", value: stagingBucket.name },
635
638
  { name: "CAELO_GENERATOR_CLI", value: "/app/apps/static-generator/src/cli.ts" },
@@ -723,6 +726,15 @@ const wafPolicy = new gcp.compute.SecurityPolicy(
723
726
  );
724
727
 
725
728
  // Tier 1 backend — static GCS bucket via Cloud CDN
729
+ //
730
+ // The GCS publisher sets an explicit Cache-Control on every object
731
+ // (static-publisher-gcs.ts): content-hashed fonts / plugin bundles are
732
+ // `max-age=31536000, immutable`, pages `max-age=60` + SWR. In
733
+ // CACHE_ALL_STATIC mode `clientTtl` CLAMPS the max-age sent to
734
+ // browsers and `maxTtl` caps the edge TTL, so both must allow a year
735
+ // or the immutable policy is silently cut back to the old 1h. Objects
736
+ // that state a shorter max-age keep it; `defaultTtl` still applies only
737
+ // to responses without one.
726
738
  const staticBackendBucket = new gcp.compute.BackendBucket(
727
739
  `${namePrefix}-static-backend`,
728
740
  {
@@ -731,8 +743,8 @@ const staticBackendBucket = new gcp.compute.BackendBucket(
731
743
  cdnPolicy: {
732
744
  cacheMode: "CACHE_ALL_STATIC",
733
745
  defaultTtl: 3600,
734
- maxTtl: 86400,
735
- clientTtl: 3600,
746
+ maxTtl: 31536000,
747
+ clientTtl: 31536000,
736
748
  },
737
749
  },
738
750
  opts,
@@ -510,6 +510,9 @@ const adminSvc = cloudRunService({
510
510
  { name: "PUBLIC_ADMIN_DATABASE_URL", value: publicAdminDatabaseUrl },
511
511
  // Use the input string (known at config time) — resource output
512
512
  // is Output<string | undefined> which Input<string> rejects.
513
+ // #551 — the public site URL; the admin seeds site_defaults.site_base_url
514
+ // from it (canonical, og:url, sitemap) when it is not configured yet.
515
+ { name: "CAELO_SITE_URL", value: `https://${domain}` },
513
516
  { name: "CAELO_FIREBASE_SITE", value: firebaseSiteId },
514
517
  { name: "CAELO_GENERATOR_CLI", value: "/app/apps/static-generator/src/cli.ts" },
515
518
  // v0.3.1 — Firebase publisher needs the gateway service name +