@wtfalch/foundry 0.17.1 → 0.18.0
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/dist/bootstrap/declare.js +3 -3
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/migrations/0045_names_to_edge.sql +107 -0
- package/dist/org.d.ts +17 -17
- package/dist/org.js +48 -58
- package/dist/plan/diff.d.ts +3 -1
- package/dist/plan/diff.js +16 -16
- package/dist/plan/entry-edge-key.d.ts +79 -0
- package/dist/plan/{entry-names-key.js → entry-edge-key.js} +90 -66
- package/dist/plan/executor.d.ts +48 -37
- package/dist/plan/executor.js +166 -98
- package/dist/plan/index.d.ts +2 -2
- package/dist/plan/index.js +1 -1
- package/dist/plan/live.d.ts +3 -3
- package/dist/plan/plan-apply.d.ts +6 -5
- package/dist/plan/plan-apply.js +40 -38
- package/dist/plan/platform-diff.d.ts +22 -22
- package/dist/plan/platform-diff.js +45 -45
- package/dist/plan/platform-org.d.ts +8 -8
- package/dist/plan/platform-org.js +7 -7
- package/dist/plan/platform-providers.d.ts +14 -14
- package/dist/plan/platform-providers.js +107 -107
- package/dist/plan/step-system.js +1 -1
- package/dist/plan/{edge-worker.d.ts → worker.d.ts} +53 -33
- package/dist/plan/{edge-worker.js → worker.js} +92 -54
- package/dist/platform.d.ts +3 -3
- package/dist/platform.js +1 -1
- package/dist/store/postgres.js +7 -7
- package/dist/store/schema.d.ts +6 -6
- package/dist/store/schema.js +4 -4
- package/package.json +1 -1
- package/dist/plan/entry-names-key.d.ts +0 -73
|
@@ -132,11 +132,11 @@ export interface FoundryPlatformStepExecutorOptions {
|
|
|
132
132
|
*/
|
|
133
133
|
ai?: PlatformAiDeps;
|
|
134
134
|
/**
|
|
135
|
-
* #478: dependencies for `coolify:app` keyed {@link
|
|
135
|
+
* #478: dependencies for `coolify:app` keyed {@link PLATFORM_EDGE_KEY} --
|
|
136
136
|
* optional for the same reason as `auth`; a host that declares
|
|
137
|
-
* `platform.
|
|
137
|
+
* `platform.edge.enabled` without it gets the step reported as blocked.
|
|
138
138
|
*/
|
|
139
|
-
|
|
139
|
+
edge?: PlatformEdgeDeps;
|
|
140
140
|
/**
|
|
141
141
|
* #175: dependencies for `coolify:app` keyed {@link PLATFORM_ARCHON_KEY}
|
|
142
142
|
* -- optional for the same reason as {@link auth}.
|
|
@@ -203,8 +203,8 @@ export interface FoundryPlatformStepExecutorOptions {
|
|
|
203
203
|
* #172 adds `coolify:app` keyed {@link PLATFORM_AI_KEY}, the platform's AI
|
|
204
204
|
* host ({@link provisionPlatformAi}), once a host passes {@link PlatformAiDeps}.
|
|
205
205
|
*
|
|
206
|
-
* #478 adds `coolify:app` keyed {@link
|
|
207
|
-
* ({@link
|
|
206
|
+
* #478 adds `coolify:app` keyed {@link PLATFORM_EDGE_KEY}, the edge host
|
|
207
|
+
* ({@link provisionPlatformEdge}), once a host passes {@link PlatformEdgeDeps}.
|
|
208
208
|
*
|
|
209
209
|
* #175 adds `coolify:app` keyed {@link PLATFORM_ARCHON_KEY}, Archon itself
|
|
210
210
|
* ({@link provisionPlatformArchon}), once a host passes
|
|
@@ -225,7 +225,7 @@ export declare class FoundryPlatformStepExecutor implements PlatformStepExecutor
|
|
|
225
225
|
private readonly integrations?;
|
|
226
226
|
private readonly files?;
|
|
227
227
|
private readonly ai?;
|
|
228
|
-
private readonly
|
|
228
|
+
private readonly edge?;
|
|
229
229
|
private readonly archon?;
|
|
230
230
|
private readonly backup?;
|
|
231
231
|
constructor(cloudflare: Pick<CloudflarePort, 'findZoneByApex' | 'createZone'>, declaredOrgStore: Pick<DeclaredOrgStore, 'findOrgIdForZone' | 'list' | 'get'>, options?: FoundryPlatformStepExecutorOptions);
|
|
@@ -236,7 +236,7 @@ export declare class FoundryPlatformStepExecutor implements PlatformStepExecutor
|
|
|
236
236
|
files?: string;
|
|
237
237
|
integrations?: string;
|
|
238
238
|
ai?: string;
|
|
239
|
-
|
|
239
|
+
edge?: string;
|
|
240
240
|
archon?: string;
|
|
241
241
|
mail?: string;
|
|
242
242
|
backupExecutorMissing?: boolean;
|
|
@@ -428,7 +428,7 @@ export interface PlatformArchonDeps {
|
|
|
428
428
|
zitadelForInstance?: (issuerUrl: string) => PlatformArchonDeps['zitadel'];
|
|
429
429
|
/**
|
|
430
430
|
* Who the Cloudflare Access app in front of Archon lets through. Host
|
|
431
|
-
* config, not `DeclaredPlatform`: the platform record
|
|
431
|
+
* config, not `DeclaredPlatform`: the platform record edge services, not
|
|
432
432
|
* people. Empty blocks the step (`blockedSteps`) rather than building an
|
|
433
433
|
* Access app nobody can pass.
|
|
434
434
|
*/
|
|
@@ -822,7 +822,7 @@ export declare const FILES_MANAGEMENT_KEY_ID = "foundry-1";
|
|
|
822
822
|
export declare const FILES_MANAGEMENT_KEY_REF = "files:platform:managementKey";
|
|
823
823
|
/** The ref the files host's introspection client secret is delivered under. */
|
|
824
824
|
export declare const FILES_INTROSPECTION_SECRET_REF = "zitadel:platform:files:introspection";
|
|
825
|
-
export interface
|
|
825
|
+
export interface PlatformEdgeRuntime {
|
|
826
826
|
fetch?: typeof fetch;
|
|
827
827
|
sleep?: (ms: number) => Promise<void>;
|
|
828
828
|
now?: () => number;
|
|
@@ -831,17 +831,17 @@ export interface PlatformNamesRuntime {
|
|
|
831
831
|
readyTimeoutMs?: number;
|
|
832
832
|
readyIntervalMs?: number;
|
|
833
833
|
}
|
|
834
|
-
export interface
|
|
834
|
+
export interface PlatformEdgeDeps {
|
|
835
835
|
coolify: Pick<CoolifyPort, 'findProjectByName' | 'createProject' | 'findOrCreateEnvironment' | 'listServers' | 'findApplicationByName' | 'getApplication' | 'requireGithubApp' | 'createGithubAppApplication' | 'createDockerfileApplication' | 'listDatabases' | 'createPostgresDatabase' | 'getDatabase' | 'bulkUpsertEnvs' | 'listEnvs' | 'updateApplication' | 'deploy' | 'deployAndWait' | 'raw'>;
|
|
836
836
|
cloudflare: Pick<CloudflarePort, 'findZoneByApex' | 'findTunnelByName' | 'createTunnel' | 'putTunnelConfig' | 'getTunnelToken' | 'findDnsRecord' | 'createDnsRecord' | 'updateDnsRecord'>;
|
|
837
837
|
deliverSecret: FoundryStepExecutorPorts['deliverSecret'];
|
|
838
838
|
/**
|
|
839
|
-
* Reads the
|
|
840
|
-
* {@link
|
|
839
|
+
* Reads the edge service's own Cloudflare API token back, held under
|
|
840
|
+
* {@link PLATFORM_EDGE_CLOUDFLARE_TOKEN_REF} -- a human step (the issue's
|
|
841
841
|
* own "Human steps"), never minted here: foundry has no Cloudflare
|
|
842
842
|
* account of its own to carve that token out of. Absent, or with nothing
|
|
843
843
|
* held under that ref yet, is reported blocked by `blockedSteps`
|
|
844
|
-
* ({@link
|
|
844
|
+
* ({@link edgeBlockedReason}), never planned to throw.
|
|
845
845
|
*/
|
|
846
846
|
heldKeys?: {
|
|
847
847
|
resolve(ref: string): Promise<string | null>;
|
|
@@ -853,7 +853,7 @@ export interface PlatformNamesDeps {
|
|
|
853
853
|
*/
|
|
854
854
|
cloudflareAccountId?: string;
|
|
855
855
|
/** Clock, sleep and fetch for the readiness wait and delivery retries; real ones by default. */
|
|
856
|
-
runtime?:
|
|
856
|
+
runtime?: PlatformEdgeRuntime;
|
|
857
857
|
}
|
|
858
858
|
/**
|
|
859
859
|
* #174: everything {@link provisionPlatformIntegrations} needs beyond
|
|
@@ -7,7 +7,7 @@ import { ensureCloudflaredConnector } from './cloudflared-connector.js';
|
|
|
7
7
|
import { waitForDeploymentFinished, waitForHealthyVersion, waitForNewDeployment, } from './deploy-health.js';
|
|
8
8
|
import { ACCESS_POLICY_NAME, getDatabaseStarted, isNotFoundError, isRunningOrStarting, } from './executor.js';
|
|
9
9
|
import { coolifyDomains, filesShareHostname } from './hostnames.js';
|
|
10
|
-
import { AUTH_ZITADEL_RUN_OPTIONS, JOBS_SCHEDULER_KEY, PLATFORM_AI_APPLIED_GITREF_SCOPE_KEY, PLATFORM_AI_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_AI_DEPLOY_PENDING_SCOPE_KEY, PLATFORM_AI_INTERNAL_URL_SCOPE_KEY, PLATFORM_AI_KEY, PLATFORM_AI_MANAGEMENT_KEY_CONFLICT_SCOPE_KEY, PLATFORM_AI_MANAGEMENT_KEY_SCOPE_KEY, PLATFORM_AI_MANAGEMENT_KEY_UNDELIVERED_SCOPE_KEY, PLATFORM_AI_MIGRATE_BLOCKED_SCOPE_KEY, PLATFORM_AI_ORGANISATION_SCOPE_KEY, PLATFORM_AI_SIGNING_KEY_SET_SCOPE_KEY, PLATFORM_AI_SIGNING_KEY_UNDELIVERED_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_BROKER_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_OPERATOR_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_ROLLING_UPDATE_SCOPE_KEY, PLATFORM_ARCHON_CONNECTOR_APP_SCOPE_KEY, PLATFORM_ARCHON_COOLIFY_READ_TOKEN_REF, PLATFORM_ARCHON_ENVS_DEPLOYED_SCOPE_KEY, PLATFORM_ARCHON_GATE_CLOSED_SCOPE_KEY, PLATFORM_ARCHON_INTEGRATIONS_KEY_SYNCED_SCOPE_KEY, PLATFORM_ARCHON_KEY, PLATFORM_ARCHON_MASTER_CLOUDFLARE_TOKEN_REF, PLATFORM_ARCHON_MASTER_COOLIFY_TOKEN_REF, PLATFORM_ARCHON_MASTER_COOLIFY_URL_REF, PLATFORM_AUTH_ADMIN_UNDELIVERED_SCOPE_KEY, PLATFORM_AUTH_ADMIN_USER_SCOPE_KEY, PLATFORM_AUTH_BROKER_DEPLOYED_SCOPE_KEY, PLATFORM_AUTH_BROKER_SCOPE_KEY, PLATFORM_AUTH_CONNECTOR_APP_SCOPE_KEY, PLATFORM_AUTH_INSTANCE_SCOPE_KEY, PLATFORM_AUTH_INSTANCE_UNDELIVERED_SCOPE_KEY, PLATFORM_AUTH_KEY, PLATFORM_AUTH_LOGIN_CLIENT_UNDELIVERED_SCOPE_KEY, PLATFORM_AUTH_LOGIN_CLIENT_USER_SCOPE_KEY, PLATFORM_AUTH_OWNER_INVITE_SCOPE_KEY, PLATFORM_AUTH_SMTP_SCOPE_KEY, PLATFORM_AUTH_ZITADEL_APPLIED_IMAGE_TAG_SCOPE_KEY, PLATFORM_AUTH_ZITADEL_RUN_OPTIONS_SCOPE_KEY, PLATFORM_BACKUP_DATABASE_SCOPE_KEYS, PLATFORM_BACKUP_SERVICE_KEYS, PLATFORM_FILES_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_FILES_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, PLATFORM_FILES_INTROSPECTION_SCOPE_KEY, PLATFORM_FILES_INTROSPECTION_UNDELIVERED_SCOPE_KEY, PLATFORM_FILES_KEY, PLATFORM_FILES_MANAGEMENT_KEY_SCOPE_KEY, PLATFORM_FILES_MANAGEMENT_UNDELIVERED_SCOPE_KEY, PLATFORM_FILES_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_INTEGRATIONS_APPLIED_GITREF_SCOPE_KEY, PLATFORM_INTEGRATIONS_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY, PLATFORM_INTEGRATIONS_KEY_ID_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY_ROTATION_PREVIOUS_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY_ROTATION_REQUESTED_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY_ROTATION_UNDELIVERED_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY_SET_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY_UNDELIVERED_SCOPE_KEY, PLATFORM_INTEGRATIONS_RUNNER_KEYRING_DEPLOYED_SCOPE_KEY, PLATFORM_MAIL_ADMIN_KEY_SCOPE_KEY, PLATFORM_MAIL_ADMIN_UNDELIVERED_SCOPE_KEY, PLATFORM_MAIL_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_MAIL_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_MAIL_CONNECTOR_APP_SCOPE_KEY, PLATFORM_MAIL_INTERNAL_URL_SCOPE_KEY, PLATFORM_MAIL_KEY, PLATFORM_MAIL_ZITADEL_PROJECT_SCOPE_KEY,
|
|
10
|
+
import { AUTH_ZITADEL_RUN_OPTIONS, JOBS_SCHEDULER_KEY, PLATFORM_AI_APPLIED_GITREF_SCOPE_KEY, PLATFORM_AI_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_AI_DEPLOY_PENDING_SCOPE_KEY, PLATFORM_AI_INTERNAL_URL_SCOPE_KEY, PLATFORM_AI_KEY, PLATFORM_AI_MANAGEMENT_KEY_CONFLICT_SCOPE_KEY, PLATFORM_AI_MANAGEMENT_KEY_SCOPE_KEY, PLATFORM_AI_MANAGEMENT_KEY_UNDELIVERED_SCOPE_KEY, PLATFORM_AI_MIGRATE_BLOCKED_SCOPE_KEY, PLATFORM_AI_ORGANISATION_SCOPE_KEY, PLATFORM_AI_SIGNING_KEY_SET_SCOPE_KEY, PLATFORM_AI_SIGNING_KEY_UNDELIVERED_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_BROKER_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_OPERATOR_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_ROLLING_UPDATE_SCOPE_KEY, PLATFORM_ARCHON_CONNECTOR_APP_SCOPE_KEY, PLATFORM_ARCHON_COOLIFY_READ_TOKEN_REF, PLATFORM_ARCHON_ENVS_DEPLOYED_SCOPE_KEY, PLATFORM_ARCHON_GATE_CLOSED_SCOPE_KEY, PLATFORM_ARCHON_INTEGRATIONS_KEY_SYNCED_SCOPE_KEY, PLATFORM_ARCHON_KEY, PLATFORM_ARCHON_MASTER_CLOUDFLARE_TOKEN_REF, PLATFORM_ARCHON_MASTER_COOLIFY_TOKEN_REF, PLATFORM_ARCHON_MASTER_COOLIFY_URL_REF, PLATFORM_AUTH_ADMIN_UNDELIVERED_SCOPE_KEY, PLATFORM_AUTH_ADMIN_USER_SCOPE_KEY, PLATFORM_AUTH_BROKER_DEPLOYED_SCOPE_KEY, PLATFORM_AUTH_BROKER_SCOPE_KEY, PLATFORM_AUTH_CONNECTOR_APP_SCOPE_KEY, PLATFORM_AUTH_INSTANCE_SCOPE_KEY, PLATFORM_AUTH_INSTANCE_UNDELIVERED_SCOPE_KEY, PLATFORM_AUTH_KEY, PLATFORM_AUTH_LOGIN_CLIENT_UNDELIVERED_SCOPE_KEY, PLATFORM_AUTH_LOGIN_CLIENT_USER_SCOPE_KEY, PLATFORM_AUTH_OWNER_INVITE_SCOPE_KEY, PLATFORM_AUTH_SMTP_SCOPE_KEY, PLATFORM_AUTH_ZITADEL_APPLIED_IMAGE_TAG_SCOPE_KEY, PLATFORM_AUTH_ZITADEL_RUN_OPTIONS_SCOPE_KEY, PLATFORM_BACKUP_DATABASE_SCOPE_KEYS, PLATFORM_BACKUP_SERVICE_KEYS, PLATFORM_EDGE_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_EDGE_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_EDGE_CLOUDFLARE_TOKEN_REF, PLATFORM_EDGE_KEY, PLATFORM_EDGE_KEY_REF, PLATFORM_EDGE_PLATFORM_KEY_SET_SCOPE_KEY, PLATFORM_EDGE_PLATFORM_KEY_UNDELIVERED_SCOPE_KEY, PLATFORM_FILES_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_FILES_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, PLATFORM_FILES_INTROSPECTION_SCOPE_KEY, PLATFORM_FILES_INTROSPECTION_UNDELIVERED_SCOPE_KEY, PLATFORM_FILES_KEY, PLATFORM_FILES_MANAGEMENT_KEY_SCOPE_KEY, PLATFORM_FILES_MANAGEMENT_UNDELIVERED_SCOPE_KEY, PLATFORM_FILES_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_INTEGRATIONS_APPLIED_GITREF_SCOPE_KEY, PLATFORM_INTEGRATIONS_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY, PLATFORM_INTEGRATIONS_KEY_ID_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY_ROTATION_PREVIOUS_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY_ROTATION_REQUESTED_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY_ROTATION_UNDELIVERED_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY_SET_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY_UNDELIVERED_SCOPE_KEY, PLATFORM_INTEGRATIONS_RUNNER_KEYRING_DEPLOYED_SCOPE_KEY, PLATFORM_MAIL_ADMIN_KEY_SCOPE_KEY, PLATFORM_MAIL_ADMIN_UNDELIVERED_SCOPE_KEY, PLATFORM_MAIL_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_MAIL_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_MAIL_CONNECTOR_APP_SCOPE_KEY, PLATFORM_MAIL_INTERNAL_URL_SCOPE_KEY, PLATFORM_MAIL_KEY, PLATFORM_MAIL_ZITADEL_PROJECT_SCOPE_KEY, archonBootstrapOperator, parseBrokerDeployRecord, parseLoginClientRecord, platformMailMxHost, } from './platform-diff.js';
|
|
11
11
|
import { applyCallsSfuPlatformStep } from './platform-sfu.js';
|
|
12
12
|
import { CreateInstanceError, createZitadelInstance, generateSystemApiKeypair, instanceWithoutBearerMessage, systemApiUsersEnv, } from './zitadel-system-api.js';
|
|
13
13
|
/** The key `runPlatformSteps` records the apex zone's id under (`system:kind:key`). */
|
|
@@ -94,8 +94,8 @@ const JOBS_SCHEDULER_REPO = 'wtfalch/jobs';
|
|
|
94
94
|
* #172 adds `coolify:app` keyed {@link PLATFORM_AI_KEY}, the platform's AI
|
|
95
95
|
* host ({@link provisionPlatformAi}), once a host passes {@link PlatformAiDeps}.
|
|
96
96
|
*
|
|
97
|
-
* #478 adds `coolify:app` keyed {@link
|
|
98
|
-
* ({@link
|
|
97
|
+
* #478 adds `coolify:app` keyed {@link PLATFORM_EDGE_KEY}, the edge host
|
|
98
|
+
* ({@link provisionPlatformEdge}), once a host passes {@link PlatformEdgeDeps}.
|
|
99
99
|
*
|
|
100
100
|
* #175 adds `coolify:app` keyed {@link PLATFORM_ARCHON_KEY}, Archon itself
|
|
101
101
|
* ({@link provisionPlatformArchon}), once a host passes
|
|
@@ -116,7 +116,7 @@ export class FoundryPlatformStepExecutor {
|
|
|
116
116
|
integrations;
|
|
117
117
|
files;
|
|
118
118
|
ai;
|
|
119
|
-
|
|
119
|
+
edge;
|
|
120
120
|
archon;
|
|
121
121
|
backup;
|
|
122
122
|
constructor(cloudflare, declaredOrgStore, options = {}) {
|
|
@@ -130,7 +130,7 @@ export class FoundryPlatformStepExecutor {
|
|
|
130
130
|
this.integrations = options.integrations;
|
|
131
131
|
this.files = options.files;
|
|
132
132
|
this.ai = options.ai;
|
|
133
|
-
this.
|
|
133
|
+
this.edge = options.edge;
|
|
134
134
|
this.archon = options.archon;
|
|
135
135
|
this.backup = options.backup;
|
|
136
136
|
}
|
|
@@ -217,14 +217,14 @@ export class FoundryPlatformStepExecutor {
|
|
|
217
217
|
blocked.ai =
|
|
218
218
|
"the platform's AI host is blocked, not planned: its signing key is already pinned but the host built the platform executor without a held-key reader to read back its private half (PlatformAiDeps.heldKeys)";
|
|
219
219
|
}
|
|
220
|
-
if (platform.
|
|
221
|
-
blocked.
|
|
222
|
-
'the
|
|
220
|
+
if (platform.edge.enabled && !this.edge) {
|
|
221
|
+
blocked.edge =
|
|
222
|
+
'the edge host is blocked, not planned: the host built the platform executor without its edge dependencies';
|
|
223
223
|
}
|
|
224
|
-
else if (platform.
|
|
225
|
-
const
|
|
226
|
-
if (
|
|
227
|
-
blocked.
|
|
224
|
+
else if (platform.edge.enabled && this.edge) {
|
|
225
|
+
const edgeBlocked = await edgeBlockedReason(this.edge);
|
|
226
|
+
if (edgeBlocked)
|
|
227
|
+
blocked.edge = edgeBlocked;
|
|
228
228
|
}
|
|
229
229
|
if (platform.archon.enabled) {
|
|
230
230
|
if (!this.archon) {
|
|
@@ -375,11 +375,11 @@ export class FoundryPlatformStepExecutor {
|
|
|
375
375
|
}
|
|
376
376
|
return provisionPlatformAi(context, this.ai);
|
|
377
377
|
}
|
|
378
|
-
if (action.system === 'coolify' && action.kind === 'app' && action.key ===
|
|
379
|
-
if (!this.
|
|
380
|
-
throw new Error('platform:
|
|
378
|
+
if (action.system === 'coolify' && action.kind === 'app' && action.key === PLATFORM_EDGE_KEY) {
|
|
379
|
+
if (!this.edge) {
|
|
380
|
+
throw new Error('platform:edge needs FoundryPlatformStepExecutor to be constructed with its edge dependencies (#478)');
|
|
381
381
|
}
|
|
382
|
-
return
|
|
382
|
+
return provisionPlatformEdge(context, this.edge);
|
|
383
383
|
}
|
|
384
384
|
if (action.system === 'coolify' &&
|
|
385
385
|
action.kind === 'app' &&
|
|
@@ -5105,74 +5105,74 @@ shareDomains) {
|
|
|
5105
5105
|
await record(PLATFORM_FILES_APPLIED_GIT_REF_SCOPE_KEY, gitRef);
|
|
5106
5106
|
return { resourceId: runnerApp.appId };
|
|
5107
5107
|
}
|
|
5108
|
-
/** https://github.com/wtfalch/
|
|
5109
|
-
const
|
|
5110
|
-
const
|
|
5111
|
-
const
|
|
5112
|
-
const
|
|
5108
|
+
/** https://github.com/wtfalch/edge -- root Dockerfile, one `runner` target (see {@link provisionPlatformEdge}'s own doc comment for why there is no `migration` target any more). */
|
|
5109
|
+
const EDGE_REPO = 'wtfalch/edge';
|
|
5110
|
+
const PLATFORM_EDGE_PROJECT = 'platform';
|
|
5111
|
+
const PLATFORM_EDGE_DB_NAME = 'platform-edge-db';
|
|
5112
|
+
const EDGE_APP_NAME = 'edge';
|
|
5113
5113
|
/** The app's fixed `custom_internal_name`, the way `platform:files`' runner names itself. */
|
|
5114
|
-
const
|
|
5114
|
+
const EDGE_RUNNER_INTERNAL_NAME = 'platform-edge';
|
|
5115
5115
|
/** The runner's port (its Dockerfile: `PORT=3000`, `EXPOSE 3000`). */
|
|
5116
|
-
const
|
|
5116
|
+
const EDGE_PORT = '3000';
|
|
5117
5117
|
/** The Postgres database name, and the owner role's name per the host's own `.env.example`. */
|
|
5118
|
-
const
|
|
5119
|
-
const
|
|
5118
|
+
const EDGE_DB = 'names';
|
|
5119
|
+
const EDGE_OWNER_ROLE = 'names_owner';
|
|
5120
5120
|
/** The restricted runtime role `migrate.mjs`'s own `ensureRuntimeRole` creates or syncs at every boot. */
|
|
5121
|
-
const
|
|
5122
|
-
const
|
|
5123
|
-
/** #197: the one Coolify app that runs `cloudflared` for {@link
|
|
5124
|
-
const
|
|
5125
|
-
const
|
|
5126
|
-
const
|
|
5127
|
-
const
|
|
5128
|
-
const
|
|
5129
|
-
const
|
|
5121
|
+
const EDGE_RUNTIME_ROLE = 'names_rt';
|
|
5122
|
+
const EDGE_TUNNEL_NAME = 'platform-edge-tunnel';
|
|
5123
|
+
/** #197: the one Coolify app that runs `cloudflared` for {@link EDGE_TUNNEL_NAME}. */
|
|
5124
|
+
const EDGE_CLOUDFLARED_APP_NAME = 'platform-edge-cloudflared';
|
|
5125
|
+
const PLATFORM_EDGE_PROJECT_SCOPE_KEY = 'coolify_project_platform_edge';
|
|
5126
|
+
const PLATFORM_EDGE_DB_SCOPE_KEY = 'coolify_platform_edge_database';
|
|
5127
|
+
const PLATFORM_EDGE_APP_SCOPE_KEY = 'coolify_platform_edge_app';
|
|
5128
|
+
const PLATFORM_EDGE_TUNNEL_SCOPE_KEY = 'cloudflare_platform_edge_tunnel';
|
|
5129
|
+
const PLATFORM_EDGE_CONNECTOR_APP_SCOPE_KEY = 'coolify_platform_edge_connector_app';
|
|
5130
5130
|
/**
|
|
5131
5131
|
* Written only when absent, never overwritten -- the `PLATFORM_FILES_RUNNER_DEFAULTS`
|
|
5132
5132
|
* convention, so an operator's own cap or rate survives every apply. Values
|
|
5133
5133
|
* are the host's own `.env.example` defaults.
|
|
5134
5134
|
*/
|
|
5135
|
-
const
|
|
5135
|
+
const PLATFORM_EDGE_RUNNER_DEFAULTS = [
|
|
5136
5136
|
{ key: 'MAX_CLAIMS_PER_ORGANISATION', value: '100' },
|
|
5137
|
-
{ key: '
|
|
5138
|
-
{ key: '
|
|
5137
|
+
{ key: 'EDGE_RATE_BURST', value: '10' },
|
|
5138
|
+
{ key: 'EDGE_RATE_PER_SECOND', value: '0.5' },
|
|
5139
5139
|
];
|
|
5140
5140
|
/**
|
|
5141
|
-
* Why `coolify:app` keyed {@link
|
|
5142
|
-
* or `null` when it can -- the host built no
|
|
5141
|
+
* Why `coolify:app` keyed {@link PLATFORM_EDGE_KEY} cannot be planned yet,
|
|
5142
|
+
* or `null` when it can -- the host built no edge dependencies (checked by
|
|
5143
5143
|
* the caller, `FoundryPlatformStepExecutor.blockedSteps`, before this is
|
|
5144
5144
|
* even called), no Cloudflare API token held under
|
|
5145
|
-
* {@link
|
|
5145
|
+
* {@link PLATFORM_EDGE_CLOUDFLARE_TOKEN_REF} (CLAUDE.md's "Human steps"),
|
|
5146
5146
|
* or no Cloudflare account id supplied beside it.
|
|
5147
5147
|
*/
|
|
5148
|
-
async function
|
|
5148
|
+
async function edgeBlockedReason(names) {
|
|
5149
5149
|
let token;
|
|
5150
5150
|
try {
|
|
5151
|
-
token = (await names.heldKeys?.resolve(
|
|
5151
|
+
token = (await names.heldKeys?.resolve(PLATFORM_EDGE_CLOUDFLARE_TOKEN_REF)) ?? null;
|
|
5152
5152
|
}
|
|
5153
5153
|
catch {
|
|
5154
5154
|
token = null;
|
|
5155
5155
|
}
|
|
5156
5156
|
if (!token) {
|
|
5157
|
-
return `the
|
|
5157
|
+
return `the edge host is blocked, not planned: no Cloudflare API token is held under ${PLATFORM_EDGE_CLOUDFLARE_TOKEN_REF} yet (the edge service's own token, tunnel edit + DNS edit, never the platform's) -- a human step`;
|
|
5158
5158
|
}
|
|
5159
5159
|
if (!names.cloudflareAccountId) {
|
|
5160
|
-
return 'the
|
|
5160
|
+
return 'the edge host is blocked, not planned: the host supplies no Cloudflare account id (PlatformEdgeDeps.cloudflareAccountId) for the token above';
|
|
5161
5161
|
}
|
|
5162
5162
|
return null;
|
|
5163
5163
|
}
|
|
5164
5164
|
/**
|
|
5165
|
-
* #478: makes `coolify:app` keyed {@link
|
|
5166
|
-
*
|
|
5167
|
-
* foundry's own platform key, reached at `platform.
|
|
5165
|
+
* #478: makes `coolify:app` keyed {@link PLATFORM_EDGE_KEY} true -- the
|
|
5166
|
+
* edge host (https://github.com/wtfalch/edge), its own database, and
|
|
5167
|
+
* foundry's own platform key, reached at `platform.edge.hostname` through
|
|
5168
5168
|
* a Cloudflare tunnel and DNS record (the public-service shape
|
|
5169
5169
|
* {@link provisionPlatformMail}/{@link provisionPlatformAuth} use, not
|
|
5170
|
-
* `platform:files`' internal-only one -- `
|
|
5170
|
+
* `platform:files`' internal-only one -- `EDGE_URL` has to be reachable by
|
|
5171
5171
|
* every org's own app, which runs outside this Coolify network, and by
|
|
5172
5172
|
* Archon).
|
|
5173
5173
|
*
|
|
5174
5174
|
* One Coolify app only, unlike `platform:files`/`platform:ai`'s separate
|
|
5175
|
-
* `migrate` app:
|
|
5175
|
+
* `migrate` app: edge' own Dockerfile dropped its `migration` target
|
|
5176
5176
|
* (its own comment: no generated package's Coolify app ever built or ran
|
|
5177
5177
|
* that stage, which once shipped `wtfalch/cms` with an empty database and
|
|
5178
5178
|
* no runtime role). `apps/host/scripts/start.sh` now runs `migrate.mjs`
|
|
@@ -5188,27 +5188,27 @@ async function namesBlockedReason(names) {
|
|
|
5188
5188
|
* restricted `names_rt` role is never created here -- `migrate.mjs`'s own
|
|
5189
5189
|
* `ensureRuntimeRole` call creates or syncs it and sets its password from
|
|
5190
5190
|
* `DATABASE_URL` at every boot, so this function only has to mint that
|
|
5191
|
-
* password once and reuse it (`
|
|
5191
|
+
* password once and reuse it (`ensureEdgeRuntimeUrl`, the
|
|
5192
5192
|
* `ensureFilesRuntimeUrl` shape) -- never rotated, since the role's
|
|
5193
5193
|
* password is set from it on every migration.
|
|
5194
5194
|
*
|
|
5195
5195
|
* The platform key follows `platform:files`' management-key discipline
|
|
5196
5196
|
* (CLAUDE.md rule 1): a random secret is minted once, its id recorded
|
|
5197
|
-
* undelivered and persisted before {@link
|
|
5197
|
+
* undelivered and persisted before {@link PlatformEdgeDeps.deliverSecret}
|
|
5198
5198
|
* is even tried, and the guard recorded only once delivery AND the env
|
|
5199
|
-
* write (the SHA-256 hash, as `
|
|
5199
|
+
* write (the SHA-256 hash, as `EDGE_PLATFORM_KEY_HASH`) both succeeded. The
|
|
5200
5200
|
* secret itself is never written to the host's own env, logged, returned
|
|
5201
5201
|
* or put in an error -- only its hash ever reaches names, and the full
|
|
5202
|
-
* value goes to {@link
|
|
5203
|
-
* {@link
|
|
5204
|
-
* back later when it mints or revokes an entry's own
|
|
5202
|
+
* value goes to {@link PlatformEdgeDeps.deliverSecret} under
|
|
5203
|
+
* {@link PLATFORM_EDGE_KEY_REF} alone, for Archon's own `EdgePort` to read
|
|
5204
|
+
* back later when it mints or revokes an entry's own edge key.
|
|
5205
5205
|
*
|
|
5206
5206
|
* `CLOUDFLARE_API_TOKEN`/`CLOUDFLARE_ACCOUNT_ID` on the runner are never
|
|
5207
|
-
* minted here either: they are the
|
|
5208
|
-
* Archon's), read back from {@link
|
|
5209
|
-
* {@link
|
|
5207
|
+
* minted here either: they are the edge service's own credential (never
|
|
5208
|
+
* Archon's), read back from {@link PlatformEdgeDeps.heldKeys} under
|
|
5209
|
+
* {@link PLATFORM_EDGE_CLOUDFLARE_TOKEN_REF} once a human has put one there
|
|
5210
5210
|
* (the issue's own "Human steps") -- `blockedSteps`
|
|
5211
|
-
* ({@link
|
|
5211
|
+
* ({@link edgeBlockedReason}) reports the step blocked, not planned, until
|
|
5212
5212
|
* then.
|
|
5213
5213
|
*
|
|
5214
5214
|
* Every part is safe to run again after a throw partway, `platform:files`'
|
|
@@ -5220,49 +5220,49 @@ async function namesBlockedReason(names) {
|
|
|
5220
5220
|
* app redeploys, with no new secret minted.
|
|
5221
5221
|
*
|
|
5222
5222
|
* Known gaps, left for review:
|
|
5223
|
-
* - `
|
|
5223
|
+
* - `EDGE_MAX_QUEUE_DEPTH` (the host's `.env.example`) is not set; the host
|
|
5224
5224
|
* falls back to its own default.
|
|
5225
|
-
* - `
|
|
5225
|
+
* - `EDGE_TRUST_CF_CONNECTING_IP` is always written `true` rather than
|
|
5226
5226
|
* left to the host's own default, since this step always puts the
|
|
5227
5227
|
* runner behind the Cloudflare tunnel it also creates.
|
|
5228
5228
|
*/
|
|
5229
|
-
async function
|
|
5229
|
+
async function provisionPlatformEdge(context, deps) {
|
|
5230
5230
|
const { platform } = context;
|
|
5231
|
-
const hostname = platform.
|
|
5232
|
-
const gitRef = platform.
|
|
5231
|
+
const hostname = platform.edge.hostname;
|
|
5232
|
+
const gitRef = platform.edge.gitRef;
|
|
5233
5233
|
const recorded = context.recordedIds();
|
|
5234
5234
|
const runtime = deps.runtime ?? {};
|
|
5235
5235
|
const sleep = runtime.sleep ?? defaultSleep;
|
|
5236
5236
|
const attempts = runtime.deliverAttempts ?? 3;
|
|
5237
|
-
const cloudflareToken = await deps.heldKeys?.resolve(
|
|
5237
|
+
const cloudflareToken = await deps.heldKeys?.resolve(PLATFORM_EDGE_CLOUDFLARE_TOKEN_REF);
|
|
5238
5238
|
if (!cloudflareToken || !deps.cloudflareAccountId) {
|
|
5239
|
-
throw new Error(`platform:
|
|
5239
|
+
throw new Error(`platform:edge needs the edge service's own Cloudflare API token (held under ${PLATFORM_EDGE_CLOUDFLARE_TOKEN_REF}) and account id (PlatformEdgeDeps.cloudflareAccountId); names never becomes healthy without them`);
|
|
5240
5240
|
}
|
|
5241
5241
|
// Never plan-and-throw (CLAUDE.md rule 2): the planner reports a lost
|
|
5242
5242
|
// secret and plans nothing; this is the same rule for a caller that
|
|
5243
5243
|
// reaches the step anyway.
|
|
5244
|
-
const lostKey = recorded[
|
|
5245
|
-
if (lostKey && !recorded[
|
|
5246
|
-
throw new Error(`platform:
|
|
5244
|
+
const lostKey = recorded[PLATFORM_EDGE_PLATFORM_KEY_UNDELIVERED_SCOPE_KEY];
|
|
5245
|
+
if (lostKey && !recorded[PLATFORM_EDGE_PLATFORM_KEY_SET_SCOPE_KEY]) {
|
|
5246
|
+
throw new Error(`platform:edge: the platform key (${lostKey}) was minted but never delivered; remove the recorded key ${PLATFORM_EDGE_PLATFORM_KEY_UNDELIVERED_SCOPE_KEY} to mint a new one`);
|
|
5247
5247
|
}
|
|
5248
5248
|
const record = async (key, id) => {
|
|
5249
5249
|
context.recordScopeId(key, id);
|
|
5250
5250
|
await context.persist?.();
|
|
5251
5251
|
};
|
|
5252
|
-
let projectUuid = recorded[
|
|
5252
|
+
let projectUuid = recorded[PLATFORM_EDGE_PROJECT_SCOPE_KEY];
|
|
5253
5253
|
if (!projectUuid) {
|
|
5254
|
-
const found = await deps.coolify.findProjectByName(
|
|
5254
|
+
const found = await deps.coolify.findProjectByName(PLATFORM_EDGE_PROJECT);
|
|
5255
5255
|
projectUuid = found
|
|
5256
5256
|
? found.uuid
|
|
5257
|
-
: (await deps.coolify.createProject(
|
|
5258
|
-
await record(
|
|
5257
|
+
: (await deps.coolify.createProject(PLATFORM_EDGE_PROJECT)).uuid;
|
|
5258
|
+
await record(PLATFORM_EDGE_PROJECT_SCOPE_KEY, projectUuid);
|
|
5259
5259
|
}
|
|
5260
5260
|
const { environment } = await deps.coolify.findOrCreateEnvironment(projectUuid, 'production');
|
|
5261
5261
|
const server = await resolvePlatformServer(deps.coolify);
|
|
5262
5262
|
// ---- database ----------------------------------------------------------
|
|
5263
|
-
let dbId = recorded[
|
|
5263
|
+
let dbId = recorded[PLATFORM_EDGE_DB_SCOPE_KEY];
|
|
5264
5264
|
if (!dbId) {
|
|
5265
|
-
const found = (await deps.coolify.listDatabases()).find((db) => db.name ===
|
|
5265
|
+
const found = (await deps.coolify.listDatabases()).find((db) => db.name === PLATFORM_EDGE_DB_NAME && db.environment_id === environment.id);
|
|
5266
5266
|
if (found) {
|
|
5267
5267
|
dbId = found.uuid;
|
|
5268
5268
|
}
|
|
@@ -5272,20 +5272,20 @@ async function provisionPlatformNames(context, deps) {
|
|
|
5272
5272
|
server_uuid: server.uuid,
|
|
5273
5273
|
environment_name: environment.name,
|
|
5274
5274
|
environment_uuid: environment.uuid,
|
|
5275
|
-
name:
|
|
5276
|
-
postgres_db:
|
|
5277
|
-
postgres_user:
|
|
5275
|
+
name: PLATFORM_EDGE_DB_NAME,
|
|
5276
|
+
postgres_db: EDGE_DB,
|
|
5277
|
+
postgres_user: EDGE_OWNER_ROLE,
|
|
5278
5278
|
});
|
|
5279
5279
|
dbId = created.uuid;
|
|
5280
5280
|
}
|
|
5281
|
-
await record(
|
|
5281
|
+
await record(PLATFORM_EDGE_DB_SCOPE_KEY, dbId);
|
|
5282
5282
|
}
|
|
5283
5283
|
const db = await getDatabaseStarted(deps.coolify, dbId);
|
|
5284
5284
|
if (!db.internal_db_url) {
|
|
5285
|
-
throw new Error('coolify:app for the platform (
|
|
5285
|
+
throw new Error('coolify:app for the platform (edge): the database has no internal_db_url yet -- retry this apply once Coolify reports one');
|
|
5286
5286
|
}
|
|
5287
5287
|
// ---- the app -------------------------------------------------------------
|
|
5288
|
-
const app = await findOrCreatePlatformApp(deps.coolify,
|
|
5288
|
+
const app = await findOrCreatePlatformApp(deps.coolify, EDGE_APP_NAME, environment, async () => {
|
|
5289
5289
|
const githubApp = await deps.coolify.requireGithubApp();
|
|
5290
5290
|
return deps.coolify.createGithubAppApplication({
|
|
5291
5291
|
project_uuid: projectUuid,
|
|
@@ -5293,41 +5293,41 @@ async function provisionPlatformNames(context, deps) {
|
|
|
5293
5293
|
environment_name: environment.name,
|
|
5294
5294
|
environment_uuid: environment.uuid,
|
|
5295
5295
|
github_app_uuid: githubApp.uuid,
|
|
5296
|
-
git_repository:
|
|
5296
|
+
git_repository: EDGE_REPO,
|
|
5297
5297
|
git_branch: gitRef,
|
|
5298
5298
|
build_pack: 'dockerfile',
|
|
5299
|
-
name:
|
|
5299
|
+
name: EDGE_APP_NAME,
|
|
5300
5300
|
});
|
|
5301
5301
|
});
|
|
5302
5302
|
const appId = app.appId;
|
|
5303
|
-
await record(
|
|
5303
|
+
await record(PLATFORM_EDGE_APP_SCOPE_KEY, appId);
|
|
5304
5304
|
// Set on every run, not only after a create: a throw right after the
|
|
5305
5305
|
// create must not leave the app on the wrong target for good, and a
|
|
5306
5306
|
// changed `gitRef` has to reach an app made on an earlier one.
|
|
5307
5307
|
await deps.coolify.updateApplication(appId, {
|
|
5308
5308
|
dockerfile_target_build: 'runner',
|
|
5309
|
-
custom_internal_name:
|
|
5309
|
+
custom_internal_name: EDGE_RUNNER_INTERNAL_NAME,
|
|
5310
5310
|
git_branch: gitRef,
|
|
5311
5311
|
});
|
|
5312
|
-
const runtimeUrl = await
|
|
5312
|
+
const runtimeUrl = await ensureEdgeRuntimeUrl(deps.coolify, appId, db.internal_db_url);
|
|
5313
5313
|
// ---- the platform key, once ----------------------------------------------
|
|
5314
5314
|
// The recorded `set` id *is* the hash (never read back from Coolify's own
|
|
5315
5315
|
// env, which can be missing or stale on a re-apply): the hash is never
|
|
5316
5316
|
// secret, so it doubles as both the undelivered id and the value the base
|
|
5317
5317
|
// env below writes, with nothing extra to compute or a live read that
|
|
5318
5318
|
// could fall back to a blank value.
|
|
5319
|
-
let keyHash = recorded[
|
|
5319
|
+
let keyHash = recorded[PLATFORM_EDGE_PLATFORM_KEY_SET_SCOPE_KEY] ?? '';
|
|
5320
5320
|
if (!keyHash) {
|
|
5321
5321
|
const secret = randomBytes(32).toString('base64url');
|
|
5322
5322
|
keyHash = createHash('sha256').update(secret).digest('hex');
|
|
5323
|
-
await record(
|
|
5324
|
-
await deliverWithRetry(deps.deliverSecret,
|
|
5323
|
+
await record(PLATFORM_EDGE_PLATFORM_KEY_UNDELIVERED_SCOPE_KEY, keyHash);
|
|
5324
|
+
await deliverWithRetry(deps.deliverSecret, PLATFORM_EDGE_KEY_REF, secret, attempts, sleep, 'the edge platform key', 'platform:edge');
|
|
5325
5325
|
// Recorded as soon as delivery succeeds, before the env write below (the
|
|
5326
5326
|
// base env block, further down, is what actually writes the hash) --
|
|
5327
5327
|
// so a failed or crashed env write is simply retried on the next apply
|
|
5328
5328
|
// instead of leaving an undelivered secret with no `set` partner and no
|
|
5329
5329
|
// way to recover but a hand edit of the recorded ids.
|
|
5330
|
-
await record(
|
|
5330
|
+
await record(PLATFORM_EDGE_PLATFORM_KEY_SET_SCOPE_KEY, keyHash);
|
|
5331
5331
|
}
|
|
5332
5332
|
// ---- base env, written on every run --------------------------------------
|
|
5333
5333
|
await deps.coolify.bulkUpsertEnvs(appId, [
|
|
@@ -5336,18 +5336,18 @@ async function provisionPlatformNames(context, deps) {
|
|
|
5336
5336
|
{ key: 'APP_URL', value: `https://${hostname}` },
|
|
5337
5337
|
{ key: 'CLOUDFLARE_API_TOKEN', value: cloudflareToken },
|
|
5338
5338
|
{ key: 'CLOUDFLARE_ACCOUNT_ID', value: deps.cloudflareAccountId },
|
|
5339
|
-
{ key: '
|
|
5339
|
+
{ key: 'EDGE_TRUST_CF_CONNECTING_IP', value: 'true' },
|
|
5340
5340
|
{ key: 'NODE_ENV', value: 'production' },
|
|
5341
|
-
{ key: '
|
|
5342
|
-
...(await missingEnvs(deps.coolify, appId,
|
|
5341
|
+
{ key: 'EDGE_PLATFORM_KEY_HASH', value: keyHash },
|
|
5342
|
+
...(await missingEnvs(deps.coolify, appId, PLATFORM_EDGE_RUNNER_DEFAULTS)),
|
|
5343
5343
|
]);
|
|
5344
5344
|
// ---- tunnel and DNS -------------------------------------------------------
|
|
5345
|
-
const existingTunnel = await deps.cloudflare.findTunnelByName(
|
|
5346
|
-
const tunnel = existingTunnel ?? (await deps.cloudflare.createTunnel(
|
|
5347
|
-
await record(
|
|
5345
|
+
const existingTunnel = await deps.cloudflare.findTunnelByName(EDGE_TUNNEL_NAME);
|
|
5346
|
+
const tunnel = existingTunnel ?? (await deps.cloudflare.createTunnel(EDGE_TUNNEL_NAME));
|
|
5347
|
+
await record(PLATFORM_EDGE_TUNNEL_SCOPE_KEY, tunnel.id);
|
|
5348
5348
|
await deps.cloudflare.putTunnelConfig(tunnel.id, {
|
|
5349
5349
|
ingress: [
|
|
5350
|
-
{ hostname, service: `http://${
|
|
5350
|
+
{ hostname, service: `http://${EDGE_RUNNER_INTERNAL_NAME}:${EDGE_PORT}` },
|
|
5351
5351
|
{ service: 'http_status:404' },
|
|
5352
5352
|
],
|
|
5353
5353
|
});
|
|
@@ -5356,16 +5356,16 @@ async function provisionPlatformNames(context, deps) {
|
|
|
5356
5356
|
coolify: deps.coolify,
|
|
5357
5357
|
cloudflare: deps.cloudflare,
|
|
5358
5358
|
tunnelId: tunnel.id,
|
|
5359
|
-
appName:
|
|
5359
|
+
appName: EDGE_CLOUDFLARED_APP_NAME,
|
|
5360
5360
|
projectUuid,
|
|
5361
5361
|
environment,
|
|
5362
5362
|
resolveServer: async () => server,
|
|
5363
|
-
recordedConnectorAppId: recorded[
|
|
5364
|
-
recordAppId: (connectorAppId) => record(
|
|
5363
|
+
recordedConnectorAppId: recorded[PLATFORM_EDGE_CONNECTOR_APP_SCOPE_KEY] ?? null,
|
|
5364
|
+
recordAppId: (connectorAppId) => record(PLATFORM_EDGE_CONNECTOR_APP_SCOPE_KEY, connectorAppId),
|
|
5365
5365
|
});
|
|
5366
5366
|
const zoneId = recorded[apexZoneKey(platform.apex)] ?? (await exactZone(deps.cloudflare, platform.apex))?.id;
|
|
5367
5367
|
if (!zoneId) {
|
|
5368
|
-
throw new Error(`platform:
|
|
5368
|
+
throw new Error(`platform:edge: no Cloudflare zone for ${platform.apex} to put ${hostname} in`);
|
|
5369
5369
|
}
|
|
5370
5370
|
const dnsTarget = `${tunnel.id}.cfargotunnel.com`;
|
|
5371
5371
|
const [dnsRecord] = await deps.cloudflare.findDnsRecord(zoneId, hostname, 'CNAME');
|
|
@@ -5390,10 +5390,10 @@ async function provisionPlatformNames(context, deps) {
|
|
|
5390
5390
|
// helper (deployment-record check, then the `/api/health` probe) can run
|
|
5391
5391
|
// in one call -- unlike `platform:mail`, nothing here needs to happen
|
|
5392
5392
|
// between the two phases.
|
|
5393
|
-
await waitForNewDeployment(deps.coolify, appId, { kind: 'ready', url: `https://${hostname}/api/health` }, runtime, sleep, 'platform:
|
|
5393
|
+
await waitForNewDeployment(deps.coolify, appId, { kind: 'ready', url: `https://${hostname}/api/health` }, runtime, sleep, 'platform:edge', `edge stays at ${recorded[PLATFORM_EDGE_APPLIED_GIT_REF_SCOPE_KEY] ?? 'no recorded ref'}`);
|
|
5394
5394
|
// Last: a run that failed anywhere above is planned again.
|
|
5395
|
-
context.recordScopeId(
|
|
5396
|
-
await record(
|
|
5395
|
+
context.recordScopeId(PLATFORM_EDGE_APPLIED_HOSTNAME_SCOPE_KEY, hostname);
|
|
5396
|
+
await record(PLATFORM_EDGE_APPLIED_GIT_REF_SCOPE_KEY, gitRef);
|
|
5397
5397
|
return { resourceId: appId };
|
|
5398
5398
|
}
|
|
5399
5399
|
/**
|
|
@@ -5403,7 +5403,7 @@ async function provisionPlatformNames(context, deps) {
|
|
|
5403
5403
|
* throws a message with no part of the URL in it, since the URL carries the
|
|
5404
5404
|
* owner's password.
|
|
5405
5405
|
*/
|
|
5406
|
-
async function
|
|
5406
|
+
async function ensureEdgeRuntimeUrl(coolify, appId, dbInternalUrl) {
|
|
5407
5407
|
const existing = (await coolify.listEnvs(appId)).find((e) => e.key === 'DATABASE_URL' && !e.is_preview && e.value);
|
|
5408
5408
|
if (existing)
|
|
5409
5409
|
return existing.value;
|
|
@@ -5414,10 +5414,10 @@ async function ensureNamesRuntimeUrl(coolify, appId, dbInternalUrl) {
|
|
|
5414
5414
|
({ host, pathname, search } = new URL(dbInternalUrl));
|
|
5415
5415
|
}
|
|
5416
5416
|
catch {
|
|
5417
|
-
throw new Error("coolify:app for the platform (
|
|
5417
|
+
throw new Error("coolify:app for the platform (edge): the database's internal_db_url could not be parsed to build the runtime DATABASE_URL");
|
|
5418
5418
|
}
|
|
5419
5419
|
const password = randomBytes(24).toString('base64url');
|
|
5420
|
-
return `postgres://${
|
|
5420
|
+
return `postgres://${EDGE_RUNTIME_ROLE}:${password}@${host}${pathname}${search}`;
|
|
5421
5421
|
}
|
|
5422
5422
|
function aiKeys(deps) {
|
|
5423
5423
|
const keys = deps?.keys;
|
package/dist/plan/step-system.js
CHANGED
|
@@ -4,7 +4,7 @@ const PLATFORM_APP_SYSTEMS = {
|
|
|
4
4
|
'platform:mail': 'stalwart',
|
|
5
5
|
'platform:files': 'files',
|
|
6
6
|
'platform:ai': 'ai',
|
|
7
|
-
'platform:
|
|
7
|
+
'platform:edge': 'edge',
|
|
8
8
|
};
|
|
9
9
|
/** The system `action` touches. The one place that decides it, so no planner sets it by hand. */
|
|
10
10
|
export function stepSystemOf(action) {
|