@caelo-cms/provisioning 0.10.49 → 0.10.50

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 (137) hide show
  1. package/caddy/Caddyfile.production +4 -0
  2. package/caddy/Caddyfile.staging +4 -0
  3. package/dist/agent-db.js +4 -0
  4. package/dist/caddy-redirects.d.ts +15 -0
  5. package/dist/caddy-redirects.d.ts.map +1 -0
  6. package/dist/caddy-redirects.js +65 -0
  7. package/dist/caddy-redirects.js.map +1 -0
  8. package/dist/caddy.d.ts +22 -2
  9. package/dist/caddy.d.ts.map +1 -1
  10. package/dist/caddy.js +66 -17
  11. package/dist/caddy.js.map +1 -1
  12. package/dist/cli-routing.d.ts.map +1 -1
  13. package/dist/cli-routing.js +7 -0
  14. package/dist/cli-routing.js.map +1 -1
  15. package/dist/cli.js +248 -254
  16. package/dist/cli.js.map +1 -1
  17. package/dist/compose.d.ts +54 -2
  18. package/dist/compose.d.ts.map +1 -1
  19. package/dist/compose.js +86 -32
  20. package/dist/compose.js.map +1 -1
  21. package/dist/gcp-names.d.ts +12 -0
  22. package/dist/gcp-names.d.ts.map +1 -1
  23. package/dist/gcp-names.js +21 -0
  24. package/dist/gcp-names.js.map +1 -1
  25. package/dist/gcp-url-map.d.ts +27 -0
  26. package/dist/gcp-url-map.d.ts.map +1 -0
  27. package/dist/gcp-url-map.js +62 -0
  28. package/dist/gcp-url-map.js.map +1 -0
  29. package/dist/index.d.ts +0 -1
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +0 -1
  32. package/dist/index.js.map +1 -1
  33. package/dist/infra-contract.d.ts +20 -1
  34. package/dist/infra-contract.d.ts.map +1 -1
  35. package/dist/infra-contract.js +39 -1
  36. package/dist/infra-contract.js.map +1 -1
  37. package/dist/public-role-isolation.d.ts +19 -0
  38. package/dist/public-role-isolation.d.ts.map +1 -0
  39. package/dist/public-role-isolation.js +69 -0
  40. package/dist/public-role-isolation.js.map +1 -0
  41. package/dist/release-contract.d.ts.map +1 -1
  42. package/dist/release-contract.js +3 -0
  43. package/dist/release-contract.js.map +1 -1
  44. package/dist/release-manifest.d.ts.map +1 -1
  45. package/dist/release-manifest.js +2 -1
  46. package/dist/release-manifest.js.map +1 -1
  47. package/dist/self-hosted-backup.d.ts +86 -0
  48. package/dist/self-hosted-backup.d.ts.map +1 -0
  49. package/dist/self-hosted-backup.js +350 -0
  50. package/dist/self-hosted-backup.js.map +1 -0
  51. package/dist/self-hosted-release.d.ts +12 -3
  52. package/dist/self-hosted-release.d.ts.map +1 -1
  53. package/dist/self-hosted-release.js +6 -4
  54. package/dist/self-hosted-release.js.map +1 -1
  55. package/dist/self-hosted-start.d.ts +50 -0
  56. package/dist/self-hosted-start.d.ts.map +1 -0
  57. package/dist/self-hosted-start.js +59 -0
  58. package/dist/self-hosted-start.js.map +1 -0
  59. package/dist/self-hosted-upgrade.d.ts +30 -0
  60. package/dist/self-hosted-upgrade.d.ts.map +1 -1
  61. package/dist/self-hosted-upgrade.js +29 -7
  62. package/dist/self-hosted-upgrade.js.map +1 -1
  63. package/dist/tar-entries.d.ts +14 -0
  64. package/dist/tar-entries.d.ts.map +1 -0
  65. package/dist/tar-entries.js +104 -0
  66. package/dist/tar-entries.js.map +1 -0
  67. package/dist/upgrade/converge.d.ts +2 -2
  68. package/dist/upgrade/converge.d.ts.map +1 -1
  69. package/dist/upgrade/converge.js +44 -2
  70. package/dist/upgrade/converge.js.map +1 -1
  71. package/dist/upgrader-assess.d.ts.map +1 -1
  72. package/dist/upgrader-assess.js +2 -1
  73. package/dist/upgrader-assess.js.map +1 -1
  74. package/dist/url-map-redirects-access.d.ts +15 -0
  75. package/dist/url-map-redirects-access.d.ts.map +1 -0
  76. package/dist/url-map-redirects-access.js +81 -0
  77. package/dist/url-map-redirects-access.js.map +1 -0
  78. package/dist/wizard.d.ts.map +1 -1
  79. package/dist/wizard.js +3 -1
  80. package/dist/wizard.js.map +1 -1
  81. package/dist/wizards/gcp.d.ts.map +1 -1
  82. package/dist/wizards/gcp.js +45 -0
  83. package/dist/wizards/gcp.js.map +1 -1
  84. package/package.json +2 -2
  85. package/src/admin-memory.test.ts +2 -2
  86. package/src/caddy-redirects.docker.test.ts +255 -0
  87. package/src/caddy-redirects.test.ts +115 -0
  88. package/src/caddy-redirects.ts +68 -0
  89. package/src/caddy-validate.test.ts +119 -0
  90. package/src/caddy.test.ts +108 -0
  91. package/src/caddy.ts +89 -20
  92. package/src/cli-routing.test.ts +12 -0
  93. package/src/cli-routing.ts +7 -0
  94. package/src/cli.ts +299 -287
  95. package/src/compose.ts +106 -34
  96. package/src/gcp-names.ts +23 -0
  97. package/src/gcp-url-map.test.ts +150 -0
  98. package/src/gcp-url-map.ts +81 -0
  99. package/src/index.test.ts +18 -146
  100. package/src/index.ts +0 -9
  101. package/src/infra-contract.test.ts +39 -0
  102. package/src/infra-contract.ts +42 -1
  103. package/src/public-role-isolation.test.ts +89 -0
  104. package/src/public-role-isolation.ts +83 -0
  105. package/src/release-contract.ts +3 -0
  106. package/src/release-manifest.test.ts +12 -0
  107. package/src/release-manifest.ts +2 -1
  108. package/src/self-hosted-backup.test.ts +344 -0
  109. package/src/self-hosted-backup.ts +480 -0
  110. package/src/self-hosted-compose.test.ts +88 -4
  111. package/src/self-hosted-release.ts +22 -7
  112. package/src/self-hosted-start.test.ts +68 -0
  113. package/src/self-hosted-start.ts +123 -0
  114. package/src/self-hosted-upgrade.ts +35 -8
  115. package/src/stack-contract.test.ts +2 -2
  116. package/src/tar-entries.test.ts +52 -0
  117. package/src/tar-entries.ts +116 -0
  118. package/src/upgrade/converge.ts +51 -4
  119. package/src/upgrade/upgrade.test.ts +78 -1
  120. package/src/upgrader-assess.test.ts +22 -0
  121. package/src/upgrader-assess.ts +2 -1
  122. package/src/url-map-redirects-access.test.ts +88 -0
  123. package/src/url-map-redirects-access.ts +89 -0
  124. package/src/wizard.ts +3 -1
  125. package/src/wizards/gcp.ts +48 -0
  126. package/stacks/aws/index.ts +34 -2
  127. package/stacks/azure/index.ts +13 -0
  128. package/stacks/gcp/index.ts +24 -17
  129. package/stacks/gcp-firebase/index.ts +7 -0
  130. package/stacks/self-hosted/Pulumi.yaml +2 -2
  131. package/stacks/self-hosted/README.md +3 -3
  132. package/stacks/self-hosted/index.ts +45 -22
  133. package/dist/redirects-emit.d.ts +0 -52
  134. package/dist/redirects-emit.d.ts.map +0 -1
  135. package/dist/redirects-emit.js +0 -111
  136. package/dist/redirects-emit.js.map +0 -1
  137. package/src/redirects-emit.ts +0 -175
@@ -10,6 +10,10 @@
10
10
 
11
11
  :80 {
12
12
  root * /srv/current
13
+ # #587 — the redirects table of the build served here (the generator
14
+ # writes it; `*` so a fresh checkout without a build still starts).
15
+ # docker-compose.yml reloads Caddy when it changes (caddy-redirects.ts).
16
+ import /srv/current/_redirects.caddy*
13
17
  # Content-hashed build outputs (fonts, plugin bundles, media) never
14
18
  # change under the same URL — cache them for a year. Keep this pattern in
15
19
  # sync with CONTENT_HASHED_PATH_PATTERN in @caelo-cms/shared
@@ -14,6 +14,10 @@
14
14
 
15
15
  :80 {
16
16
  root * /srv/current
17
+ # #587 — the redirects table of the build served here (the generator
18
+ # writes it; `*` so a fresh checkout without a build still starts).
19
+ # docker-compose.yml reloads Caddy when it changes (caddy-redirects.ts).
20
+ import /srv/current/_redirects.caddy*
17
21
  # Content-hashed build outputs (fonts, plugin bundles, media) never
18
22
  # change under the same URL — cache them for a year. Keep this pattern in
19
23
  # sync with CONTENT_HASHED_PATH_PATTERN in @caelo-cms/shared
package/dist/agent-db.js CHANGED
@@ -8083,6 +8083,10 @@ var pageLogEntrySchema = object({
8083
8083
  // ../shared/src/proposal-status.ts
8084
8084
  var PROPOSAL_STATUSES = ["pending", "applied", "rejected", "superseded"];
8085
8085
  var proposalStatus = _enum(PROPOSAL_STATUSES);
8086
+ // ../shared/src/redirect-emit-cloudfront.ts
8087
+ var CLOUDFRONT_KVS_MAX_TOTAL_BYTES = 5 * 1024 * 1024;
8088
+ // ../shared/src/redirect-plan.ts
8089
+ var REDIRECT_STATUSES = new Set([301, 302, 307, 308]);
8086
8090
  // ../shared/src/release-manifest.ts
8087
8091
  var RELEASE_CHANNELS = ["stable", "rc", "beta", "alpha"];
8088
8092
  var IMAGE_DIGEST = /^sha256:[0-9a-f]{64}$/;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The container's shell script: Caddy in the foreground of the container's
3
+ * lifetime, reloaded when any file matching `snippetGlob` changes.
4
+ *
5
+ * @param snippetGlob Shell glob of the imported snippets, e.g.
6
+ * `/srv/caelo/output/*\/current/_redirects.caddy`.
7
+ */
8
+ export declare function caddyRedirectReloadScript(snippetGlob: string): string;
9
+ /**
10
+ * A Compose `command:` line running {@link caddyRedirectReloadScript}. `$` is
11
+ * doubled because Compose interpolates `$VAR` in the file; the JSON string is
12
+ * a valid YAML double-quoted scalar.
13
+ */
14
+ export declare function caddyRedirectReloadCommand(snippetGlob: string): string;
15
+ //# sourceMappingURL=caddy-redirects.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"caddy-redirects.d.ts","sourceRoot":"","sources":["../src/caddy-redirects.ts"],"names":[],"mappings":"AA2BA;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAuBrE;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAGtE"}
@@ -0,0 +1,65 @@
1
+ // SPDX-License-Identifier: MPL-2.0
2
+ /**
3
+ * How the self-hosted Caddy picks up a deploy's redirect snippet (#587).
4
+ *
5
+ * Each public site block imports `<site root>/_redirects.caddy`, which the
6
+ * static generator rewrites on every build. Caddy reads imports only when it
7
+ * loads a config, so something must reload it after a deploy. The container
8
+ * does that itself: its command runs Caddy plus a small loop that watches a
9
+ * checksum of the snippets and runs `caddy reload` (against Caddy's own
10
+ * localhost admin endpoint, inside the container) when they change.
11
+ *
12
+ * Why not the alternatives:
13
+ * - `caddy run --watch` stops watching for good after the first config that
14
+ * fails to load — a snippet read while a deploy is still copying it would
15
+ * freeze the redirects until Caddy restarts.
16
+ * - The admin reloading Caddy through the admin API would need a channel
17
+ * from the admin into Caddy and would post a file's content over HTTP.
18
+ *
19
+ * The loop waits until the checksum is stable across two reads before
20
+ * reloading (a deploy may be mid-copy), and a reload Caddy rejects only
21
+ * logs: Caddy keeps the previous config, and the next change retries.
22
+ */
23
+ /** Seconds between checksum polls. */
24
+ const POLL_SECONDS = 2;
25
+ /**
26
+ * The container's shell script: Caddy in the foreground of the container's
27
+ * lifetime, reloaded when any file matching `snippetGlob` changes.
28
+ *
29
+ * @param snippetGlob Shell glob of the imported snippets, e.g.
30
+ * `/srv/caelo/output/*\/current/_redirects.caddy`.
31
+ */
32
+ export function caddyRedirectReloadScript(snippetGlob) {
33
+ if (/[\s'"`$\\;&|<>]/.test(snippetGlob)) {
34
+ throw new Error(`caddy-redirects: unsafe snippet glob ${JSON.stringify(snippetGlob)}`);
35
+ }
36
+ const config = "--config /etc/caddy/Caddyfile --adapter caddyfile";
37
+ return [
38
+ `sum() { cat ${snippetGlob} 2>/dev/null | md5sum; }`,
39
+ "last=$(sum)",
40
+ `caddy run ${config} &`,
41
+ "pid=$!",
42
+ `trap 'kill -TERM "$pid" 2>/dev/null; wait "$pid"; exit $?' TERM INT`,
43
+ `while kill -0 "$pid" 2>/dev/null; do`,
44
+ ` sleep ${POLL_SECONDS}`,
45
+ " now=$(sum)",
46
+ ` [ "$now" = "$last" ] && continue`,
47
+ // A deploy may still be copying the file: reload only a stable one.
48
+ " sleep 1",
49
+ ` [ "$(sum)" = "$now" ] || continue`,
50
+ " last=$now",
51
+ ` caddy reload ${config} || echo "caelo: redirects changed but Caddy rejected the reload; still serving the previous redirects" >&2`,
52
+ "done",
53
+ `wait "$pid"`,
54
+ ].join("\n");
55
+ }
56
+ /**
57
+ * A Compose `command:` line running {@link caddyRedirectReloadScript}. `$` is
58
+ * doubled because Compose interpolates `$VAR` in the file; the JSON string is
59
+ * a valid YAML double-quoted scalar.
60
+ */
61
+ export function caddyRedirectReloadCommand(snippetGlob) {
62
+ const script = caddyRedirectReloadScript(snippetGlob).replaceAll("$", () => "$$");
63
+ return `command: ["/bin/sh", "-c", ${JSON.stringify(script)}]`;
64
+ }
65
+ //# sourceMappingURL=caddy-redirects.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"caddy-redirects.js","sourceRoot":"","sources":["../src/caddy-redirects.ts"],"names":[],"mappings":"AAAA,mCAAmC;AAEnC;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,sCAAsC;AACtC,MAAM,YAAY,GAAG,CAAC,CAAC;AAEvB;;;;;;GAMG;AACH,MAAM,UAAU,yBAAyB,CAAC,WAAmB;IAC3D,IAAI,iBAAiB,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CAAC,wCAAwC,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;IACzF,CAAC;IACD,MAAM,MAAM,GAAG,mDAAmD,CAAC;IACnE,OAAO;QACL,eAAe,WAAW,0BAA0B;QACpD,aAAa;QACb,aAAa,MAAM,IAAI;QACvB,QAAQ;QACR,qEAAqE;QACrE,sCAAsC;QACtC,WAAW,YAAY,EAAE;QACzB,cAAc;QACd,oCAAoC;QACpC,oEAAoE;QACpE,WAAW;QACX,qCAAqC;QACrC,aAAa;QACb,kBAAkB,MAAM,6GAA6G;QACrI,MAAM;QACN,aAAa;KACd,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,0BAA0B,CAAC,WAAmB;IAC5D,MAAM,MAAM,GAAG,yBAAyB,CAAC,WAAW,CAAC,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;IAClF,OAAO,8BAA8B,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC;AACjE,CAAC"}
package/dist/caddy.d.ts CHANGED
@@ -3,13 +3,33 @@ export interface CaddyDomainSpec {
3
3
  readonly kind: "admin" | "public";
4
4
  readonly env: "production" | "staging";
5
5
  }
6
+ /**
7
+ * How Caddy gets certificates: `acme` — publicly trusted ones from Let's
8
+ * Encrypt / ZeroSSL (needs public DNS + ports 80/443 reachable); `internal`
9
+ * — Caddy's own local CA, for a LAN or evaluation install without public
10
+ * DNS (browsers warn until the CA root is trusted).
11
+ */
12
+ export type CaddyTlsMode = "acme" | "internal";
6
13
  export interface CaddyfileSpec {
7
14
  readonly ownerEmail: string;
15
+ readonly tls: CaddyTlsMode;
8
16
  readonly publicSiteRoot: string;
9
17
  readonly stagingSiteRoot: string;
10
- readonly adminPort: number;
11
- readonly gatewayPort: number;
18
+ /** `host:port` of the admin as Caddy reaches it (Compose: `caelo-admin:5173`). */
19
+ readonly adminUpstream: string;
20
+ /** `host:port` of the API gateway as Caddy reaches it (Compose: `caelo-gateway:8090`). */
21
+ readonly gatewayUpstream: string;
12
22
  readonly domains: ReadonlyArray<CaddyDomainSpec>;
13
23
  }
24
+ /** The host the admin of a self-hosted install answers on (same convention as GCP). */
25
+ export declare function selfHostedAdminHost(domain: string): string;
26
+ /**
27
+ * The vhosts every self-hosted install serves before an Owner adds domains
28
+ * in the admin: the admin on `admin.<domain>`, the live site on `<domain>`,
29
+ * the staging site on its own host. The admin needs its own host — it
30
+ * routes from `/` (`/setup`, `/login`, …), so it cannot share one with the
31
+ * public site, and Caddy refuses two site blocks for the same address.
32
+ */
33
+ export declare function selfHostedSeedDomains(domain: string, stagingDomain: string): CaddyDomainSpec[];
14
34
  export declare function generateCaddyfile(spec: CaddyfileSpec): string;
15
35
  //# sourceMappingURL=caddy.d.ts.map
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"caddy.d.ts","sourceRoot":"","sources":["../src/caddy.ts"],"names":[],"mappings":"AA2DA,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;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,UAAU,CAAC;AAE/C,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,GAAG,EAAE,YAAY,CAAC;IAC3B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,kFAAkF;IAClF,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,0FAA0F;IAC1F,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;CAClD;AAED,uFAAuF;AACvF,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,eAAe,EAAE,CAM9F;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,CAqC7D"}
package/dist/caddy.js CHANGED
@@ -4,18 +4,32 @@
4
4
  *
5
5
  * Reads the `domains` table + the deploy targets and emits a
6
6
  * deterministic Caddyfile string. The CLI's `regenerate-caddy`
7
- * sub-command writes this to `/etc/caddy/Caddyfile` and runs
7
+ * sub-command writes this to `.caelo/Caddyfile` and runs
8
8
  * `caddy reload`.
9
9
  *
10
10
  * Per-vhost shape:
11
11
  * <hostname> {
12
- * # admin → reverse_proxy localhost:5173
13
- * # public → root + try_files (static), with /api/* → gateway
14
- * tls <ownerEmail>
12
+ * # admin → reverse_proxy <adminUpstream>
13
+ * # public → /api/* → <gatewayUpstream>; everything else is the
14
+ * # static site (try_files + file_server, 404 → /404.html)
15
15
  * }
16
16
  *
17
+ * Upstreams are `host:port` as seen FROM CADDY. In the Compose install
18
+ * Caddy runs in its own container, where `localhost` is Caddy itself, so
19
+ * the CLI passes the Compose service names (`caelo-admin:5173`,
20
+ * `caelo-gateway:8090`) — issue #707.
21
+ *
22
+ * Every directive here must be one Caddy accepts: the output is checked
23
+ * with `caddy validate` on the pinned Caddy image (caddy-validate.test.ts,
24
+ * run in CI) — issue #566 shipped a `try_files` nested inside
25
+ * `file_server { }`, which Caddy 2.11 refuses, so no vhost came up.
26
+ *
17
27
  * Staging vhosts force `X-Robots-Tag: noindex`.
18
28
  *
29
+ * Public vhosts import the redirect snippet of the build they serve
30
+ * (`_redirects.caddy`, #587); the Caddy container reloads itself when a
31
+ * deploy changes it (caddy-redirects.ts).
32
+ *
19
33
  * Public vhosts mark content-hashed build outputs (fonts, plugin
20
34
  * bundles — `CONTENT_HASHED_PATH_PATTERN` in @caelo-cms/shared)
21
35
  * `immutable` for a year. Everything else keeps Caddy's file_server
@@ -25,17 +39,49 @@
25
39
  * hashed URL (an old page requesting an asset the new build dropped)
26
40
  * is never cached for a year.
27
41
  *
42
+ * A path that matches no file is a real 404 serving the generator's
43
+ * `/404.html` — never the home page with a 200 (an SPA-style
44
+ * `/index.html` fallback would turn every dead URL into a soft 404).
45
+ * Same behaviour as the GCS / S3 / Blob adapters (CLAUDE.md §11.B).
46
+ *
28
47
  * `.ico` files (theme favicons) are served as `image/x-icon`, the one
29
48
  * icon MIME the media library stores and `<link rel="icon" type>`
30
49
  * declares. file_server only fills Content-Type when no earlier handler
31
50
  * set it, so the `header` line wins over the host's mime.types name.
32
51
  */
33
- import { CONTENT_HASHED_PATH_PATTERN, IMMUTABLE_CACHE_CONTROL } from "@caelo-cms/shared";
52
+ import { CONTENT_HASHED_PATH_PATTERN, caddyRedirectsImport, IMMUTABLE_CACHE_CONTROL, } from "@caelo-cms/shared";
53
+ /** The host the admin of a self-hosted install answers on (same convention as GCP). */
54
+ export function selfHostedAdminHost(domain) {
55
+ return `admin.${domain}`;
56
+ }
57
+ /**
58
+ * The vhosts every self-hosted install serves before an Owner adds domains
59
+ * in the admin: the admin on `admin.<domain>`, the live site on `<domain>`,
60
+ * the staging site on its own host. The admin needs its own host — it
61
+ * routes from `/` (`/setup`, `/login`, …), so it cannot share one with the
62
+ * public site, and Caddy refuses two site blocks for the same address.
63
+ */
64
+ export function selfHostedSeedDomains(domain, stagingDomain) {
65
+ return [
66
+ { hostname: selfHostedAdminHost(domain), kind: "admin", env: "production" },
67
+ { hostname: domain, kind: "public", env: "production" },
68
+ { hostname: stagingDomain, kind: "public", env: "staging" },
69
+ ];
70
+ }
34
71
  export function generateCaddyfile(spec) {
72
+ const hostnames = new Set();
73
+ for (const d of spec.domains) {
74
+ if (hostnames.has(d.hostname)) {
75
+ // Caddy refuses a config with two site blocks for one address, so a
76
+ // duplicate would take every vhost down, not just this one.
77
+ throw new Error(`Caddyfile: ${d.hostname} is listed twice — one host serves either the admin or a public site, not both.`);
78
+ }
79
+ hostnames.add(d.hostname);
80
+ }
35
81
  const blocks = [];
36
82
  // Global options.
37
83
  blocks.push(`{
38
- email ${spec.ownerEmail}
84
+ email ${spec.ownerEmail}${spec.tls === "internal" ? "\n local_certs" : ""}
39
85
  }
40
86
  `);
41
87
  for (const d of spec.domains) {
@@ -47,7 +93,7 @@ export function generateCaddyfile(spec) {
47
93
  blocks.push(`# No domains configured yet — cms-provision regenerate-caddy
48
94
  # will overwrite this file when you add one at /settings/domains.
49
95
  :8081 {
50
- reverse_proxy localhost:${spec.adminPort}
96
+ reverse_proxy ${spec.adminUpstream}
51
97
  }
52
98
  `);
53
99
  }
@@ -57,15 +103,18 @@ function vhost(d, spec) {
57
103
  const noindex = d.env === "staging" ? `\n header X-Robots-Tag "noindex"` : "";
58
104
  if (d.kind === "admin") {
59
105
  return `${d.hostname} {${noindex}
60
- reverse_proxy localhost:${spec.adminPort}
106
+ reverse_proxy ${spec.adminUpstream}
61
107
  }
62
108
  `;
63
109
  }
64
- // public — API routes go to the gateway,
65
- // the rest serves static files. Locale variants serve from a
110
+ // public — API routes go to the gateway, the rest is the static site.
111
+ // `try_files` is its own directive (a rewrite), never a file_server
112
+ // subdirective (#566).
66
113
  const root = d.env === "staging" ? spec.stagingSiteRoot : spec.publicSiteRoot;
114
+ // #587 — the redirects table of the build this vhost serves.
67
115
  return `${d.hostname} {${noindex}
68
116
  root * ${root}
117
+ ${caddyRedirectsImport(root)}
69
118
  @content_hashed {
70
119
  path_regexp content_hashed ${CONTENT_HASHED_PATH_PATTERN}
71
120
  file
@@ -74,15 +123,15 @@ function vhost(d, spec) {
74
123
  @ico path *.ico
75
124
  header @ico Content-Type "image/x-icon"
76
125
  handle /api/* {
77
- reverse_proxy localhost:${spec.gatewayPort}
78
- }
79
- handle /admin/* {
80
- reverse_proxy localhost:${spec.adminPort}
126
+ reverse_proxy ${spec.gatewayUpstream}
81
127
  }
82
128
  handle {
83
- file_server {
84
- try_files {path} {path}/index.html /index.html
85
- }
129
+ try_files {path} {path}/index.html
130
+ file_server
131
+ }
132
+ handle_errors 404 {
133
+ rewrite * /404.html
134
+ file_server
86
135
  }
87
136
  }
88
137
  `;
package/dist/caddy.js.map CHANGED
@@ -1 +1 @@
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
+ {"version":3,"file":"caddy.js","sourceRoot":"","sources":["../src/caddy.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"cli-routing.d.ts","sourceRoot":"","sources":["../src/cli-routing.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AAEH,+CAA+C;AAC/C,MAAM,MAAM,QAAQ,GAAG;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAElG;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,QAAQ,CAWjG;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAE/D"}
1
+ {"version":3,"file":"cli-routing.d.ts","sourceRoot":"","sources":["../src/cli-routing.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AAEH,+CAA+C;AAC/C,MAAM,MAAM,QAAQ,GAAG;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAElG;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,QAAQ,CAkBjG;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAE/D"}
@@ -17,6 +17,13 @@ export function resolveCliRoute(argv, handlerNames) {
17
17
  if (argv.includes("--no-wizard") || argv.includes("--help") || argv.includes("-h")) {
18
18
  return { kind: "usage" };
19
19
  }
20
+ // #707 — `--provider self-hosted --domain … --owner-email …` is the
21
+ // self-hosted one-command install: `init` writes `.caelo/` and starts the
22
+ // stack. (The cloud wizard has nothing to provision for it.)
23
+ const provider = argv.indexOf("--provider");
24
+ if (provider !== -1 && argv[provider + 1] === "self-hosted") {
25
+ return { kind: "handler", name: "init" };
26
+ }
20
27
  return { kind: "wizard" };
21
28
  }
22
29
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"cli-routing.js","sourceRoot":"","sources":["../src/cli-routing.ts"],"names":[],"mappings":"AAAA,mCAAmC;AAenC;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,IAAuB,EAAE,YAA8B;IACrF,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IACtB,IAAI,KAAK,KAAK,SAAS,IAAI,IAAI,GAAG,CAAC,YAAY,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;QAC5D,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IAC1C,CAAC;IACD,MAAM,SAAS,GAAG,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IAC/D,IAAI,CAAC,SAAS;QAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IACzC,IAAI,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACnF,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IAC3B,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;AAC5B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,QAAgB;IACpD,OAAO,QAAQ,KAAK,aAAa,CAAC;AACpC,CAAC"}
1
+ {"version":3,"file":"cli-routing.js","sourceRoot":"","sources":["../src/cli-routing.ts"],"names":[],"mappings":"AAAA,mCAAmC;AAenC;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,IAAuB,EAAE,YAA8B;IACrF,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IACtB,IAAI,KAAK,KAAK,SAAS,IAAI,IAAI,GAAG,CAAC,YAAY,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;QAC5D,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IAC1C,CAAC;IACD,MAAM,SAAS,GAAG,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IAC/D,IAAI,CAAC,SAAS;QAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IACzC,IAAI,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACnF,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IAC3B,CAAC;IACD,oEAAoE;IACpE,0EAA0E;IAC1E,6DAA6D;IAC7D,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;IAC5C,IAAI,QAAQ,KAAK,CAAC,CAAC,IAAI,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC,KAAK,aAAa,EAAE,CAAC;QAC5D,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAC3C,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;AAC5B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,QAAgB;IACpD,OAAO,QAAQ,KAAK,aAAa,CAAC;AACpC,CAAC"}