@caelo-cms/provisioning 0.10.50 → 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/adapter.d.ts +4 -6
- package/dist/adapter.d.ts.map +1 -1
- package/dist/admin-internal.d.ts +41 -0
- package/dist/admin-internal.d.ts.map +1 -0
- package/dist/admin-internal.js +169 -0
- package/dist/admin-internal.js.map +1 -0
- package/dist/agent/units.js +1 -1
- package/dist/agent/units.js.map +1 -1
- package/dist/agent-db.js +169 -65
- package/dist/aws-task-roles.d.ts +27 -0
- package/dist/aws-task-roles.d.ts.map +1 -0
- package/dist/aws-task-roles.js +35 -0
- package/dist/aws-task-roles.js.map +1 -0
- package/dist/cli.js +34 -1
- package/dist/cli.js.map +1 -1
- package/dist/compose.js +2 -2
- package/dist/compose.js.map +1 -1
- package/dist/deployed-release.d.ts +3 -2
- package/dist/deployed-release.d.ts.map +1 -1
- package/dist/deployed-release.js +12 -5
- package/dist/deployed-release.js.map +1 -1
- package/dist/gateway-credentials.d.ts.map +1 -1
- package/dist/gateway-credentials.js +6 -3
- package/dist/gateway-credentials.js.map +1 -1
- package/dist/gcloud.d.ts +2 -2
- package/dist/gcloud.d.ts.map +1 -1
- package/dist/gcloud.js +3 -3
- package/dist/gcloud.js.map +1 -1
- package/dist/infra-contract.d.ts +1 -1
- package/dist/infra-contract.d.ts.map +1 -1
- package/dist/infra-contract.js +12 -1
- package/dist/infra-contract.js.map +1 -1
- package/dist/ops-backend.js +1 -1
- package/dist/owner-reset.d.ts +37 -0
- package/dist/owner-reset.d.ts.map +1 -0
- package/dist/owner-reset.js +80 -0
- package/dist/owner-reset.js.map +1 -0
- 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/stack-contract.d.ts +9 -10
- package/dist/stack-contract.d.ts.map +1 -1
- package/dist/stack-contract.js +21 -10
- package/dist/stack-contract.js.map +1 -1
- package/dist/staging-admin.d.ts +12 -20
- package/dist/staging-admin.d.ts.map +1 -1
- package/dist/staging-admin.js +7 -159
- package/dist/staging-admin.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-cost.js +1 -1
- package/dist/wizards/gcp.js +1 -1
- package/dist/wizards/gcp.js.map +1 -1
- package/package.json +7 -5
- package/src/adapter.ts +4 -6
- package/src/admin-internal.ts +212 -0
- package/src/agent/units.ts +1 -1
- package/src/aws-task-roles.ts +58 -0
- package/src/caddy-redirects.docker.test.ts +8 -1
- package/src/checkpoint-secrets.test.ts +0 -1
- package/src/cli.ts +41 -1
- package/src/compose.ts +2 -2
- package/src/deployed-release.test.ts +22 -4
- package/src/deployed-release.ts +14 -7
- package/src/error-pages-stacks.test.ts +38 -0
- package/src/gateway-credentials.ts +6 -3
- package/src/gateway-env-isolation.test.ts +212 -0
- package/src/gcloud.ts +5 -3
- package/src/index.test.ts +6 -6
- package/src/infra-contract.test.ts +5 -3
- package/src/infra-contract.ts +12 -1
- package/src/ops-backend.ts +1 -1
- package/src/owner-reset.test.ts +165 -0
- package/src/owner-reset.ts +106 -0
- package/src/release-contract.ts +3 -0
- package/src/release-manifest.test.ts +1 -0
- package/src/runtime-secrets.test.ts +25 -8
- package/src/stack-contract.test.ts +9 -18
- package/src/stack-contract.ts +25 -16
- package/src/stack-converge-secrets.test.ts +45 -10
- package/src/stack-converge.test.ts +19 -10
- package/src/staging-admin.ts +18 -181
- 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-cost.ts +1 -1
- package/src/wizards/gcp.ts +1 -1
- package/stacks/aws/README.md +3 -1
- package/stacks/aws/index.ts +62 -43
- package/stacks/azure/README.md +3 -1
- package/stacks/azure/index.ts +8 -14
- package/stacks/gcp/README.md +1 -1
- package/stacks/gcp/index.ts +2 -5
- package/stacks/gcp-firebase/index.ts +0 -3
- package/static/welcome.html +93 -111
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
# `file` gates the header on the file existing: a 404 for a hashed
|
|
23
23
|
# URL (old page after a deploy switch) must not be cached for a year.
|
|
24
24
|
@content_hashed {
|
|
25
|
-
path_regexp content_hashed ^/(?:_app/immutable/.+|_assets/fonts/[^/]+/[0-9a-f]{16}\.woff2|_assets/fonts/pinned/[0-9a-f]{64}\.[a-z]+(?:-[a-z]+)?\.[0-9a-f]{12}\.woff2|_assets/[a-z0-9][a-z0-9-]*(?:/[a-z][a-z0-9-]*)?\.[0-9a-f]{16}\.[a-z0-9]{2,5}|_caelo/plugin/[^/]+/[^/]+\.[0-9a-f]{12}\.(?:js|css))$
|
|
25
|
+
path_regexp content_hashed ^/(?:_app/immutable/.+|_assets/fonts/[^/]+/[0-9a-f]{16}\.woff2|_assets/fonts/pinned/[0-9a-f]{64}\.[0-9a-f]{12}\.[a-z]+(?:-[a-z]+)?\.[0-9a-f]{12}\.woff2|_assets/[a-z0-9][a-z0-9-]*(?:/[a-z][a-z0-9-]*)?\.[0-9a-f]{16}\.[a-z0-9]{2,5}|_caelo/plugin/[^/]+/[^/]+\.[0-9a-f]{12}\.(?:js|css))$
|
|
26
26
|
file
|
|
27
27
|
}
|
|
28
28
|
header @content_hashed Cache-Control "public, max-age=31536000, immutable"
|
package/caddy/Caddyfile.staging
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
# `file` gates the header on the file existing: a 404 for a hashed
|
|
27
27
|
# URL (old page after a deploy switch) must not be cached for a year.
|
|
28
28
|
@content_hashed {
|
|
29
|
-
path_regexp content_hashed ^/(?:_app/immutable/.+|_assets/fonts/[^/]+/[0-9a-f]{16}\.woff2|_assets/fonts/pinned/[0-9a-f]{64}\.[a-z]+(?:-[a-z]+)?\.[0-9a-f]{12}\.woff2|_assets/[a-z0-9][a-z0-9-]*(?:/[a-z][a-z0-9-]*)?\.[0-9a-f]{16}\.[a-z0-9]{2,5}|_caelo/plugin/[^/]+/[^/]+\.[0-9a-f]{12}\.(?:js|css))$
|
|
29
|
+
path_regexp content_hashed ^/(?:_app/immutable/.+|_assets/fonts/[^/]+/[0-9a-f]{16}\.woff2|_assets/fonts/pinned/[0-9a-f]{64}\.[0-9a-f]{12}\.[a-z]+(?:-[a-z]+)?\.[0-9a-f]{12}\.woff2|_assets/[a-z0-9][a-z0-9-]*(?:/[a-z][a-z0-9-]*)?\.[0-9a-f]{16}\.[a-z0-9]{2,5}|_caelo/plugin/[^/]+/[^/]+\.[0-9a-f]{12}\.(?:js|css))$
|
|
30
30
|
file
|
|
31
31
|
}
|
|
32
32
|
header @content_hashed Cache-Control "public, max-age=31536000, immutable"
|
package/dist/adapter.d.ts
CHANGED
|
@@ -49,14 +49,12 @@ export interface DnsRecord {
|
|
|
49
49
|
export interface CloudAdapterOutputs {
|
|
50
50
|
/** DSN for cms_admin role (encrypted in Pulumi state). */
|
|
51
51
|
readonly adminDatabaseUrl: string;
|
|
52
|
-
/** DSN for public_role on cms_public (encrypted in Pulumi state). */
|
|
53
|
-
readonly publicDatabaseUrl: string;
|
|
54
52
|
/**
|
|
55
|
-
* DSN for
|
|
56
|
-
*
|
|
57
|
-
*
|
|
53
|
+
* DSN for public_role on cms_public (encrypted in Pulumi state) — the API
|
|
54
|
+
* gateway's only database login. It never gets a cms_admin DSN of any
|
|
55
|
+
* role (CLAUDE.md §2, #635).
|
|
58
56
|
*/
|
|
59
|
-
readonly
|
|
57
|
+
readonly publicDatabaseUrl: string;
|
|
60
58
|
/** Provider-native blob URL (s3://, gs://, https://<account>.blob.core.windows.net/<container>). */
|
|
61
59
|
readonly mediaStorageUrl: string;
|
|
62
60
|
/** Public-facing URL the admin reaches for media reads. */
|
package/dist/adapter.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,SAAS,GAAG,YAAY,CAAC;AAE3D,MAAM,WAAW,kBAAkB;IACjC,mFAAmF;IACnF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,+EAA+E;IAC/E,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,2FAA2F;IAC3F,QAAQ,CAAC,YAAY,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC;IAClD,gFAAgF;IAChF,QAAQ,CAAC,qBAAqB,CAAC,EAAE;QAC/B,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;QAClC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;KAChC,CAAC;CACH;AAED;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,GAAG,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC;IAC9C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IAClC,0DAA0D;IAC1D,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,SAAS,GAAG,YAAY,CAAC;AAE3D,MAAM,WAAW,kBAAkB;IACjC,mFAAmF;IACnF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,+EAA+E;IAC/E,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,2FAA2F;IAC3F,QAAQ,CAAC,YAAY,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC;IAClD,gFAAgF;IAChF,QAAQ,CAAC,qBAAqB,CAAC,EAAE;QAC/B,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;QAClC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;KAChC,CAAC;CACH;AAED;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,GAAG,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC;IAC9C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IAClC,0DAA0D;IAC1D,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC;;;;OAIG;IACH,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,oGAAoG;IACpG,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,2DAA2D;IAC3D,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,wEAAwE;IACxE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,6DAA6D;IAC7D,QAAQ,CAAC,kBAAkB,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;IACtD;;;;OAIG;IACH,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,8FAA8F;IAC9F,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAC;IACrC,yDAAyD;IACzD,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;CACnC;AAED,MAAM,MAAM,iBAAiB,GAAG,aAAa,GAAG,KAAK,GAAG,cAAc,GAAG,KAAK,GAAG,OAAO,CAAC;AAEzF;;;;;GAKG;AACH,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC;IACtC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type GcloudResult } from "./gcloud.js";
|
|
2
|
+
type Run = (args: string[]) => Promise<GcloudResult>;
|
|
3
|
+
export interface AdminInternalTarget {
|
|
4
|
+
readonly projectId: string;
|
|
5
|
+
readonly region: string;
|
|
6
|
+
}
|
|
7
|
+
export interface AdminInternalDeps {
|
|
8
|
+
readonly run?: Run;
|
|
9
|
+
readonly fetch?: typeof fetch;
|
|
10
|
+
/** Seconds since epoch (IAP JWT); tests pin it. */
|
|
11
|
+
readonly now?: () => number;
|
|
12
|
+
}
|
|
13
|
+
/** One endpoint call: where, under which token scope, with what body. */
|
|
14
|
+
export interface AdminInternalCall {
|
|
15
|
+
/** Path under the admin origin, e.g. `/api/internal/owner-reset`. */
|
|
16
|
+
readonly endpoint: string;
|
|
17
|
+
/** Scope the endpoint's `requireInternalAuth` checks. */
|
|
18
|
+
readonly scope: string;
|
|
19
|
+
readonly body: Record<string, unknown>;
|
|
20
|
+
/** The issue that added the endpoint, named when an older admin answers 404. */
|
|
21
|
+
readonly introducedBy: string;
|
|
22
|
+
}
|
|
23
|
+
export type AdminInternalResult<T> = {
|
|
24
|
+
ok: true;
|
|
25
|
+
value: T;
|
|
26
|
+
} | {
|
|
27
|
+
ok: false;
|
|
28
|
+
error: string;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* POST `call.body` to the endpoint and return the admin's JSON answer and
|
|
32
|
+
* the admin URL it was reached at (to build links the operator opens).
|
|
33
|
+
* Every failure names the fix: an IAP refusal or a pre-endpoint admin points
|
|
34
|
+
* at `cms-provision upgrade`, a refusal carries the admin's own message.
|
|
35
|
+
*/
|
|
36
|
+
export declare function callAdminInternal<T>(target: AdminInternalTarget, call: AdminInternalCall, deps?: AdminInternalDeps): Promise<AdminInternalResult<{
|
|
37
|
+
answer: T;
|
|
38
|
+
adminUrl: string;
|
|
39
|
+
}>>;
|
|
40
|
+
export {};
|
|
41
|
+
//# sourceMappingURL=admin-internal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin-internal.d.ts","sourceRoot":"","sources":["../src/admin-internal.ts"],"names":[],"mappings":"AAuBA,OAAO,EAA2B,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAIzE,KAAK,GAAG,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;AAKrD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC;IACnB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IAC9B,mDAAmD;IACnD,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CAC7B;AAED,yEAAyE;AACzE,MAAM,WAAW,iBAAiB;IAChC,qEAAqE;IACrE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,yDAAyD;IACzD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,gFAAgF;IAChF,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,MAAM,mBAAmB,CAAC,CAAC,IAAI;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3F;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,CAAC,EACvC,MAAM,EAAE,mBAAmB,EAC3B,IAAI,EAAE,iBAAiB,EACvB,IAAI,GAAE,iBAAsB,GAC3B,OAAO,CAAC,mBAAmB,CAAC;IAAE,MAAM,EAAE,CAAC,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,CAkG/D"}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
/**
|
|
3
|
+
* The CLI's channel to the admin's `/api/internal/*` endpoints on a
|
|
4
|
+
* gcp-firebase install, for commands that need an answer BACK from the admin
|
|
5
|
+
* (a generated password, a reset link). A one-shot Cloud Run job could only
|
|
6
|
+
* report through Cloud Logging, which would put that secret into the
|
|
7
|
+
* project's logs; this call answers over TLS straight to the CLI process, and
|
|
8
|
+
* nothing is written anywhere (CLAUDE.md §7, #659).
|
|
9
|
+
*
|
|
10
|
+
* Getting there, as the operator's own gcloud identity — the same path the
|
|
11
|
+
* Power-MCP shim takes (packages/mcp-server/src/ingress-auth.ts):
|
|
12
|
+
*
|
|
13
|
+
* 1. the admin Cloud Run service and its URL;
|
|
14
|
+
* 2. the install's internal secret from Secret Manager (in memory only),
|
|
15
|
+
* which signs a 5-minute token for the endpoint's scope;
|
|
16
|
+
* 3. a JWT signed as `caelo-mcp@<project>` through the IAM Credentials
|
|
17
|
+
* API — the admin's IAP lets that service account in, and every
|
|
18
|
+
* IAP-allowlisted operator may sign as it (`cms-provision upgrade`
|
|
19
|
+
* grants it) — so the request passes IAP first, then Caelo's token.
|
|
20
|
+
*/
|
|
21
|
+
import { findService } from "./admin-domain.js";
|
|
22
|
+
import { gcloud as defaultGcloud } from "./gcloud.js";
|
|
23
|
+
import { GCP_STACK_ENV, gcpSecretId, mcpIapServiceAccountEmail } from "./gcp-names.js";
|
|
24
|
+
import { mintInternalToken } from "./internal-token.js";
|
|
25
|
+
/** Signed JWTs live at most 1 hour; one call needs a few seconds. */
|
|
26
|
+
const IAP_JWT_LIFETIME_S = 600;
|
|
27
|
+
/**
|
|
28
|
+
* POST `call.body` to the endpoint and return the admin's JSON answer and
|
|
29
|
+
* the admin URL it was reached at (to build links the operator opens).
|
|
30
|
+
* Every failure names the fix: an IAP refusal or a pre-endpoint admin points
|
|
31
|
+
* at `cms-provision upgrade`, a refusal carries the admin's own message.
|
|
32
|
+
*/
|
|
33
|
+
export async function callAdminInternal(target, call, deps = {}) {
|
|
34
|
+
const run = deps.run ?? defaultGcloud;
|
|
35
|
+
const doFetch = deps.fetch ?? fetch;
|
|
36
|
+
const scope = [`--region=${target.region}`, `--project=${target.projectId}`];
|
|
37
|
+
const found = await findService(run, scope, "admin");
|
|
38
|
+
if (!found.ok)
|
|
39
|
+
return { ok: false, error: found.error };
|
|
40
|
+
const url = await run([
|
|
41
|
+
"run",
|
|
42
|
+
"services",
|
|
43
|
+
"describe",
|
|
44
|
+
found.service,
|
|
45
|
+
...scope,
|
|
46
|
+
"--format=value(status.url)",
|
|
47
|
+
]);
|
|
48
|
+
const adminUrl = url.stdout.trim();
|
|
49
|
+
if (!url.ok || !adminUrl) {
|
|
50
|
+
return { ok: false, error: `read the admin's URL: ${url.stderr.trim() || "empty"}` };
|
|
51
|
+
}
|
|
52
|
+
const secretId = gcpSecretId(GCP_STACK_ENV, "internal-secret");
|
|
53
|
+
const secret = await run([
|
|
54
|
+
"secrets",
|
|
55
|
+
"versions",
|
|
56
|
+
"access",
|
|
57
|
+
"latest",
|
|
58
|
+
`--secret=${secretId}`,
|
|
59
|
+
`--project=${target.projectId}`,
|
|
60
|
+
]);
|
|
61
|
+
const internalSecret = secret.stdout.trim();
|
|
62
|
+
if (!secret.ok || internalSecret.length < 32) {
|
|
63
|
+
return {
|
|
64
|
+
ok: false,
|
|
65
|
+
error: `read the install's internal secret (${secretId}) from Secret Manager: ${secret.stderr.trim() || "missing or too short"}. Your gcloud account needs secretmanager.versions.access on the project.`,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
// Installs from before #678 may not have the IAM Credentials API on;
|
|
69
|
+
// enabling an enabled API is a no-op (codex review on #681).
|
|
70
|
+
const api = await run([
|
|
71
|
+
"services",
|
|
72
|
+
"enable",
|
|
73
|
+
"iamcredentials.googleapis.com",
|
|
74
|
+
`--project=${target.projectId}`,
|
|
75
|
+
]);
|
|
76
|
+
if (!api.ok) {
|
|
77
|
+
return {
|
|
78
|
+
ok: false,
|
|
79
|
+
error: `enable the IAM Credentials API (iamcredentials.googleapis.com), needed to pass the admin's IAP: ${api.stderr.trim()}`,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
const iap = await signIapJwt(run, doFetch, target.projectId, adminUrl, deps.now);
|
|
83
|
+
if (!iap.ok)
|
|
84
|
+
return iap;
|
|
85
|
+
const token = await mintInternalToken(internalSecret, call.scope);
|
|
86
|
+
let res;
|
|
87
|
+
try {
|
|
88
|
+
res = await doFetch(`${adminUrl}${call.endpoint}`, {
|
|
89
|
+
method: "POST",
|
|
90
|
+
headers: {
|
|
91
|
+
authorization: `Bearer ${iap.value}`,
|
|
92
|
+
"x-caelo-internal-token": token,
|
|
93
|
+
"content-type": "application/json",
|
|
94
|
+
},
|
|
95
|
+
body: JSON.stringify(call.body),
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
catch (e) {
|
|
99
|
+
return { ok: false, error: `reach the admin at ${adminUrl}: ${String(e)}` };
|
|
100
|
+
}
|
|
101
|
+
const text = await res.text();
|
|
102
|
+
if (res.headers.get("x-goog-iap-generated-response") === "true") {
|
|
103
|
+
return {
|
|
104
|
+
ok: false,
|
|
105
|
+
error: `Google IAP refused the request (HTTP ${res.status}: ${text.trim().slice(0, 200)}) — ${mcpIapServiceAccountEmail(target.projectId)} is not allowed on the admin's IAP yet. Run \`cms-provision upgrade\` (it allowlists that service account), then run this command again.`,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
if (res.status === 404) {
|
|
109
|
+
return {
|
|
110
|
+
ok: false,
|
|
111
|
+
error: `the admin has no ${call.endpoint} yet (it runs a release from before ${call.introducedBy}). Run \`cms-provision upgrade\`, then run this command again.`,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
let parsed;
|
|
115
|
+
try {
|
|
116
|
+
parsed = JSON.parse(text);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
return {
|
|
120
|
+
ok: false,
|
|
121
|
+
error: `the admin answered HTTP ${res.status} with a non-JSON body: ${text.slice(0, 200)}`,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
if (!res.ok || parsed.ok !== true) {
|
|
125
|
+
return {
|
|
126
|
+
ok: false,
|
|
127
|
+
error: `the admin refused (HTTP ${res.status}): ${parsed.error ?? text.slice(0, 300)}`,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
return { ok: true, value: { answer: parsed, adminUrl } };
|
|
131
|
+
}
|
|
132
|
+
/** A JWT signed as the install's MCP service account, for the admin's IAP. */
|
|
133
|
+
async function signIapJwt(run, doFetch, projectId, adminUrl, now) {
|
|
134
|
+
const access = await run(["auth", "print-access-token"]);
|
|
135
|
+
const accessToken = access.stdout.trim();
|
|
136
|
+
if (!access.ok || !accessToken) {
|
|
137
|
+
return {
|
|
138
|
+
ok: false,
|
|
139
|
+
error: `get an access token from gcloud: ${access.stderr.trim() || "empty"}. Run \`gcloud auth login\` with the account you provisioned with.`,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
const serviceAccount = mcpIapServiceAccountEmail(projectId);
|
|
143
|
+
const iat = now ? now() : Math.floor(Date.now() / 1000);
|
|
144
|
+
const res = await doFetch(`https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${encodeURIComponent(serviceAccount)}:signJwt`, {
|
|
145
|
+
method: "POST",
|
|
146
|
+
headers: { authorization: `Bearer ${accessToken}`, "content-type": "application/json" },
|
|
147
|
+
body: JSON.stringify({
|
|
148
|
+
payload: JSON.stringify({
|
|
149
|
+
iss: serviceAccount,
|
|
150
|
+
sub: serviceAccount,
|
|
151
|
+
aud: `${new URL(adminUrl).origin}/*`,
|
|
152
|
+
iat,
|
|
153
|
+
exp: iat + IAP_JWT_LIFETIME_S,
|
|
154
|
+
}),
|
|
155
|
+
}),
|
|
156
|
+
});
|
|
157
|
+
if (!res.ok) {
|
|
158
|
+
const detail = (await res.text().catch(() => "")).slice(0, 300);
|
|
159
|
+
return {
|
|
160
|
+
ok: false,
|
|
161
|
+
error: `sign an IAP token as ${serviceAccount} (HTTP ${res.status}): ${detail} — your account needs roles/iam.serviceAccountTokenCreator on it; \`cms-provision upgrade\` grants it to everyone the admin's IAP lets in.`,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
const { signedJwt } = (await res.json());
|
|
165
|
+
if (!signedJwt)
|
|
166
|
+
return { ok: false, error: "IAM signJwt answered without a signedJwt" };
|
|
167
|
+
return { ok: true, value: signedJwt };
|
|
168
|
+
}
|
|
169
|
+
//# sourceMappingURL=admin-internal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin-internal.js","sourceRoot":"","sources":["../src/admin-internal.ts"],"names":[],"mappings":"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"}
|
package/dist/agent/units.js
CHANGED
|
@@ -77,7 +77,7 @@ export function agentServiceUnit(spec) {
|
|
|
77
77
|
"# Generated by cms-provision (agent install) — do not edit; re-run it instead.",
|
|
78
78
|
"[Unit]",
|
|
79
79
|
`Description=Caelo update agent for ${spec.domain}`,
|
|
80
|
-
"Documentation=https://caelo-cms.com/install
|
|
80
|
+
"Documentation=https://caelo-cms.com/docs/install/maintenance/",
|
|
81
81
|
"After=network-online.target",
|
|
82
82
|
"",
|
|
83
83
|
"[Service]",
|
package/dist/agent/units.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"units.js","sourceRoot":"","sources":["../../src/agent/units.ts"],"names":[],"mappings":"AAAA,mCAAmC;AAEnC;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,kCAAkC,EAAE,MAAM,mBAAmB,CAAC;AA2BvE,oFAAoF;AACpF,MAAM,CAAC,MAAM,yBAAyB,GAAG,EAAE,CAAC;AAE5C;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,MAAc,EAAE,QAAgB;IAC5D,MAAM,IAAI,GAAG,MAAM;SAChB,WAAW,EAAE;SACb,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;SAC3B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC;SACvB,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAChB,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,2CAA2C,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAChG,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC7E,OAAO,eAAe,IAAI,IAAI,IAAI,EAAE,CAAC;AACvC,CAAC;AAED,4CAA4C;AAC5C,MAAM,UAAU,eAAe,CAAC,OAAe;IAC7C,IAAI,CAAC,4CAA4C,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QAChE,MAAM,IAAI,KAAK,CAAC,0BAA0B,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACvE,CAAC;IACD,OAAO,2BAA2B,OAAO,EAAE,CAAC;AAC9C,CAAC;AAED;;;;GAIG;AACH,SAAS,IAAI,CAAC,KAAa;IACzB,IAAI,CAAC,KAAK,IAAI,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACzC,MAAM,IAAI,KAAK,CAAC,cAAc,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC;IAC7E,CAAC;IACD,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC;AAC1C,CAAC;AAED,iCAAiC;AACjC,SAAS,WAAW,CAAC,GAAW,EAAE,KAAa;IAC7C,OAAO,eAAe,IAAI,CAAC,GAAG,GAAG,IAAI,KAAK,EAAE,CAAC,EAAE,CAAC;AAClD,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,YAAY,CAC1B,IAA4D;IAE5D,IAAI,IAAI,CAAC,OAAO;QAAE,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;IACtE,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;AACrF,CAAC;AAED,wBAAwB;AACxB,MAAM,UAAU,gBAAgB,CAAC,IAAmB;IAClD,OAAO;QACL,oCAAoC;QACpC,gFAAgF;QAChF,QAAQ;QACR,sCAAsC,IAAI,CAAC,MAAM,EAAE;QACnD
|
|
1
|
+
{"version":3,"file":"units.js","sourceRoot":"","sources":["../../src/agent/units.ts"],"names":[],"mappings":"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"}
|
package/dist/agent-db.js
CHANGED
|
@@ -6791,6 +6791,7 @@ var setMediaAltToolInput = object({
|
|
|
6791
6791
|
alt: string2().max(2048)
|
|
6792
6792
|
}).strict();
|
|
6793
6793
|
var setHomePageToolInput = object({ pageId: string2().uuid() }).strict();
|
|
6794
|
+
var setNotFoundPageToolInput = object({ pageId: string2().uuid().nullable() }).strict();
|
|
6794
6795
|
var setMediaSourceToolInput = object({
|
|
6795
6796
|
assetId: string2().uuid(),
|
|
6796
6797
|
sourceKind: _enum(["upload", "ai_generated", "imported", "external"]).optional(),
|
|
@@ -7550,6 +7551,60 @@ var THEME_ASSET_KEY_TO_SLOT = {
|
|
|
7550
7551
|
};
|
|
7551
7552
|
var THEME_ASSET_KEYS = Object.keys(THEME_ASSET_KEY_TO_SLOT);
|
|
7552
7553
|
|
|
7554
|
+
// ../shared/src/font-assets.ts
|
|
7555
|
+
var fontRef = object({ id: string2().uuid(), sha256: string2().regex(/^[a-f0-9]{64}$/) }).strict();
|
|
7556
|
+
var fontLicense = object({
|
|
7557
|
+
name: string2().min(1).max(200),
|
|
7558
|
+
text: string2().min(1).max(1e5),
|
|
7559
|
+
webEmbedding: boolean2(),
|
|
7560
|
+
documentEmbedding: boolean2()
|
|
7561
|
+
}).strict();
|
|
7562
|
+
var fontMetadata = fontRef.extend({
|
|
7563
|
+
family: string2(),
|
|
7564
|
+
subfamily: string2(),
|
|
7565
|
+
postscriptName: string2(),
|
|
7566
|
+
cssFamily: string2(),
|
|
7567
|
+
format: _enum(["ttf", "otf", "woff", "woff2"]),
|
|
7568
|
+
weight: number2(),
|
|
7569
|
+
style: _enum(["normal", "italic"]),
|
|
7570
|
+
axes: record(string2(), object({ min: number2(), max: number2(), default: number2() })),
|
|
7571
|
+
sizeBytes: number2(),
|
|
7572
|
+
glyphCount: number2(),
|
|
7573
|
+
embedding: object({
|
|
7574
|
+
web: boolean2(),
|
|
7575
|
+
document: boolean2(),
|
|
7576
|
+
subset: boolean2(),
|
|
7577
|
+
restriction: string2().nullable()
|
|
7578
|
+
}),
|
|
7579
|
+
license: fontLicense,
|
|
7580
|
+
source: string2(),
|
|
7581
|
+
createdAt: string2()
|
|
7582
|
+
});
|
|
7583
|
+
var fontImportInput = object({
|
|
7584
|
+
dataBase64: string2().min(1).max(11184812),
|
|
7585
|
+
license: fontLicense,
|
|
7586
|
+
source: string2().min(1).max(1000)
|
|
7587
|
+
}).strict();
|
|
7588
|
+
var fontResolveInput = fontRef.extend({
|
|
7589
|
+
use: _enum(["web", "document"]),
|
|
7590
|
+
text: string2().max(200000).default(""),
|
|
7591
|
+
formats: array(_enum(["ttf", "otf", "woff", "woff2"])).min(1).max(4)
|
|
7592
|
+
}).strict();
|
|
7593
|
+
var fontReadInput = fontRef.extend({ offset: number2().int().min(0), length: number2().int().min(1).max(262144) }).strict();
|
|
7594
|
+
var fontFindInput = object({
|
|
7595
|
+
query: string2().max(200).default(""),
|
|
7596
|
+
limit: number2().int().min(1).max(100).default(50)
|
|
7597
|
+
}).strict();
|
|
7598
|
+
|
|
7599
|
+
// ../shared/src/font-axis-policy.ts
|
|
7600
|
+
var fontAxisTag = string2().regex(/^[A-Za-z0-9]{4}$/, "axis tag must be 4 characters");
|
|
7601
|
+
var fontAxisSetting = union([
|
|
7602
|
+
literal("full"),
|
|
7603
|
+
object({ min: number2().finite(), max: number2().finite() }).strict(),
|
|
7604
|
+
number2().finite()
|
|
7605
|
+
]);
|
|
7606
|
+
var fontAxisPolicy = record(fontAxisTag, fontAxisSetting).refine((p) => Object.keys(p).length > 0 && Object.keys(p).length <= 16, "name between 1 and 16 axes; pass null to reset the face to the default policy");
|
|
7607
|
+
|
|
7553
7608
|
// ../shared/src/safe-keys.ts
|
|
7554
7609
|
var UNSAFE_KEYS = new Set(["__proto__", "constructor", "prototype"]);
|
|
7555
7610
|
|
|
@@ -7790,50 +7845,28 @@ var createThemeOverridesSchema = record(string2(), unknown());
|
|
|
7790
7845
|
var languageTagSchema = string2().max(35).regex(/^[A-Za-z]{2,8}(-[A-Za-z0-9]{1,8})*$/, "must be a BCP 47 language tag such as `en`, `de` or `pt-BR`");
|
|
7791
7846
|
var HTML_WS = new Set(["\t", `
|
|
7792
7847
|
`, "\f", "\r", " "]);
|
|
7793
|
-
// ../shared/src/font-
|
|
7794
|
-
var
|
|
7795
|
-
var
|
|
7796
|
-
|
|
7797
|
-
|
|
7798
|
-
|
|
7799
|
-
|
|
7800
|
-
|
|
7801
|
-
|
|
7802
|
-
|
|
7803
|
-
|
|
7804
|
-
|
|
7805
|
-
|
|
7806
|
-
|
|
7807
|
-
|
|
7808
|
-
|
|
7809
|
-
|
|
7810
|
-
|
|
7811
|
-
|
|
7812
|
-
|
|
7813
|
-
|
|
7814
|
-
|
|
7815
|
-
subset: boolean2(),
|
|
7816
|
-
restriction: string2().nullable()
|
|
7817
|
-
}),
|
|
7818
|
-
license: fontLicense,
|
|
7819
|
-
source: string2(),
|
|
7820
|
-
createdAt: string2()
|
|
7821
|
-
});
|
|
7822
|
-
var fontImportInput = object({
|
|
7823
|
-
dataBase64: string2().min(1).max(11184812),
|
|
7824
|
-
license: fontLicense,
|
|
7825
|
-
source: string2().min(1).max(1000)
|
|
7826
|
-
}).strict();
|
|
7827
|
-
var fontResolveInput = fontRef.extend({
|
|
7828
|
-
use: _enum(["web", "document"]),
|
|
7829
|
-
text: string2().max(200000).default(""),
|
|
7830
|
-
formats: array(_enum(["ttf", "otf", "woff", "woff2"])).min(1).max(4)
|
|
7831
|
-
}).strict();
|
|
7832
|
-
var fontReadInput = fontRef.extend({ offset: number2().int().min(0), length: number2().int().min(1).max(262144) }).strict();
|
|
7833
|
-
var fontFindInput = object({
|
|
7834
|
-
query: string2().max(200).default(""),
|
|
7835
|
-
limit: number2().int().min(1).max(100).default(50)
|
|
7836
|
-
}).strict();
|
|
7848
|
+
// ../shared/src/font-usage.ts
|
|
7849
|
+
var GLOBAL_KEYWORDS = new Set(["inherit", "initial", "unset", "revert", "revert-layer"]);
|
|
7850
|
+
var SYSTEM_FONTS = new Set([
|
|
7851
|
+
"caption",
|
|
7852
|
+
"icon",
|
|
7853
|
+
"menu",
|
|
7854
|
+
"message-box",
|
|
7855
|
+
"small-caption",
|
|
7856
|
+
"status-bar"
|
|
7857
|
+
]);
|
|
7858
|
+
var SIZE_KEYWORDS = new Set([
|
|
7859
|
+
"xx-small",
|
|
7860
|
+
"x-small",
|
|
7861
|
+
"small",
|
|
7862
|
+
"medium",
|
|
7863
|
+
"large",
|
|
7864
|
+
"x-large",
|
|
7865
|
+
"xx-large",
|
|
7866
|
+
"xxx-large",
|
|
7867
|
+
"larger",
|
|
7868
|
+
"smaller"
|
|
7869
|
+
]);
|
|
7837
7870
|
// ../shared/src/fonts.ts
|
|
7838
7871
|
var SYSTEM_FONT_FAMILIES = new Set([
|
|
7839
7872
|
"serif",
|
|
@@ -8219,7 +8252,9 @@ var revertPageInput = object({
|
|
|
8219
8252
|
var STAGING_SESSION_TTL_SECONDS = 12 * 60 * 60;
|
|
8220
8253
|
var ENCODER = new TextEncoder;
|
|
8221
8254
|
// ../shared/src/static-cache-policy.ts
|
|
8222
|
-
var
|
|
8255
|
+
var PINNED_FONT_SLICE_FILE_PATTERN = "[0-9a-f]{64}\\.[0-9a-f]{12}\\.[a-z]+(?:-[a-z]+)?\\.[0-9a-f]{12}\\.woff2";
|
|
8256
|
+
var PINNED_FONT_SLICE_FILE_RE = new RegExp(`^${PINNED_FONT_SLICE_FILE_PATTERN}$`);
|
|
8257
|
+
var CONTENT_HASHED_PATH_PATTERN = `^/(?:_app/immutable/.+|_assets/fonts/[^/]+/[0-9a-f]{16}\\.woff2|_assets/fonts/pinned/${PINNED_FONT_SLICE_FILE_PATTERN}|_assets/[a-z0-9][a-z0-9-]*(?:/[a-z][a-z0-9-]*)?\\.[0-9a-f]{16}\\.[a-z0-9]{2,5}|_caelo/plugin/[^/]+/[^/]+\\.[0-9a-f]{12}\\.(?:js|css))$`;
|
|
8223
8258
|
var CONTENT_HASHED_PATH_RE = new RegExp(CONTENT_HASHED_PATH_PATTERN);
|
|
8224
8259
|
// ../shared/src/structured-sets.ts
|
|
8225
8260
|
var navMenuItem = lazy(() => object({
|
|
@@ -17777,6 +17812,13 @@ function isRlsDenial(error) {
|
|
|
17777
17812
|
// ../query-api/src/adapter.ts
|
|
17778
17813
|
var SQL3 = globalThis.Bun?.SQL;
|
|
17779
17814
|
var DEFAULT_ROLES = { admin: "admin_role", public: ["admin_role", "public_role"] };
|
|
17815
|
+
|
|
17816
|
+
class AdminDatabaseUnavailableError extends Error {
|
|
17817
|
+
name = "AdminDatabaseUnavailableError";
|
|
17818
|
+
constructor(what) {
|
|
17819
|
+
super(`${what}: this process has no cms_admin connection by design (public side, CLAUDE.md \xA72). Read what the admin published into cms_public instead.`);
|
|
17820
|
+
}
|
|
17821
|
+
}
|
|
17780
17822
|
function resolvePoolMax(explicit) {
|
|
17781
17823
|
if (explicit !== undefined)
|
|
17782
17824
|
return validatePoolMax(explicit, "poolMax option");
|
|
@@ -17800,19 +17842,41 @@ class DatabaseAdapter {
|
|
|
17800
17842
|
#skipVerify;
|
|
17801
17843
|
#roles;
|
|
17802
17844
|
#verifyPromise = null;
|
|
17845
|
+
#projectionPublisher = null;
|
|
17803
17846
|
constructor(config) {
|
|
17804
|
-
|
|
17805
|
-
if (
|
|
17806
|
-
|
|
17807
|
-
this.#publicRaw = new SQL3(config.publicDatabaseUrl);
|
|
17808
|
-
} else {
|
|
17809
|
-
this.#adminRaw = new SQL3(config.adminDatabaseUrl, { max: poolMax });
|
|
17810
|
-
this.#publicRaw = new SQL3(config.publicDatabaseUrl, { max: poolMax });
|
|
17847
|
+
this.#roles = config.expectedRoles ?? DEFAULT_ROLES;
|
|
17848
|
+
if (config.adminDatabaseUrl === null !== (this.#roles.admin === null)) {
|
|
17849
|
+
throw new Error("DatabaseAdapter: a public-only adapter (adminDatabaseUrl: null) must expect no admin role, and an adapter with an admin URL must expect one");
|
|
17811
17850
|
}
|
|
17812
|
-
|
|
17851
|
+
const poolMax = resolvePoolMax(config.poolMax);
|
|
17852
|
+
const open = (url) => poolMax === undefined ? new SQL3(url) : new SQL3(url, { max: poolMax });
|
|
17853
|
+
this.#adminRaw = config.adminDatabaseUrl === null ? null : open(config.adminDatabaseUrl);
|
|
17854
|
+
this.#publicRaw = open(config.publicDatabaseUrl);
|
|
17855
|
+
this.#admin = this.#adminRaw === null ? null : drizzle(this.#adminRaw);
|
|
17813
17856
|
this.#public = drizzle(this.#publicRaw);
|
|
17814
17857
|
this.#skipVerify = config.skipRoleVerification === true;
|
|
17815
|
-
|
|
17858
|
+
}
|
|
17859
|
+
get hasAdminDatabase() {
|
|
17860
|
+
return this.#admin !== null;
|
|
17861
|
+
}
|
|
17862
|
+
setProjectionPublisher(publish) {
|
|
17863
|
+
this.#projectionPublisher = publish;
|
|
17864
|
+
}
|
|
17865
|
+
async#projectionDirty(tx) {
|
|
17866
|
+
if (this.#projectionPublisher === null)
|
|
17867
|
+
return false;
|
|
17868
|
+
const found = await tx.execute(sql`SELECT current_setting('caelo.projection_dirty', true) AS v`);
|
|
17869
|
+
return found[0]?.v === "on";
|
|
17870
|
+
}
|
|
17871
|
+
async#publishAfterCommit() {
|
|
17872
|
+
const publish = this.#projectionPublisher;
|
|
17873
|
+
if (publish !== null)
|
|
17874
|
+
await publish();
|
|
17875
|
+
}
|
|
17876
|
+
#adminDb(what) {
|
|
17877
|
+
if (this.#admin === null)
|
|
17878
|
+
throw new AdminDatabaseUnavailableError(what);
|
|
17879
|
+
return this.#admin;
|
|
17816
17880
|
}
|
|
17817
17881
|
verifyRoles() {
|
|
17818
17882
|
if (this.#verifyPromise !== null)
|
|
@@ -17824,9 +17888,11 @@ class DatabaseAdapter {
|
|
|
17824
17888
|
return this.#verifyPromise;
|
|
17825
17889
|
}
|
|
17826
17890
|
async#verifyRolesOnce() {
|
|
17827
|
-
|
|
17828
|
-
|
|
17829
|
-
|
|
17891
|
+
if (this.#adminRaw !== null) {
|
|
17892
|
+
const admin = await this.#identity(this.#adminRaw);
|
|
17893
|
+
if (admin.user !== this.#roles.admin || admin.database !== "cms_admin") {
|
|
17894
|
+
throw new Error(`DatabaseAdapter admin pool expected (${this.#roles.admin}, cms_admin) but connected as (${admin.user}, ${admin.database}). Check ADMIN_DATABASE_URL.`);
|
|
17895
|
+
}
|
|
17830
17896
|
}
|
|
17831
17897
|
const pub = await this.#identity(this.#publicRaw);
|
|
17832
17898
|
if (pub.database !== "cms_public") {
|
|
@@ -17846,9 +17912,11 @@ class DatabaseAdapter {
|
|
|
17846
17912
|
async runOperation(op, ctx, validatedInput) {
|
|
17847
17913
|
if (!this.#skipVerify)
|
|
17848
17914
|
await this.verifyRoles();
|
|
17849
|
-
const db = op.database === "cms_admin" ? this.#
|
|
17915
|
+
const db = op.database === "cms_admin" ? this.#adminDb(`operation ${op.name}`) : this.#public;
|
|
17916
|
+
let publishAfterCommit = false;
|
|
17917
|
+
let result;
|
|
17850
17918
|
try {
|
|
17851
|
-
|
|
17919
|
+
result = await db.transaction(async (tx) => {
|
|
17852
17920
|
await tx.execute(sql`SELECT set_config('caelo.actor_id', ${ctx.actorId}, true)`);
|
|
17853
17921
|
await tx.execute(sql`SELECT set_config('caelo.actor_kind', ${ctx.actorKind}, true)`);
|
|
17854
17922
|
await tx.execute(sql`SELECT set_config('caelo.plugin_id', ${ctx.pluginId ?? ""}, true)`);
|
|
@@ -17859,7 +17927,10 @@ class DatabaseAdapter {
|
|
|
17859
17927
|
if (denied !== null)
|
|
17860
17928
|
return err(branchNotFound(op.name, denied));
|
|
17861
17929
|
}
|
|
17862
|
-
|
|
17930
|
+
const out = await op.handler(ctx, validatedInput, tx);
|
|
17931
|
+
if (op.database === "cms_admin")
|
|
17932
|
+
publishAfterCommit = await this.#projectionDirty(tx);
|
|
17933
|
+
return out;
|
|
17863
17934
|
});
|
|
17864
17935
|
} catch (thrown) {
|
|
17865
17936
|
if (thrown instanceof OperationAbortError) {
|
|
@@ -17880,14 +17951,28 @@ class DatabaseAdapter {
|
|
|
17880
17951
|
...pgDetail ? { pgDetail } : {}
|
|
17881
17952
|
});
|
|
17882
17953
|
}
|
|
17954
|
+
if (publishAfterCommit) {
|
|
17955
|
+
try {
|
|
17956
|
+
await this.#publishAfterCommit();
|
|
17957
|
+
} catch (thrown) {
|
|
17958
|
+
return err({
|
|
17959
|
+
kind: "HandlerError",
|
|
17960
|
+
operation: op.name,
|
|
17961
|
+
message: `The change is saved, but the public site does not serve it yet: publishing it failed (${thrown instanceof Error ? thrown.message : String(thrown)}). Do not repeat the change; it is published automatically within seconds once the public database answers.`
|
|
17962
|
+
});
|
|
17963
|
+
}
|
|
17964
|
+
}
|
|
17965
|
+
return result;
|
|
17883
17966
|
}
|
|
17884
17967
|
async close() {
|
|
17885
|
-
await Promise.all([this.#adminRaw
|
|
17968
|
+
await Promise.all([this.#adminRaw?.end(), this.#publicRaw.end()]);
|
|
17886
17969
|
}
|
|
17887
17970
|
async withAdminTransaction(ctx, fn) {
|
|
17971
|
+
const admin = this.#adminDb("withAdminTransaction");
|
|
17888
17972
|
if (!this.#skipVerify)
|
|
17889
17973
|
await this.verifyRoles();
|
|
17890
|
-
|
|
17974
|
+
let publishAfterCommit = false;
|
|
17975
|
+
const out = await admin.transaction(async (tx) => {
|
|
17891
17976
|
await tx.execute(sql`SELECT set_config('caelo.actor_id', ${ctx.actorId}, true)`);
|
|
17892
17977
|
await tx.execute(sql`SELECT set_config('caelo.actor_kind', ${ctx.actorKind}, true)`);
|
|
17893
17978
|
await tx.execute(sql`SELECT set_config('caelo.plugin_id', ${ctx.pluginId ?? ""}, true)`);
|
|
@@ -17897,10 +17982,27 @@ class DatabaseAdapter {
|
|
|
17897
17982
|
if (denied !== null) {
|
|
17898
17983
|
throw new OperationAbortError(branchNotFound("withAdminTransaction", denied));
|
|
17899
17984
|
}
|
|
17985
|
+
const result = await fn(tx);
|
|
17986
|
+
publishAfterCommit = await this.#projectionDirty(tx);
|
|
17987
|
+
return result;
|
|
17988
|
+
});
|
|
17989
|
+
if (publishAfterCommit)
|
|
17990
|
+
await this.#publishAfterCommit();
|
|
17991
|
+
return out;
|
|
17992
|
+
}
|
|
17993
|
+
async withPublicTransaction(ctx, fn) {
|
|
17994
|
+
if (!this.#skipVerify)
|
|
17995
|
+
await this.verifyRoles();
|
|
17996
|
+
return await this.#public.transaction(async (tx) => {
|
|
17997
|
+
await tx.execute(sql`SELECT set_config('caelo.actor_id', ${ctx.actorId}, true)`);
|
|
17998
|
+
await tx.execute(sql`SELECT set_config('caelo.actor_kind', ${ctx.actorKind}, true)`);
|
|
17999
|
+
await tx.execute(sql`SELECT set_config('caelo.plugin_id', ${ctx.pluginId ?? ""}, true)`);
|
|
17900
18000
|
return await fn(tx);
|
|
17901
18001
|
});
|
|
17902
18002
|
}
|
|
17903
18003
|
rawAdmin() {
|
|
18004
|
+
if (this.#adminRaw === null)
|
|
18005
|
+
throw new AdminDatabaseUnavailableError("rawAdmin");
|
|
17904
18006
|
return this.#adminRaw;
|
|
17905
18007
|
}
|
|
17906
18008
|
rawPublic() {
|
|
@@ -17916,21 +18018,23 @@ class DatabaseAdapter {
|
|
|
17916
18018
|
});
|
|
17917
18019
|
}
|
|
17918
18020
|
async provisionPluginAdminSchema(opts) {
|
|
18021
|
+
const admin = this.#adminDb("provisionPluginAdminSchema");
|
|
17919
18022
|
if (!this.#skipVerify)
|
|
17920
18023
|
await this.verifyRoles();
|
|
17921
|
-
await
|
|
18024
|
+
await admin.transaction(async (tx) => {
|
|
17922
18025
|
await tx.execute(sql.raw("SELECT set_config('caelo.actor_kind', 'system', true)"));
|
|
17923
18026
|
await tx.execute(sql.raw(`SELECT set_config('caelo.plugin_id', '${opts.pluginId}', true)`));
|
|
17924
18027
|
await tx.execute(sql.raw(opts.sql));
|
|
17925
18028
|
});
|
|
17926
18029
|
}
|
|
17927
18030
|
async dropPluginAdminSchema(opts) {
|
|
18031
|
+
const admin = this.#adminDb("dropPluginAdminSchema");
|
|
17928
18032
|
if (!this.#skipVerify)
|
|
17929
18033
|
await this.verifyRoles();
|
|
17930
18034
|
if (!/^plugin_[a-z][a-z0-9_]*$/.test(opts.schemaName)) {
|
|
17931
18035
|
throw new Error(`dropPluginAdminSchema: refusing to drop schema "${opts.schemaName}" \u2014 name doesn't match plugin_<slug> pattern`);
|
|
17932
18036
|
}
|
|
17933
|
-
await
|
|
18037
|
+
await admin.transaction(async (tx) => {
|
|
17934
18038
|
await tx.execute(sql.raw("SELECT set_config('caelo.actor_kind', 'system', true)"));
|
|
17935
18039
|
await tx.execute(sql.raw(`DROP SCHEMA IF EXISTS "${opts.schemaName}" CASCADE`));
|
|
17936
18040
|
});
|
|
@@ -17947,7 +18051,7 @@ class DatabaseAdapter {
|
|
|
17947
18051
|
});
|
|
17948
18052
|
}
|
|
17949
18053
|
get admin() {
|
|
17950
|
-
return this.#admin;
|
|
18054
|
+
return this.#adminDb("admin pool");
|
|
17951
18055
|
}
|
|
17952
18056
|
get public() {
|
|
17953
18057
|
return this.#public;
|