@caelo-cms/provisioning 0.10.51 → 0.10.52
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/caddy/Caddyfile.production +1 -1
- package/caddy/Caddyfile.staging +1 -1
- package/dist/agent/units.js +1 -1
- package/dist/agent/units.js.map +1 -1
- package/dist/agent-db.js +80 -45
- package/dist/infra-contract.d.ts +1 -1
- package/dist/infra-contract.d.ts.map +1 -1
- package/dist/infra-contract.js +7 -1
- package/dist/infra-contract.js.map +1 -1
- package/dist/ops-backend.js +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/state-store.d.ts +8 -0
- package/dist/state-store.d.ts.map +1 -1
- package/dist/state-store.js +10 -2
- package/dist/state-store.js.map +1 -1
- package/dist/upgrader-job.d.ts.map +1 -1
- package/dist/upgrader-job.js +7 -1
- package/dist/upgrader-job.js.map +1 -1
- package/dist/upgrader-setup.d.ts +5 -0
- package/dist/upgrader-setup.d.ts.map +1 -1
- package/dist/upgrader-setup.js +40 -6
- package/dist/upgrader-setup.js.map +1 -1
- package/dist/wizards/gcp.js +1 -1
- package/dist/wizards/gcp.js.map +1 -1
- package/package.json +7 -5
- package/src/agent/units.ts +1 -1
- package/src/caddy-redirects.docker.test.ts +8 -1
- package/src/error-pages-stacks.test.ts +38 -0
- package/src/infra-contract.test.ts +4 -3
- package/src/infra-contract.ts +7 -1
- package/src/ops-backend.ts +1 -1
- package/src/release-contract.ts +3 -0
- package/src/release-manifest.test.ts +1 -0
- package/src/state-store.ts +11 -2
- package/src/upgrader-job.test.ts +17 -1
- package/src/upgrader-job.ts +13 -3
- package/src/upgrader-setup.test.ts +12 -1
- package/src/upgrader-setup.ts +41 -5
- package/src/welcome-page.test.ts +40 -0
- package/src/wizards/gcp.ts +1 -1
- package/stacks/aws/README.md +2 -0
- package/stacks/aws/index.ts +7 -0
- package/stacks/azure/README.md +2 -0
- package/static/welcome.html +93 -111
package/src/upgrader-setup.ts
CHANGED
|
@@ -31,7 +31,13 @@
|
|
|
31
31
|
* health check signs its IAP credential as that account
|
|
32
32
|
* (upgrade/health.ts). The operator-access sync job keeps it there.
|
|
33
33
|
* - `roles/storage.objectUser` on the ops bucket, conditioned to the
|
|
34
|
-
* `install/` prefix (install.json) — not the Pulumi state.
|
|
34
|
+
* `install/` prefix (install.json, the state lock) — not the Pulumi state.
|
|
35
|
+
* - the custom role `caeloUpgraderOpsList` (`storage.objects.list` only)
|
|
36
|
+
* on the ops bucket, unconditioned: `gcloud storage cp` lists the
|
|
37
|
+
* destination prefix and reads the bucket's storage layout before every
|
|
38
|
+
* upload, and GCS checks both against the bucket, never an object path,
|
|
39
|
+
* so no `install/` condition can admit them. It reveals object names,
|
|
40
|
+
* not contents.
|
|
35
41
|
* - `roles/logging.logWriter`.
|
|
36
42
|
*
|
|
37
43
|
* It gets no `*.setIamPolicy`, no service-account admin, no secret
|
|
@@ -71,6 +77,18 @@ export const UPGRADER_SQL_BACKUP_ROLE: CustomRoleSpec = {
|
|
|
71
77
|
],
|
|
72
78
|
};
|
|
73
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Listing the ops bucket, which every `gcloud storage cp` into it needs (see
|
|
82
|
+
* the file header). Names only — reads and writes stay on `install/`.
|
|
83
|
+
*/
|
|
84
|
+
export const UPGRADER_OPS_LIST_ROLE: CustomRoleSpec = {
|
|
85
|
+
roleId: "caeloUpgraderOpsList",
|
|
86
|
+
title: "Caelo upgrader: list the ops bucket",
|
|
87
|
+
description:
|
|
88
|
+
"Lets the Caelo upgrader job list object names in the install's ops bucket (gcloud storage cp needs it). Names only; object reads/writes stay limited to install/.",
|
|
89
|
+
permissions: ["storage.objects.list"],
|
|
90
|
+
};
|
|
91
|
+
|
|
74
92
|
/** Project-level roles of the upgrader's service account. */
|
|
75
93
|
export const UPGRADER_PROJECT_ROLES = [
|
|
76
94
|
"roles/run.developer",
|
|
@@ -176,10 +194,12 @@ export async function ensureUpgraderJob(
|
|
|
176
194
|
};
|
|
177
195
|
}
|
|
178
196
|
|
|
179
|
-
// 1. The backup
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
197
|
+
// 1. The backup and ops-bucket-list roles.
|
|
198
|
+
for (const role of [UPGRADER_SQL_BACKUP_ROLE, UPGRADER_OPS_LIST_ROLE]) {
|
|
199
|
+
const roleError = await ensureCustomRole(run, projectId, role);
|
|
200
|
+
if (roleError) return { ok: false, done, error: roleError };
|
|
201
|
+
done.push(`custom role ${role.roleId}`);
|
|
202
|
+
}
|
|
183
203
|
|
|
184
204
|
// 2. The job's own service account.
|
|
185
205
|
const describe = await run([
|
|
@@ -276,6 +296,22 @@ export async function ensureUpgraderJob(
|
|
|
276
296
|
return fail(`grant the upgrader install.json in gs://${bucket}`, bucketGrant);
|
|
277
297
|
done.push(`upgrader: roles/storage.objectUser on gs://${bucket}/${OPS_INSTALL_PREFIX}/`);
|
|
278
298
|
|
|
299
|
+
const listRole = customRoleName(projectId, UPGRADER_OPS_LIST_ROLE.roleId);
|
|
300
|
+
const listGrant = await retry([
|
|
301
|
+
"storage",
|
|
302
|
+
"buckets",
|
|
303
|
+
"add-iam-policy-binding",
|
|
304
|
+
`gs://${bucket}`,
|
|
305
|
+
`--member=${member}`,
|
|
306
|
+
`--role=${listRole}`,
|
|
307
|
+
"--condition=None",
|
|
308
|
+
project,
|
|
309
|
+
"--quiet",
|
|
310
|
+
"--format=none",
|
|
311
|
+
]);
|
|
312
|
+
if (!listGrant.ok) return fail(`grant the upgrader object listing on gs://${bucket}`, listGrant);
|
|
313
|
+
done.push(`upgrader: ${listRole} on gs://${bucket}`);
|
|
314
|
+
|
|
279
315
|
// 6. Its database login (migration 0267's platform_updater role).
|
|
280
316
|
const sqlUser = await ensureCloudSqlIamUser(run, {
|
|
281
317
|
projectId,
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* #781 — contracts of `static/welcome.html`, the placeholder the GCP
|
|
5
|
+
* wizard uploads to a fresh static bucket. It must stay fully
|
|
6
|
+
* self-contained (the bucket holds nothing else, and a first impression
|
|
7
|
+
* that fetches from third parties leaks the visitor), keep the
|
|
8
|
+
* `{{ADMIN_URL}}` placeholder the wizard substitutes, and carry the
|
|
9
|
+
* Caelatum sky instead of the old animated mesh.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { describe, expect, it } from "bun:test";
|
|
13
|
+
import { readFileSync } from "node:fs";
|
|
14
|
+
|
|
15
|
+
const html = readFileSync(new URL("../static/welcome.html", import.meta.url), "utf8");
|
|
16
|
+
|
|
17
|
+
describe("welcome.html", () => {
|
|
18
|
+
it("makes no external requests", () => {
|
|
19
|
+
// Any absolute or protocol-relative URL, stylesheet/script/image
|
|
20
|
+
// reference or CSS import/url() would be a network fetch.
|
|
21
|
+
expect(html).not.toMatch(/https?:\/\//i);
|
|
22
|
+
expect(html).not.toMatch(/["'(]\s*\/\//);
|
|
23
|
+
expect(html).not.toMatch(/<link\b/i);
|
|
24
|
+
expect(html).not.toMatch(/<script\b/i);
|
|
25
|
+
expect(html).not.toMatch(/<img\b/i);
|
|
26
|
+
expect(html).not.toMatch(/@import|url\(|@font-face/i);
|
|
27
|
+
expect(html).not.toMatch(/\b(src|href)\s*=/i);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("keeps the admin URL placeholder the wizard substitutes", () => {
|
|
31
|
+
expect(html).toContain("{{ADMIN_URL}}");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("uses the sky + relief plate with the inline mark, not the mesh", () => {
|
|
35
|
+
expect(html).toContain("#c3dbf2 0%, #dce9f6 40%, #efe7ef 72%, #f8e6d8 100%");
|
|
36
|
+
expect(html).toContain("0 0 0 5px var(--plate-ring)");
|
|
37
|
+
expect(html).toMatch(/<svg viewBox="0 0 32 32" aria-hidden="true"/);
|
|
38
|
+
expect(html).not.toMatch(/class="blob|@keyframes (drift|pulse)/);
|
|
39
|
+
});
|
|
40
|
+
});
|
package/src/wizards/gcp.ts
CHANGED
|
@@ -1965,7 +1965,7 @@ async function stepFinalize(installId: string, setupLink: "print" | "secret"): P
|
|
|
1965
1965
|
` click ${bold("New token")} (scope ${bold("chat")}: Caelo's AI does the work; ${bold("admin")}: your agent drives Caelo's tools),`,
|
|
1966
1966
|
" copy the bearer (shown once) and run the `claude mcp add` command the page shows.",
|
|
1967
1967
|
" The admin is behind Google IAP, so run `gcloud auth application-default login` once on that machine.",
|
|
1968
|
-
` Details: ${cyan("https://caelo-cms.com/mcp")}`,
|
|
1968
|
+
` Details: ${cyan("https://caelo-cms.com/docs/mcp/")}`,
|
|
1969
1969
|
...adminNote,
|
|
1970
1970
|
"",
|
|
1971
1971
|
bold("Lifecycle commands:"),
|
package/stacks/aws/README.md
CHANGED
|
@@ -18,6 +18,8 @@ The Caelo runtime never knows it's on AWS — it just consumes `DATABASE_URL` /
|
|
|
18
18
|
|
|
19
19
|
## First-time install
|
|
20
20
|
|
|
21
|
+
Run this stack from a checkout of the Caelo repository after `bun install` at its root. `@pulumi/aws` + `@pulumi/awsx` are devDependencies of `@caelo-cms/provisioning`: the CLI wizard does not provision AWS yet, so the published package does not make every GCP and self-hosted install download them (the one-click install from Cloud Shell runs in a 5 GB home directory). They move back to `dependencies` when the AWS wizard ships.
|
|
22
|
+
|
|
21
23
|
```bash
|
|
22
24
|
cd packages/provisioning/stacks/aws
|
|
23
25
|
pulumi stack init prod
|
package/stacks/aws/index.ts
CHANGED
|
@@ -297,7 +297,14 @@ const distribution = new aws.cloudfront.Distribution(`${namePrefix}-cdn`, {
|
|
|
297
297
|
minimumProtocolVersion: "TLSv1.2_2021",
|
|
298
298
|
},
|
|
299
299
|
restrictions: { geoRestriction: { restrictionType: "none" } },
|
|
300
|
+
// #802 — unknown URLs get the site's own 404 page (the generator writes
|
|
301
|
+
// `/404.html` from the designated page) with status 404. The S3 origins
|
|
302
|
+
// are OAI-only with `s3:GetObject` and no `s3:ListBucket`, so S3 answers
|
|
303
|
+
// a missing key with 403, not 404; without the 403 entry visitors saw
|
|
304
|
+
// S3's AccessDenied XML. Both origins on this distribution are private
|
|
305
|
+
// buckets, so a 403 here always means "no such object".
|
|
300
306
|
customErrorResponses: [
|
|
307
|
+
{ errorCode: 403, responseCode: 404, responsePagePath: "/404.html", errorCachingMinTtl: 60 },
|
|
301
308
|
{ errorCode: 404, responseCode: 404, responsePagePath: "/404.html", errorCachingMinTtl: 60 },
|
|
302
309
|
],
|
|
303
310
|
});
|
package/stacks/azure/README.md
CHANGED
|
@@ -17,6 +17,8 @@ The Caelo runtime never knows it's on Azure — every Container App consumes pla
|
|
|
17
17
|
|
|
18
18
|
## First-time install
|
|
19
19
|
|
|
20
|
+
Run this stack from a checkout of the Caelo repository after `bun install` at its root. `@pulumi/azure-native` is a devDependency of `@caelo-cms/provisioning`: the CLI wizard does not provision Azure yet, so the published package does not make every GCP and self-hosted install download it (the one-click install from Cloud Shell runs in a 5 GB home directory). It moves back to `dependencies` when the Azure wizard ships.
|
|
21
|
+
|
|
20
22
|
```bash
|
|
21
23
|
cd packages/provisioning/stacks/azure
|
|
22
24
|
pulumi stack init prod
|
package/static/welcome.html
CHANGED
|
@@ -5,151 +5,133 @@
|
|
|
5
5
|
Uploaded by the provisioning wizard the first time it brings up the
|
|
6
6
|
static bucket. Replaced the moment the operator publishes their
|
|
7
7
|
first deploy via the admin app's "Publish" flow.
|
|
8
|
+
|
|
9
|
+
#781 — the first arrival screen: the Caelatum sky with the message on
|
|
10
|
+
a relief plate, matching the admin's sign-in screens. Fully
|
|
11
|
+
self-contained on purpose: inline CSS, inline SVG mark, no web fonts
|
|
12
|
+
and no other external requests (the page is served from a bucket that
|
|
13
|
+
has nothing else in it yet). Schibsted Grotesk is used when installed
|
|
14
|
+
locally; otherwise the fallback stack renders.
|
|
8
15
|
-->
|
|
9
16
|
<html lang="en">
|
|
10
17
|
<head>
|
|
11
18
|
<meta charset="utf-8">
|
|
12
19
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
13
20
|
<meta name="robots" content="noindex">
|
|
21
|
+
<meta name="color-scheme" content="light dark">
|
|
14
22
|
<title>Caelo CMS — your install is ready</title>
|
|
15
23
|
<style>
|
|
16
24
|
*,*::before,*::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
17
25
|
:root {
|
|
18
|
-
--
|
|
19
|
-
--muted: #
|
|
20
|
-
--
|
|
21
|
-
--
|
|
22
|
-
--
|
|
23
|
-
--
|
|
24
|
-
--
|
|
26
|
+
--ink: #0e1a2e;
|
|
27
|
+
--muted: #4a576b;
|
|
28
|
+
--card: #ffffff;
|
|
29
|
+
--line: #e0e5ee;
|
|
30
|
+
--code-bg: #f2f5fa;
|
|
31
|
+
--sky-label: #1f5299;
|
|
32
|
+
--sky: linear-gradient(180deg, #c3dbf2 0%, #dce9f6 40%, #efe7ef 72%, #f8e6d8 100%);
|
|
33
|
+
--plate-ring: rgba(255, 255, 255, 0.55);
|
|
34
|
+
}
|
|
35
|
+
/* The sky is light-mode only; dark mode is the admin's night blue. */
|
|
36
|
+
@media (prefers-color-scheme: dark) {
|
|
37
|
+
:root {
|
|
38
|
+
--ink: #e6ecf5;
|
|
39
|
+
--muted: #9fb0c8;
|
|
40
|
+
--card: #111c2e;
|
|
41
|
+
--line: #22314a;
|
|
42
|
+
--code-bg: #16233a;
|
|
43
|
+
--sky-label: #7fb4ff;
|
|
44
|
+
--sky: none;
|
|
45
|
+
--plate-ring: rgba(255, 255, 255, 0.08);
|
|
46
|
+
}
|
|
25
47
|
}
|
|
26
|
-
html, body { height: 100%; }
|
|
48
|
+
html, body { min-height: 100%; }
|
|
27
49
|
body {
|
|
28
|
-
font: 16px/1.55
|
|
29
|
-
color: var(--
|
|
50
|
+
font: 16px/1.55 "Schibsted Grotesk", Arial, Helvetica, sans-serif;
|
|
51
|
+
color: var(--ink);
|
|
52
|
+
background: #f8f9fc var(--sky);
|
|
53
|
+
min-height: 100vh;
|
|
30
54
|
display: grid;
|
|
31
55
|
place-items: center;
|
|
32
|
-
padding:
|
|
33
|
-
background: #f6f6fb;
|
|
34
|
-
overflow: hidden;
|
|
35
|
-
position: relative;
|
|
36
|
-
}
|
|
37
|
-
/* Animated mesh-gradient background. Three soft blobs orbiting at
|
|
38
|
-
different speeds; backdrop-filter on the card softens them. */
|
|
39
|
-
.blob {
|
|
40
|
-
position: fixed;
|
|
41
|
-
border-radius: 50%;
|
|
42
|
-
filter: blur(80px);
|
|
43
|
-
opacity: 0.55;
|
|
44
|
-
pointer-events: none;
|
|
45
|
-
animation: drift 22s ease-in-out infinite;
|
|
46
|
-
will-change: transform;
|
|
56
|
+
padding: 3rem 1rem;
|
|
47
57
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
.blob.b3 { width: 380px; height: 380px; background: #67e8f9; top: 40%; left: 55%; animation-delay: -14s; animation-duration: 27s; }
|
|
51
|
-
@keyframes drift {
|
|
52
|
-
0%, 100% { transform: translate(0, 0) scale(1); }
|
|
53
|
-
33% { transform: translate(40px, -30px) scale(1.07); }
|
|
54
|
-
66% { transform: translate(-30px, 35px) scale(0.96); }
|
|
58
|
+
@media (prefers-color-scheme: dark) {
|
|
59
|
+
body { background: #0b1422; }
|
|
55
60
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
main {
|
|
61
|
-
position: relative;
|
|
62
|
-
z-index: 1;
|
|
63
|
-
max-width: 36rem;
|
|
64
|
-
width: 100%;
|
|
65
|
-
background: var(--card-bg);
|
|
66
|
-
border: 1px solid var(--card-border);
|
|
67
|
-
border-radius: 22px;
|
|
68
|
-
padding: 2.5rem;
|
|
69
|
-
box-shadow: var(--shadow);
|
|
70
|
-
backdrop-filter: blur(14px) saturate(140%);
|
|
71
|
-
-webkit-backdrop-filter: blur(14px) saturate(140%);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
.brand {
|
|
75
|
-
display: flex;
|
|
76
|
-
align-items: center;
|
|
77
|
-
gap: 0.65rem;
|
|
61
|
+
.frame { width: 100%; max-width: 36rem; }
|
|
62
|
+
.kicker {
|
|
63
|
+
text-align: center;
|
|
64
|
+
font-size: 0.75rem;
|
|
78
65
|
font-weight: 600;
|
|
79
|
-
letter-spacing:
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
margin-bottom:
|
|
66
|
+
letter-spacing: 0.14em;
|
|
67
|
+
text-transform: uppercase;
|
|
68
|
+
color: var(--sky-label);
|
|
69
|
+
margin-bottom: 1rem;
|
|
83
70
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
@keyframes pulse {
|
|
91
|
-
0% { box-shadow: 0 0 0 0 rgba(79,70,229,0.55); }
|
|
92
|
-
70% { box-shadow: 0 0 0 12px rgba(79,70,229,0); }
|
|
93
|
-
100% { box-shadow: 0 0 0 0 rgba(79,70,229,0); }
|
|
71
|
+
main {
|
|
72
|
+
background: var(--card);
|
|
73
|
+
border: 1px solid var(--line);
|
|
74
|
+
border-radius: 14px;
|
|
75
|
+
padding: 2.25rem 2.5rem;
|
|
76
|
+
box-shadow: 0 0 0 5px var(--plate-ring), 0 18px 40px -26px rgba(42, 64, 110, 0.45);
|
|
94
77
|
}
|
|
95
|
-
@media (
|
|
96
|
-
|
|
78
|
+
@media (max-width: 480px) {
|
|
79
|
+
main { padding: 1.5rem 1.25rem; }
|
|
97
80
|
}
|
|
98
|
-
|
|
99
81
|
h1 {
|
|
100
|
-
|
|
82
|
+
display: flex;
|
|
83
|
+
align-items: center;
|
|
84
|
+
gap: 0.75rem;
|
|
85
|
+
font-size: clamp(1.5rem, 1.2rem + 1.2vw, 2rem);
|
|
101
86
|
letter-spacing: -0.02em;
|
|
102
87
|
line-height: 1.15;
|
|
103
|
-
margin-bottom: 0.
|
|
88
|
+
margin-bottom: 0.85rem;
|
|
104
89
|
}
|
|
105
|
-
|
|
106
|
-
p
|
|
107
|
-
|
|
108
|
-
.cta {
|
|
109
|
-
display: inline-flex;
|
|
110
|
-
align-items: center;
|
|
111
|
-
gap: 0.5rem;
|
|
112
|
-
margin-top: 1.5rem;
|
|
113
|
-
padding: 0.75rem 1.1rem;
|
|
114
|
-
background: var(--fg);
|
|
115
|
-
color: #fff;
|
|
116
|
-
text-decoration: none;
|
|
117
|
-
border-radius: 12px;
|
|
118
|
-
font-weight: 500;
|
|
119
|
-
transition: transform 0.15s ease, box-shadow 0.15s ease;
|
|
120
|
-
}
|
|
121
|
-
.cta:hover { transform: translateY(-1px); box-shadow: 0 12px 24px -8px rgba(15,17,40,0.25); }
|
|
122
|
-
.cta svg { width: 14px; height: 14px; }
|
|
123
|
-
|
|
90
|
+
h1 svg { flex-shrink: 0; width: 36px; height: 36px; }
|
|
91
|
+
p { color: var(--muted); }
|
|
124
92
|
.hint {
|
|
125
|
-
margin-top:
|
|
126
|
-
padding-top: 1.
|
|
127
|
-
border-top: 1px solid
|
|
93
|
+
margin-top: 1.75rem;
|
|
94
|
+
padding-top: 1.25rem;
|
|
95
|
+
border-top: 1px solid var(--line);
|
|
128
96
|
font-size: 0.875rem;
|
|
129
97
|
color: var(--muted);
|
|
130
98
|
}
|
|
131
99
|
.hint code {
|
|
132
|
-
font: 0.85em
|
|
133
|
-
|
|
100
|
+
font: 0.85em Menlo, Consolas, monospace;
|
|
101
|
+
color: var(--ink);
|
|
102
|
+
background: var(--code-bg);
|
|
134
103
|
padding: 0.15em 0.45em;
|
|
135
104
|
border-radius: 6px;
|
|
105
|
+
overflow-wrap: anywhere;
|
|
106
|
+
}
|
|
107
|
+
/* One entrance settle, only for people who have not asked for
|
|
108
|
+
reduced motion; no looping animation on this page. */
|
|
109
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
110
|
+
main { animation: settle 0.5s ease-out both; }
|
|
111
|
+
@keyframes settle {
|
|
112
|
+
from { opacity: 0; transform: translateY(8px); }
|
|
113
|
+
to { opacity: 1; transform: none; }
|
|
114
|
+
}
|
|
136
115
|
}
|
|
137
|
-
.hint a { color: var(--accent); text-decoration: none; }
|
|
138
|
-
.hint a:hover { text-decoration: underline; }
|
|
139
116
|
</style>
|
|
140
117
|
</head>
|
|
141
118
|
<body>
|
|
142
|
-
<div class="
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
119
|
+
<div class="frame">
|
|
120
|
+
<p class="kicker">Caelo CMS</p>
|
|
121
|
+
<main>
|
|
122
|
+
<h1>
|
|
123
|
+
<svg viewBox="0 0 32 32" aria-hidden="true" focusable="false">
|
|
124
|
+
<path d="M23.07 7.57A11 11 0 1 0 23.07 24.43" fill="none" stroke="currentColor" stroke-width="3.4" stroke-linecap="round"/>
|
|
125
|
+
<path d="M20.5 10.64A7 7 0 1 0 20.5 21.36" fill="none" stroke="#c9952f" stroke-width="1.1" stroke-linecap="round"/>
|
|
126
|
+
<circle cx="25.2" cy="16" r="2.6" fill="#e8684a"/>
|
|
127
|
+
</svg>
|
|
128
|
+
Your install is up. One more step.
|
|
129
|
+
</h1>
|
|
130
|
+
<p>This is the placeholder Caelo serves on a fresh deploy. The operator who provisioned this install has a one-time bootstrap link in their terminal output — opening it walks through Owner-account setup. After publish, this page disappears.</p>
|
|
131
|
+
<div class="hint">
|
|
132
|
+
Bootstrap link expires in 24h and rotates per <code>bunx @caelo-cms/provisioning</code> run. After Owner setup, the admin lives at <code>{{ADMIN_URL}}</code> behind <code>Sign in with Google</code> (IAP allowlist).
|
|
133
|
+
</div>
|
|
134
|
+
</main>
|
|
135
|
+
</div>
|
|
154
136
|
</body>
|
|
155
137
|
</html>
|