@wtfalch/foundry 0.21.0 → 0.21.1
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.
|
@@ -577,6 +577,12 @@ export declare const PLATFORM_AI_MIGRATE_BLOCKED_SCOPE_KEY = "ai_platform_migrat
|
|
|
577
577
|
* that throws) is repaired by the next one.
|
|
578
578
|
*/
|
|
579
579
|
export declare const PLATFORM_AI_DEPLOY_PENDING_SCOPE_KEY = "ai_platform_deploy_pending";
|
|
580
|
+
/**
|
|
581
|
+
* Set before the `coolify:jobs` step writes the scheduler's env and cleared
|
|
582
|
+
* once its deploy finished, so a run that dies in between is repaired by the
|
|
583
|
+
* next one. The planner plans the step while it is set.
|
|
584
|
+
*/
|
|
585
|
+
export declare const JOBS_DEPLOY_PENDING_SCOPE_KEY = "jobs_deploy_pending";
|
|
580
586
|
/**
|
|
581
587
|
* #171: the resource key `planFiles` plans under and `FoundryPlatformStepExecutor`
|
|
582
588
|
* dispatches on, the {@link PLATFORM_AUTH_KEY} shape for the files host.
|
|
@@ -653,6 +653,12 @@ export const PLATFORM_AI_MIGRATE_BLOCKED_SCOPE_KEY = 'ai_platform_migrate_blocke
|
|
|
653
653
|
* that throws) is repaired by the next one.
|
|
654
654
|
*/
|
|
655
655
|
export const PLATFORM_AI_DEPLOY_PENDING_SCOPE_KEY = 'ai_platform_deploy_pending';
|
|
656
|
+
/**
|
|
657
|
+
* Set before the `coolify:jobs` step writes the scheduler's env and cleared
|
|
658
|
+
* once its deploy finished, so a run that dies in between is repaired by the
|
|
659
|
+
* next one. The planner plans the step while it is set.
|
|
660
|
+
*/
|
|
661
|
+
export const JOBS_DEPLOY_PENDING_SCOPE_KEY = 'jobs_deploy_pending';
|
|
656
662
|
/**
|
|
657
663
|
* #171: the resource key `planFiles` plans under and `FoundryPlatformStepExecutor`
|
|
658
664
|
* dispatches on, the {@link PLATFORM_AUTH_KEY} shape for the files host.
|
|
@@ -900,7 +906,7 @@ export function planPlatform(platform, live, options = {}) {
|
|
|
900
906
|
plannedSteps.push(zone);
|
|
901
907
|
// #173: the jobs scheduler, second of the eight services this frame leaves
|
|
902
908
|
// open (this file's own doc comment above).
|
|
903
|
-
const jobs = planJobs(platform, byKey, claimed, jobsObservable);
|
|
909
|
+
const jobs = planJobs(platform, byKey, claimed, jobsObservable, recordedIds);
|
|
904
910
|
if (jobs.step)
|
|
905
911
|
plannedSteps.push(jobs.step);
|
|
906
912
|
// A blocked SFU is reported in `unmanaged`, not planned: a step the
|
|
@@ -1114,7 +1120,7 @@ export const JOBS_SCHEDULER_KEY = 'jobs';
|
|
|
1114
1120
|
* (`diff.ts`'s eventual `jobs:registration`) has nothing to register against
|
|
1115
1121
|
* before this exists.
|
|
1116
1122
|
*/
|
|
1117
|
-
function planJobs(platform, byKey, claimed, observable) {
|
|
1123
|
+
function planJobs(platform, byKey, claimed, observable, recordedIds) {
|
|
1118
1124
|
const key = resourceKey('coolify', 'jobs', JOBS_SCHEDULER_KEY);
|
|
1119
1125
|
const live = byKey.get(key);
|
|
1120
1126
|
if (live)
|
|
@@ -1125,8 +1131,14 @@ function planJobs(platform, byKey, claimed, observable) {
|
|
|
1125
1131
|
// The step also starts the scheduler's database (0.16.3), so a live app
|
|
1126
1132
|
// whose database the reader saw stopped plans it again. A status the
|
|
1127
1133
|
// reader could not read is absent, and plans nothing.
|
|
1134
|
+
// An app without `DATABASE_URL_OWNER` also plans it again: the step
|
|
1135
|
+
// sets the owner and runtime URLs the scheduler's migrate stage needs.
|
|
1136
|
+
// So does a recorded pending deploy: the step wrote its env and died
|
|
1137
|
+
// before the deploy finished.
|
|
1128
1138
|
const databaseStatus = live.config.databaseStatus;
|
|
1129
|
-
if (
|
|
1139
|
+
if (live.config.ownerUrlMissing !== true &&
|
|
1140
|
+
!recordedIds[JOBS_DEPLOY_PENDING_SCOPE_KEY] &&
|
|
1141
|
+
(typeof databaseStatus !== 'string' || databaseStatus.startsWith('running'))) {
|
|
1130
1142
|
return { step: null, blocked: null };
|
|
1131
1143
|
}
|
|
1132
1144
|
}
|
|
@@ -117,7 +117,7 @@ export declare const JOBS_ADMIN_TOKEN_REF = "jobs:scheduler:adminToken";
|
|
|
117
117
|
* org's own app, reused here for the platform's one scheduler instead of once
|
|
118
118
|
* per org.
|
|
119
119
|
*/
|
|
120
|
-
type JobsCoolifyPort = Pick<CoolifyPort, 'findProjectByName' | 'createProject' | 'findOrCreateEnvironment' | 'listServers' | 'requireGithubApp' | 'findApplicationByName' | 'createGithubAppApplication' | 'listDatabases' | 'createPostgresDatabase' | 'getDatabase' | 'raw' | 'bulkUpsertEnvs' | 'getApplication' | 'deploy'>;
|
|
120
|
+
type JobsCoolifyPort = Pick<CoolifyPort, 'findProjectByName' | 'createProject' | 'findOrCreateEnvironment' | 'listServers' | 'requireGithubApp' | 'findApplicationByName' | 'createGithubAppApplication' | 'listDatabases' | 'createPostgresDatabase' | 'getDatabase' | 'raw' | 'bulkUpsertEnvs' | 'listEnvs' | 'getApplication' | 'deploy'>;
|
|
121
121
|
/**
|
|
122
122
|
* Options for {@link FoundryPlatformStepExecutor}'s constructor, replacing
|
|
123
123
|
* four positional arguments that shifted on every merge that added a
|
|
@@ -1154,7 +1154,7 @@ export declare class ProvidersPlatformCloudflareReader implements PlatformCloudf
|
|
|
1154
1154
|
export declare class ProvidersPlatformCoolifyReader implements PlatformCoolifyReader {
|
|
1155
1155
|
private readonly client;
|
|
1156
1156
|
private readonly platformStore;
|
|
1157
|
-
constructor(client: Pick<CoolifyPort, 'getApplication' | 'getDatabase' | 'listDatabaseBackups' | 'raw'>, platformStore: Pick<PlatformStore, 'getServiceIds'>);
|
|
1157
|
+
constructor(client: Pick<CoolifyPort, 'getApplication' | 'getDatabase' | 'listDatabaseBackups' | 'listEnvs' | 'raw'>, platformStore: Pick<PlatformStore, 'getServiceIds'>);
|
|
1158
1158
|
listResources(): Promise<LiveResource[]>;
|
|
1159
1159
|
}
|
|
1160
1160
|
/** Scope id (no `:`): the share domains the runner's Coolify `domains` was last set to, comma-joined. */
|
|
@@ -8,7 +8,7 @@ import { waitForDeploymentFinished, waitForHealthyVersion, waitForNewDeployment,
|
|
|
8
8
|
import { ACCESS_POLICY_NAME, getDatabaseStarted, isNotFoundError, isRunningOrStarting, } from './executor.js';
|
|
9
9
|
import { healthCheckSettings } from './health-check-settings.js';
|
|
10
10
|
import { coolifyDomains, filesShareHostname } from './hostnames.js';
|
|
11
|
-
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_INTERNAL_URL_SCOPE_KEY, 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_HC_API_KEY_REF, PLATFORM_HC_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_HC_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_HC_KEY, PLATFORM_HEARTBEATS_APPLIED_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, PLATFORM_UPTIME_HEARTBEAT_CHECK_NAME, archonBootstrapOperator, desiredHeartbeatCheckNames, heartbeatCheckUrlRef, parseBrokerDeployRecord, parseLoginClientRecord, platformBackupHeartbeatCheckName, platformMailMxHost, } from './platform-diff.js';
|
|
11
|
+
import { AUTH_ZITADEL_RUN_OPTIONS, JOBS_DEPLOY_PENDING_SCOPE_KEY, 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_INTERNAL_URL_SCOPE_KEY, 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_HC_API_KEY_REF, PLATFORM_HC_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_HC_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_HC_KEY, PLATFORM_HEARTBEATS_APPLIED_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, PLATFORM_UPTIME_HEARTBEAT_CHECK_NAME, archonBootstrapOperator, desiredHeartbeatCheckNames, heartbeatCheckUrlRef, parseBrokerDeployRecord, parseLoginClientRecord, platformBackupHeartbeatCheckName, platformMailMxHost, } from './platform-diff.js';
|
|
12
12
|
import { applyCallsSfuPlatformStep } from './platform-sfu.js';
|
|
13
13
|
import { CreateInstanceError, createZitadelInstance, generateSystemApiKeypair, instanceWithoutBearerMessage, systemApiUsersEnv, } from './zitadel-system-api.js';
|
|
14
14
|
/** The key `runPlatformSteps` records the apex zone's id under (`system:kind:key`). */
|
|
@@ -596,7 +596,25 @@ export class FoundryPlatformStepExecutor {
|
|
|
596
596
|
if (!db.internal_db_url) {
|
|
597
597
|
throw new Error(`platform step coolify:jobs: database ${dbId} has no internal_db_url yet; not deploying the scheduler without a database`);
|
|
598
598
|
}
|
|
599
|
-
|
|
599
|
+
// The scheduler migrates as `DATABASE_URL_OWNER` and creates its own
|
|
600
|
+
// `jobs_rt` role from `DATABASE_URL` (jobs README, "Deploying"). An app
|
|
601
|
+
// with no owner URL still holds the owner's URL in `DATABASE_URL`, so
|
|
602
|
+
// that is never reused: a runtime URL is minted, once, and kept after.
|
|
603
|
+
const hasOwnerUrl = (await coolify.listEnvs(appId)).some((e) => e.key === 'DATABASE_URL_OWNER' && !e.is_preview && e.value);
|
|
604
|
+
const runtimeUrl = await ensureAiRuntimeUrl(coolify, hasOwnerUrl ? [appId] : [], db.internal_db_url, 'platform step coolify:jobs');
|
|
605
|
+
const envs = [
|
|
606
|
+
{ key: 'DATABASE_URL_OWNER', value: db.internal_db_url },
|
|
607
|
+
{ key: 'DATABASE_URL', value: runtimeUrl },
|
|
608
|
+
];
|
|
609
|
+
// Whether the scheduler runs on envs other than the ones written below.
|
|
610
|
+
// Marked and persisted before the first write, and cleared only after the
|
|
611
|
+
// deploy, so a run that dies in between leaves the mark and the next run
|
|
612
|
+
// deploys again (`CLAUDE.md` rule 4).
|
|
613
|
+
const pending = Boolean(recorded[JOBS_DEPLOY_PENDING_SCOPE_KEY]) || (await envsDiffer(coolify, appId, envs));
|
|
614
|
+
if (pending) {
|
|
615
|
+
context.recordScopeId(JOBS_DEPLOY_PENDING_SCOPE_KEY, '1');
|
|
616
|
+
await context.persist?.();
|
|
617
|
+
}
|
|
600
618
|
// SCHEDULER_ADMIN_TOKEN gates the admin API the org registrations use
|
|
601
619
|
// (jobs README, "The admin API"); unset, every admin route answers 401.
|
|
602
620
|
// Delivered to the host before it is written to the app, so a failed
|
|
@@ -629,8 +647,12 @@ export class FoundryPlatformStepExecutor {
|
|
|
629
647
|
// a redeploy, so deploy after a create or a new token. Also when it is not
|
|
630
648
|
// running, so a deploy that failed on an earlier run is retried.
|
|
631
649
|
const running = (await coolify.getApplication(appId)).status?.startsWith('running') ?? false;
|
|
632
|
-
if (!existingApp || tokenJustSet || !running)
|
|
650
|
+
if (!existingApp || tokenJustSet || pending || !running)
|
|
633
651
|
await coolify.deploy(appId);
|
|
652
|
+
if (pending) {
|
|
653
|
+
context.recordScopeId(JOBS_DEPLOY_PENDING_SCOPE_KEY, '');
|
|
654
|
+
await context.persist?.();
|
|
655
|
+
}
|
|
634
656
|
return { resourceId: appId };
|
|
635
657
|
}
|
|
636
658
|
}
|
|
@@ -4185,7 +4207,7 @@ async function provisionPlatformAi(context, deps) {
|
|
|
4185
4207
|
// ---- env -----------------------------------------------------------------
|
|
4186
4208
|
// Both database URLs are live credentials: handed to `bulkUpsertEnvs` and
|
|
4187
4209
|
// nowhere else, never returned, recorded, logged or put in an error.
|
|
4188
|
-
const runtimeUrl = await ensureAiRuntimeUrl(coolify, [runner.appId, worker.appId, migrate.appId], ownerUrl);
|
|
4210
|
+
const runtimeUrl = await ensureAiRuntimeUrl(coolify, [runner.appId, worker.appId, migrate.appId], ownerUrl, 'platform:ai');
|
|
4189
4211
|
const migrateEnvs = [
|
|
4190
4212
|
{ key: 'DATABASE_URL_OWNER', value: ownerUrl },
|
|
4191
4213
|
{ key: 'DATABASE_URL', value: runtimeUrl },
|
|
@@ -4556,7 +4578,7 @@ async function waitForAiMigration(coolify, appId, runtime, sleep) {
|
|
|
4556
4578
|
* inside a try: Node's parse error carries the whole input, credential
|
|
4557
4579
|
* included, so it is replaced with a fixed message.
|
|
4558
4580
|
*/
|
|
4559
|
-
async function ensureAiRuntimeUrl(coolify, appIds, ownerUrl) {
|
|
4581
|
+
async function ensureAiRuntimeUrl(coolify, appIds, ownerUrl, step) {
|
|
4560
4582
|
for (const appId of appIds) {
|
|
4561
4583
|
const existing = (await coolify.listEnvs(appId)).find((e) => e.key === 'DATABASE_URL' && !e.is_preview && e.value);
|
|
4562
4584
|
if (existing)
|
|
@@ -4569,11 +4591,11 @@ async function ensureAiRuntimeUrl(coolify, appIds, ownerUrl) {
|
|
|
4569
4591
|
({ host, pathname, search } = new URL(ownerUrl));
|
|
4570
4592
|
}
|
|
4571
4593
|
catch {
|
|
4572
|
-
throw new Error(
|
|
4594
|
+
throw new Error(`${step}: the database's internal_db_url could not be parsed`);
|
|
4573
4595
|
}
|
|
4574
4596
|
const database = pathname.replace(/^\//, '');
|
|
4575
4597
|
if (!/^[A-Za-z0-9_]+$/.test(database)) {
|
|
4576
|
-
throw new Error(
|
|
4598
|
+
throw new Error(`${step}: the database's name cannot form a runtime role name (letters, digits and _ only)`);
|
|
4577
4599
|
}
|
|
4578
4600
|
const password = randomBytes(24).toString('base64url');
|
|
4579
4601
|
return `postgres://${database}_rt:${password}@${host}${pathname}${search}`;
|
|
@@ -7098,6 +7120,16 @@ export class ProvidersPlatformCoolifyReader {
|
|
|
7098
7120
|
databaseStatus = undefined;
|
|
7099
7121
|
}
|
|
7100
7122
|
}
|
|
7123
|
+
// An app with no `DATABASE_URL_OWNER` predates the owner/runtime
|
|
7124
|
+
// split, so `planJobs` plans the step again to set both URLs. Left
|
|
7125
|
+
// out when the envs cannot be read: unknown is not "missing".
|
|
7126
|
+
let ownerUrlMissing;
|
|
7127
|
+
try {
|
|
7128
|
+
ownerUrlMissing = !(await this.client.listEnvs(app.uuid)).some((e) => e.key === 'DATABASE_URL_OWNER' && !e.is_preview && e.value);
|
|
7129
|
+
}
|
|
7130
|
+
catch {
|
|
7131
|
+
ownerUrlMissing = undefined;
|
|
7132
|
+
}
|
|
7101
7133
|
resources.push({
|
|
7102
7134
|
system: 'coolify',
|
|
7103
7135
|
kind: 'jobs',
|
|
@@ -7106,6 +7138,7 @@ export class ProvidersPlatformCoolifyReader {
|
|
|
7106
7138
|
uuid: app.uuid,
|
|
7107
7139
|
status: app.status,
|
|
7108
7140
|
...(databaseStatus !== undefined && { databaseStatus }),
|
|
7141
|
+
...(ownerUrlMissing && { ownerUrlMissing }),
|
|
7109
7142
|
},
|
|
7110
7143
|
});
|
|
7111
7144
|
}
|