@wtfalch/foundry 0.21.0 → 0.22.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/plan/diff.js +176 -22
- package/dist/plan/executor.d.ts +21 -4
- package/dist/plan/executor.js +104 -36
- package/dist/plan/plan-apply.d.ts +15 -1
- package/dist/plan/plan-apply.js +13 -4
- package/dist/plan/platform-diff.d.ts +34 -0
- package/dist/plan/platform-diff.js +44 -6
- package/dist/plan/platform-org.d.ts +28 -0
- package/dist/plan/platform-org.js +17 -0
- package/dist/plan/platform-providers.d.ts +14 -3
- package/dist/plan/platform-providers.js +132 -12
- package/package.json +3 -2
|
@@ -9,7 +9,7 @@ import { type AdoptCandidate, type AdoptInput, type AdoptPlan } from './adopt.js
|
|
|
9
9
|
import { type AgoraHostPort } from './agora.js';
|
|
10
10
|
import { type StepAction } from './diff.js';
|
|
11
11
|
import { type LiveReaders, type LiveResource, type PlatformLiveReaders } from './live.js';
|
|
12
|
-
import { type HeldKeyResolver } from './platform-org.js';
|
|
12
|
+
import { type EdgePortName, type HeldKeyResolver } from './platform-org.js';
|
|
13
13
|
import type { CallsSfuConnectionPort } from './platform-sfu.js';
|
|
14
14
|
/** Who did it, for `@wtfalch/audit`'s "who, when, the plan, the result" (#9, #19). */
|
|
15
15
|
export interface AuditActor {
|
|
@@ -142,6 +142,8 @@ export interface StepExecutionContext {
|
|
|
142
142
|
* scope key can never collide with the `system:kind:key` form.
|
|
143
143
|
*/
|
|
144
144
|
recordScopeId(key: string, id: string): void;
|
|
145
|
+
/** Removes a recorded id (any key form). Optional: a context without it cannot forget one. */
|
|
146
|
+
forgetId?(key: string): void;
|
|
145
147
|
/**
|
|
146
148
|
* Writes every id recorded so far to the store now, not only at the end of
|
|
147
149
|
* the run. For an id that must survive a crash mid-step: the ai key's id
|
|
@@ -161,6 +163,14 @@ export interface StepExecutor {
|
|
|
161
163
|
execute(action: StepAction, context: StepExecutionContext): Promise<{
|
|
162
164
|
resourceId?: string;
|
|
163
165
|
} | undefined>;
|
|
166
|
+
/**
|
|
167
|
+
* #555: which of the edge-routed ports (`dns`, `zones`, `access`,
|
|
168
|
+
* `ratelimits`, `tunnels`) this executor was built with its own
|
|
169
|
+
* implementation of, which it uses before edge. The planner blocks a step
|
|
170
|
+
* that needs edge only if the port it needs is not listed here. Optional:
|
|
171
|
+
* an executor without it supplies none.
|
|
172
|
+
*/
|
|
173
|
+
ownEdgePorts?(): Partial<Record<EdgePortName, boolean>>;
|
|
164
174
|
}
|
|
165
175
|
/**
|
|
166
176
|
* #167: {@link StepExecutionContext}'s platform-scoped counterpart -- what
|
|
@@ -212,6 +222,10 @@ export interface PlatformStepExecutor {
|
|
|
212
222
|
hc?: string;
|
|
213
223
|
/** #540: see `PlanPlatformOptions.blocked.heartbeatsExecutorMissing` (`platform-diff.ts`). */
|
|
214
224
|
heartbeatsExecutorMissing?: boolean;
|
|
225
|
+
/** See `PlanPlatformOptions.blocked.archonRunsInsideApp`. */
|
|
226
|
+
archonRunsInsideApp?: boolean;
|
|
227
|
+
/** See `PlanPlatformOptions.blocked.archonDeployRunning`. */
|
|
228
|
+
archonDeployRunning?: string;
|
|
215
229
|
}>;
|
|
216
230
|
/**
|
|
217
231
|
* Steps whose live state differs from the declared one in a way the recorded
|
package/dist/plan/plan-apply.js
CHANGED
|
@@ -13,7 +13,7 @@ import { FILES_ROOT_UNDELIVERED_SCOPE_KEY, entryFilesUndeliveredScopeKey, } from
|
|
|
13
13
|
import { readLivePlatformResources, readLiveResources, } from './live.js';
|
|
14
14
|
import { lockedHostnames } from './locked-hostnames.js';
|
|
15
15
|
import { PLATFORM_ARCHON_KEY, PLATFORM_FILES_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_FILES_KEY, appStepKey, planPlatform as computePlatformPlanDetail, toPlatformPlan, } from './platform-diff.js';
|
|
16
|
-
import { DEFAULT_ORG_PLATFORM_READINESS, ORG_PLATFORM_KEY_REFS, agoraHostValuesFromPlatform, heldPlatformKeyRefs, orgPlatformReadinessFrom, } from './platform-org.js';
|
|
16
|
+
import { DEFAULT_ORG_PLATFORM_READINESS, ORG_PLATFORM_KEY_REFS, agoraHostValuesFromPlatform, dnsBlockedReason, heldPlatformKeyRefs, orgPlatformReadinessFrom, } from './platform-org.js';
|
|
17
17
|
import { PLATFORM_AUTH_BROKER_MAIL_KEY_REF } from './platform-providers.js';
|
|
18
18
|
import { liveWorkers, workerTraceScopeKeys } from './worker.js';
|
|
19
19
|
/** #441: the lease scope that serializes every mutation of one company, and the one for the shared platform. */
|
|
@@ -773,6 +773,9 @@ export class FoundryPlanApply {
|
|
|
773
773
|
recordScopeId: (key, id) => {
|
|
774
774
|
serviceIds[key] = id;
|
|
775
775
|
},
|
|
776
|
+
forgetId: (key) => {
|
|
777
|
+
delete serviceIds[key];
|
|
778
|
+
},
|
|
776
779
|
persistScopeIds: async () => {
|
|
777
780
|
await this.store.putAppliedState({
|
|
778
781
|
orgId,
|
|
@@ -938,7 +941,13 @@ export class FoundryPlanApply {
|
|
|
938
941
|
catch {
|
|
939
942
|
// not held
|
|
940
943
|
}
|
|
941
|
-
return {
|
|
944
|
+
return {
|
|
945
|
+
...readiness,
|
|
946
|
+
mailKeyHeld,
|
|
947
|
+
canHoldSecrets: typeof heldKeys?.put === 'function',
|
|
948
|
+
dnsBlocked: dnsBlockedReason(platform, recorded),
|
|
949
|
+
ownEdgePorts: this.executor.ownEdgePorts?.(),
|
|
950
|
+
};
|
|
942
951
|
}
|
|
943
952
|
/**
|
|
944
953
|
* #177: `AgoraHostValues` from the applied platform, the same
|
|
@@ -1171,7 +1180,7 @@ function destroyTargets(serviceIds, orgId) {
|
|
|
1171
1180
|
continue;
|
|
1172
1181
|
}
|
|
1173
1182
|
for (const [resourceKey, id] of Object.entries(serviceIds)) {
|
|
1174
|
-
if (!resourceKey.startsWith(prefix))
|
|
1183
|
+
if (!resourceKey.startsWith(prefix) || !id)
|
|
1175
1184
|
continue;
|
|
1176
1185
|
// Archon#271: foundry did not create an adopted app or database, so it never deletes it.
|
|
1177
1186
|
if (adopted.has(resourceKey))
|
|
@@ -1224,7 +1233,7 @@ function destroyTargets(serviceIds, orgId) {
|
|
|
1224
1233
|
targets.push({ system: 'edge', kind: 'key', key: entryKey, id });
|
|
1225
1234
|
}
|
|
1226
1235
|
for (const [resourceKey, id] of Object.entries(serviceIds)) {
|
|
1227
|
-
if (!resourceKey.startsWith('zitadel:app:'))
|
|
1236
|
+
if (!resourceKey.startsWith('zitadel:app:') || !id)
|
|
1228
1237
|
continue;
|
|
1229
1238
|
targets.push({
|
|
1230
1239
|
system: 'zitadel',
|
|
@@ -135,6 +135,15 @@ export declare const PLATFORM_ARCHON_INTEGRATIONS_KEY_SYNCED_SCOPE_KEY = "archon
|
|
|
135
135
|
* values until someone redeploys by hand (#467).
|
|
136
136
|
*/
|
|
137
137
|
export declare const PLATFORM_ARCHON_ENVS_DEPLOYED_SCOPE_KEY = "archon_envs_deployed_hash";
|
|
138
|
+
/**
|
|
139
|
+
* The uuid of the deployment Archon's step triggered without waiting
|
|
140
|
+
* (`PlatformArchonDeps.runsInsideApp`), `pending` between the trigger and the
|
|
141
|
+
* moment its uuid is known. The next plan reads that deployment's status:
|
|
142
|
+
* running blocks the step, failed or unknown plans it again, finished lets the
|
|
143
|
+
* next run of the step confirm it (the integrations key-synced marker) and
|
|
144
|
+
* clear this.
|
|
145
|
+
*/
|
|
146
|
+
export declare const PLATFORM_ARCHON_DEPLOYMENT_SCOPE_KEY = "archon_deployment";
|
|
138
147
|
/**
|
|
139
148
|
* #211 round 4: the broker app id Archon last registered its entry with.
|
|
140
149
|
* `provisionPlatformArchon` reads the broker to register with from
|
|
@@ -414,6 +423,16 @@ export declare function platformMailMxHost(apex: string): string;
|
|
|
414
423
|
*/
|
|
415
424
|
export declare const ORG_MAIL_APPLIED_APEX_SCOPE_KEY = "mail_applied_apex";
|
|
416
425
|
export declare const ORG_MAIL_APPLIED_HOST_SCOPE_KEY = "mail_applied_host";
|
|
426
|
+
/**
|
|
427
|
+
* #554: the mode and `usesDomain` the step last applied. No reader lists a
|
|
428
|
+
* live `coolify mail <org>` resource, so `planMail` decides "applied" from
|
|
429
|
+
* the record. Records written before these keys existed lack them; a
|
|
430
|
+
* missing mode vouches for `hosted` only, a missing `usesDomain` only when
|
|
431
|
+
* the declared domain equals the recorded `mail_domain`. A park records
|
|
432
|
+
* mode `off`.
|
|
433
|
+
*/
|
|
434
|
+
export declare const ORG_MAIL_APPLIED_MODE_SCOPE_KEY = "mail_applied_mode";
|
|
435
|
+
export declare const ORG_MAIL_APPLIED_USES_DOMAIN_SCOPE_KEY = "mail_applied_uses_domain";
|
|
417
436
|
/**
|
|
418
437
|
* The key of a hosting entry in a `coolify:app` or `cloudflare:access` step
|
|
419
438
|
* and in the ids recorded for it: `<orgId>:<entry key>`, so a second entry
|
|
@@ -577,6 +596,12 @@ export declare const PLATFORM_AI_MIGRATE_BLOCKED_SCOPE_KEY = "ai_platform_migrat
|
|
|
577
596
|
* that throws) is repaired by the next one.
|
|
578
597
|
*/
|
|
579
598
|
export declare const PLATFORM_AI_DEPLOY_PENDING_SCOPE_KEY = "ai_platform_deploy_pending";
|
|
599
|
+
/**
|
|
600
|
+
* Set before the `coolify:jobs` step writes the scheduler's env and cleared
|
|
601
|
+
* once its deploy finished, so a run that dies in between is repaired by the
|
|
602
|
+
* next one. The planner plans the step while it is set.
|
|
603
|
+
*/
|
|
604
|
+
export declare const JOBS_DEPLOY_PENDING_SCOPE_KEY = "jobs_deploy_pending";
|
|
580
605
|
/**
|
|
581
606
|
* #171: the resource key `planFiles` plans under and `FoundryPlatformStepExecutor`
|
|
582
607
|
* dispatches on, the {@link PLATFORM_AUTH_KEY} shape for the files host.
|
|
@@ -839,6 +864,15 @@ export interface PlanPlatformOptions {
|
|
|
839
864
|
* steps" note in `platform-providers.ts`'s `FoundryPlatformStepExecutor.blockedSteps`.
|
|
840
865
|
*/
|
|
841
866
|
heartbeatsApiKeyMissing?: boolean;
|
|
867
|
+
/**
|
|
868
|
+
* The host runs inside Archon's app (`PlatformArchonDeps.runsInsideApp`):
|
|
869
|
+
* Archon's step redeploys it, so it is ordered last in the plan, after
|
|
870
|
+
* every step that would otherwise be lost when the old container stops.
|
|
871
|
+
* Not a block.
|
|
872
|
+
*/
|
|
873
|
+
archonRunsInsideApp?: boolean;
|
|
874
|
+
/** Why Archon's step must wait: the deployment it triggered is still running. Blocks even an applied step. */
|
|
875
|
+
archonDeployRunning?: string;
|
|
842
876
|
};
|
|
843
877
|
/**
|
|
844
878
|
* Steps whose live state differs from the declared one in a way the recorded
|
|
@@ -156,6 +156,15 @@ export const PLATFORM_ARCHON_INTEGRATIONS_KEY_SYNCED_SCOPE_KEY = 'archon_integra
|
|
|
156
156
|
* values until someone redeploys by hand (#467).
|
|
157
157
|
*/
|
|
158
158
|
export const PLATFORM_ARCHON_ENVS_DEPLOYED_SCOPE_KEY = 'archon_envs_deployed_hash';
|
|
159
|
+
/**
|
|
160
|
+
* The uuid of the deployment Archon's step triggered without waiting
|
|
161
|
+
* (`PlatformArchonDeps.runsInsideApp`), `pending` between the trigger and the
|
|
162
|
+
* moment its uuid is known. The next plan reads that deployment's status:
|
|
163
|
+
* running blocks the step, failed or unknown plans it again, finished lets the
|
|
164
|
+
* next run of the step confirm it (the integrations key-synced marker) and
|
|
165
|
+
* clear this.
|
|
166
|
+
*/
|
|
167
|
+
export const PLATFORM_ARCHON_DEPLOYMENT_SCOPE_KEY = 'archon_deployment';
|
|
159
168
|
/**
|
|
160
169
|
* #211 round 4: the broker app id Archon last registered its entry with.
|
|
161
170
|
* `provisionPlatformArchon` reads the broker to register with from
|
|
@@ -451,6 +460,16 @@ export function platformMailMxHost(apex) {
|
|
|
451
460
|
*/
|
|
452
461
|
export const ORG_MAIL_APPLIED_APEX_SCOPE_KEY = 'mail_applied_apex';
|
|
453
462
|
export const ORG_MAIL_APPLIED_HOST_SCOPE_KEY = 'mail_applied_host';
|
|
463
|
+
/**
|
|
464
|
+
* #554: the mode and `usesDomain` the step last applied. No reader lists a
|
|
465
|
+
* live `coolify mail <org>` resource, so `planMail` decides "applied" from
|
|
466
|
+
* the record. Records written before these keys existed lack them; a
|
|
467
|
+
* missing mode vouches for `hosted` only, a missing `usesDomain` only when
|
|
468
|
+
* the declared domain equals the recorded `mail_domain`. A park records
|
|
469
|
+
* mode `off`.
|
|
470
|
+
*/
|
|
471
|
+
export const ORG_MAIL_APPLIED_MODE_SCOPE_KEY = 'mail_applied_mode';
|
|
472
|
+
export const ORG_MAIL_APPLIED_USES_DOMAIN_SCOPE_KEY = 'mail_applied_uses_domain';
|
|
454
473
|
/**
|
|
455
474
|
* The key of a hosting entry in a `coolify:app` or `cloudflare:access` step
|
|
456
475
|
* and in the ids recorded for it: `<orgId>:<entry key>`, so a second entry
|
|
@@ -653,6 +672,12 @@ export const PLATFORM_AI_MIGRATE_BLOCKED_SCOPE_KEY = 'ai_platform_migrate_blocke
|
|
|
653
672
|
* that throws) is repaired by the next one.
|
|
654
673
|
*/
|
|
655
674
|
export const PLATFORM_AI_DEPLOY_PENDING_SCOPE_KEY = 'ai_platform_deploy_pending';
|
|
675
|
+
/**
|
|
676
|
+
* Set before the `coolify:jobs` step writes the scheduler's env and cleared
|
|
677
|
+
* once its deploy finished, so a run that dies in between is repaired by the
|
|
678
|
+
* next one. The planner plans the step while it is set.
|
|
679
|
+
*/
|
|
680
|
+
export const JOBS_DEPLOY_PENDING_SCOPE_KEY = 'jobs_deploy_pending';
|
|
656
681
|
/**
|
|
657
682
|
* #171: the resource key `planFiles` plans under and `FoundryPlatformStepExecutor`
|
|
658
683
|
* dispatches on, the {@link PLATFORM_AUTH_KEY} shape for the files host.
|
|
@@ -900,7 +925,7 @@ export function planPlatform(platform, live, options = {}) {
|
|
|
900
925
|
plannedSteps.push(zone);
|
|
901
926
|
// #173: the jobs scheduler, second of the eight services this frame leaves
|
|
902
927
|
// open (this file's own doc comment above).
|
|
903
|
-
const jobs = planJobs(platform, byKey, claimed, jobsObservable);
|
|
928
|
+
const jobs = planJobs(platform, byKey, claimed, jobsObservable, recordedIds);
|
|
904
929
|
if (jobs.step)
|
|
905
930
|
plannedSteps.push(jobs.step);
|
|
906
931
|
// A blocked SFU is reported in `unmanaged`, not planned: a step the
|
|
@@ -963,8 +988,10 @@ export function planPlatform(platform, live, options = {}) {
|
|
|
963
988
|
// #175: last, on purpose. Archon's sign-in client lives in the sign-in
|
|
964
989
|
// `planAuth` builds, and its env reads what every other service's step
|
|
965
990
|
// records or delivers, so in one apply it runs after all of them.
|
|
966
|
-
const archon = planArchon(platform, recordedIds, blocked.archon ?? dnsBlocked?.('Archon'), authReady, drifted.archon);
|
|
967
|
-
|
|
991
|
+
const archon = planArchon(platform, recordedIds, blocked.archon ?? dnsBlocked?.('Archon'), authReady, drifted.archon, blocked.archonDeployRunning);
|
|
992
|
+
// The host runs inside this app: its step ends by redeploying the process
|
|
993
|
+
// that runs the plan, so it goes after everything else (pushed below).
|
|
994
|
+
if (archon.step && !blocked.archonRunsInsideApp)
|
|
968
995
|
plannedSteps.push(archon.step);
|
|
969
996
|
// #257: after every service above, so a database any of them just
|
|
970
997
|
// recorded is already visible to `recordedIds` on this same plan.
|
|
@@ -976,6 +1003,8 @@ export function planPlatform(platform, live, options = {}) {
|
|
|
976
1003
|
const heartbeats = planHeartbeats(platform, recordedIds, hcReady, blocked.heartbeatsExecutorMissing ?? false, blocked.heartbeatsApiKeyMissing ?? false);
|
|
977
1004
|
if (heartbeats.step)
|
|
978
1005
|
plannedSteps.push(heartbeats.step);
|
|
1006
|
+
if (archon.step && blocked.archonRunsInsideApp)
|
|
1007
|
+
plannedSteps.push(archon.step);
|
|
979
1008
|
// #251: one line per service whose recorded applied ref no longer matches
|
|
980
1009
|
// what the next apply would use (or, for zitadel, what is actually live).
|
|
981
1010
|
// Never pushed into `plannedSteps` -- see `VersionChangeItem`'s own doc
|
|
@@ -1114,7 +1143,7 @@ export const JOBS_SCHEDULER_KEY = 'jobs';
|
|
|
1114
1143
|
* (`diff.ts`'s eventual `jobs:registration`) has nothing to register against
|
|
1115
1144
|
* before this exists.
|
|
1116
1145
|
*/
|
|
1117
|
-
function planJobs(platform, byKey, claimed, observable) {
|
|
1146
|
+
function planJobs(platform, byKey, claimed, observable, recordedIds) {
|
|
1118
1147
|
const key = resourceKey('coolify', 'jobs', JOBS_SCHEDULER_KEY);
|
|
1119
1148
|
const live = byKey.get(key);
|
|
1120
1149
|
if (live)
|
|
@@ -1125,8 +1154,14 @@ function planJobs(platform, byKey, claimed, observable) {
|
|
|
1125
1154
|
// The step also starts the scheduler's database (0.16.3), so a live app
|
|
1126
1155
|
// whose database the reader saw stopped plans it again. A status the
|
|
1127
1156
|
// reader could not read is absent, and plans nothing.
|
|
1157
|
+
// An app without `DATABASE_URL_OWNER` also plans it again: the step
|
|
1158
|
+
// sets the owner and runtime URLs the scheduler's migrate stage needs.
|
|
1159
|
+
// So does a recorded pending deploy: the step wrote its env and died
|
|
1160
|
+
// before the deploy finished.
|
|
1128
1161
|
const databaseStatus = live.config.databaseStatus;
|
|
1129
|
-
if (
|
|
1162
|
+
if (live.config.ownerUrlMissing !== true &&
|
|
1163
|
+
!recordedIds[JOBS_DEPLOY_PENDING_SCOPE_KEY] &&
|
|
1164
|
+
(typeof databaseStatus !== 'string' || databaseStatus.startsWith('running'))) {
|
|
1130
1165
|
return { step: null, blocked: null };
|
|
1131
1166
|
}
|
|
1132
1167
|
}
|
|
@@ -2215,9 +2250,12 @@ function planAi(platform, recordedIds, executorBlocked) {
|
|
|
2215
2250
|
* ({@link PLATFORM_ARCHON_CONNECTOR_APP_SCOPE_KEY}) plans the step again, as
|
|
2216
2251
|
* {@link planAuth} does.
|
|
2217
2252
|
*/
|
|
2218
|
-
function planArchon(platform, recordedIds, executorBlocked, authReady, drifted) {
|
|
2253
|
+
function planArchon(platform, recordedIds, executorBlocked, authReady, drifted, deployRunning) {
|
|
2219
2254
|
if (!platform.archon.enabled)
|
|
2220
2255
|
return { step: null, blocked: null };
|
|
2256
|
+
// A deploy the step triggered is still running: planning it again would deploy twice.
|
|
2257
|
+
if (deployRunning)
|
|
2258
|
+
return { step: null, blocked: deployRunning };
|
|
2221
2259
|
const key = resourceKey('coolify', 'app', PLATFORM_ARCHON_KEY);
|
|
2222
2260
|
const applied = Boolean(recordedIds[key]) &&
|
|
2223
2261
|
Boolean(recordedIds[PLATFORM_ARCHON_CONNECTOR_APP_SCOPE_KEY]) &&
|
|
@@ -35,6 +35,8 @@ export declare const NO_DOMAIN_MAIL_APEX = "oikos.host";
|
|
|
35
35
|
* names the same "positional arguments that shifted on every merge" problem
|
|
36
36
|
* this avoids).
|
|
37
37
|
*/
|
|
38
|
+
/** The edge-routed ports an executor may be given its own implementation of. */
|
|
39
|
+
export type EdgePortName = 'dns' | 'zones' | 'access' | 'ratelimits' | 'tunnels';
|
|
38
40
|
export interface OrgPlatformReadiness {
|
|
39
41
|
/**
|
|
40
42
|
* `null` until the platform's own mail server (#169) is enabled and
|
|
@@ -96,6 +98,22 @@ export interface OrgPlatformReadiness {
|
|
|
96
98
|
* used to say there was none; N1 is no longer deployed by hand).
|
|
97
99
|
*/
|
|
98
100
|
edgeReady: boolean;
|
|
101
|
+
/**
|
|
102
|
+
* #555: why a company step that writes through edge cannot run yet, `null`
|
|
103
|
+
* or absent when none is blocked. DNS records, zones, Access apps, rate
|
|
104
|
+
* limit rules and tunnels all go through edge unless the host supplies its
|
|
105
|
+
* own port ({@link ownEdgePorts}); while edge is not applied, `diff.ts`
|
|
106
|
+
* reports each such step blocked instead of planning one that would fail
|
|
107
|
+
* half way.
|
|
108
|
+
*/
|
|
109
|
+
dnsBlocked?: string | null;
|
|
110
|
+
/**
|
|
111
|
+
* #555: the edge-routed ports the host's executor supplies itself
|
|
112
|
+
* (`FoundryStepExecutorPorts.dns` / `zones` / `access` / `ratelimits` /
|
|
113
|
+
* `tunnels`). The executor uses these before edge, so a step that needs
|
|
114
|
+
* only these is not blocked by {@link dnsBlocked}.
|
|
115
|
+
*/
|
|
116
|
+
ownEdgePorts?: Partial<Record<EdgePortName, boolean>>;
|
|
99
117
|
/** `https://<platform.edge.hostname>`, which an entry with `uses.edge` gets as `EDGE_URL`. `null` until `edgeReady`. */
|
|
100
118
|
edgeUrl?: string | null;
|
|
101
119
|
/**
|
|
@@ -149,6 +167,16 @@ export interface HeldKeyResolver {
|
|
|
149
167
|
* (which could carry provider detail) goes nowhere.
|
|
150
168
|
*/
|
|
151
169
|
export declare function heldPlatformKeyRefs(heldKeys: HeldKeyResolver): Promise<Set<string>>;
|
|
170
|
+
/**
|
|
171
|
+
* #555: the reason no company step may write DNS yet, `null` when one may.
|
|
172
|
+
* Every DNS record, zone, Access app, rate limit rule and tunnel call goes
|
|
173
|
+
* through edge unless the host supplies its own port
|
|
174
|
+
* ({@link OrgPlatformReadiness.ownEdgePorts}), so edge must be enabled and
|
|
175
|
+
* applied (the same check as `edgeReady`, without the host-port half). {@link FoundryPlanApply} sets
|
|
176
|
+
* {@link OrgPlatformReadiness.dnsBlocked} from this; the pure
|
|
177
|
+
* {@link orgPlatformReadinessFrom} leaves it unset (nothing blocked).
|
|
178
|
+
*/
|
|
179
|
+
export declare function dnsBlockedReason(platform: DeclaredPlatform, platformRecordedIds: Readonly<Record<string, string>>): string | null;
|
|
152
180
|
/**
|
|
153
181
|
* `FoundryPlanApply.computeFreshPlan`'s own precedent for turning a
|
|
154
182
|
* `PlatformStore`'s `get()`/`getServiceIds()` into what an org's plan needs.
|
|
@@ -63,6 +63,23 @@ export async function heldPlatformKeyRefs(heldKeys) {
|
|
|
63
63
|
function coolifyAppApplied(platformRecordedIds, key) {
|
|
64
64
|
return Boolean(platformRecordedIds[`coolify:app:${key}`]);
|
|
65
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* #555: the reason no company step may write DNS yet, `null` when one may.
|
|
68
|
+
* Every DNS record, zone, Access app, rate limit rule and tunnel call goes
|
|
69
|
+
* through edge unless the host supplies its own port
|
|
70
|
+
* ({@link OrgPlatformReadiness.ownEdgePorts}), so edge must be enabled and
|
|
71
|
+
* applied (the same check as `edgeReady`, without the host-port half). {@link FoundryPlanApply} sets
|
|
72
|
+
* {@link OrgPlatformReadiness.dnsBlocked} from this; the pure
|
|
73
|
+
* {@link orgPlatformReadinessFrom} leaves it unset (nothing blocked).
|
|
74
|
+
*/
|
|
75
|
+
export function dnsBlockedReason(platform, platformRecordedIds) {
|
|
76
|
+
if (!platform.edge.enabled) {
|
|
77
|
+
return 'every DNS record is written through edge, and the platform does not enable edge';
|
|
78
|
+
}
|
|
79
|
+
return coolifyAppApplied(platformRecordedIds, PLATFORM_EDGE_KEY)
|
|
80
|
+
? null
|
|
81
|
+
: 'every DNS record is written through edge, and edge is not applied yet';
|
|
82
|
+
}
|
|
66
83
|
/**
|
|
67
84
|
* `FoundryPlanApply.computeFreshPlan`'s own precedent for turning a
|
|
68
85
|
* `PlatformStore`'s `get()`/`getServiceIds()` into what an org's plan needs.
|
|
@@ -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
|
|
@@ -290,6 +290,8 @@ export declare class FoundryPlatformStepExecutor implements PlatformStepExecutor
|
|
|
290
290
|
hc?: string;
|
|
291
291
|
heartbeatsExecutorMissing?: boolean;
|
|
292
292
|
heartbeatsApiKeyMissing?: boolean;
|
|
293
|
+
archonRunsInsideApp?: boolean;
|
|
294
|
+
archonDeployRunning?: string;
|
|
293
295
|
}>;
|
|
294
296
|
/**
|
|
295
297
|
* Whether Archon's live Access policy lets through anyone but the declared
|
|
@@ -474,7 +476,7 @@ export interface PlatformMailRuntime {
|
|
|
474
476
|
* {@link PlatformStepExecutionContext}, bundled like {@link PlatformAuthDeps}.
|
|
475
477
|
*/
|
|
476
478
|
export interface PlatformArchonDeps {
|
|
477
|
-
coolify: Pick<CoolifyPort, 'findProjectByName' | 'createProject' | 'findOrCreateEnvironment' | 'listServers' | 'findApplicationByName' | 'getApplication' | 'requireGithubApp' | 'createGithubAppApplication' | 'createDockerfileApplication' | 'listDatabases' | 'createPostgresDatabase' | 'getDatabase' | 'raw' | 'bulkUpsertEnvs' | 'listEnvs' | 'deleteEnv' | 'updateApplication' | 'deploy' | 'deployAndWait'>;
|
|
479
|
+
coolify: Pick<CoolifyPort, 'findProjectByName' | 'createProject' | 'findOrCreateEnvironment' | 'listServers' | 'findApplicationByName' | 'getApplication' | 'requireGithubApp' | 'createGithubAppApplication' | 'createDockerfileApplication' | 'listDatabases' | 'createPostgresDatabase' | 'getDatabase' | 'raw' | 'bulkUpsertEnvs' | 'listEnvs' | 'deleteEnv' | 'updateApplication' | 'deploy' | 'deployAndWait' | 'getDeployment'>;
|
|
478
480
|
/** F4.dns: every DNS record call, through edge at its recorded address (see {@link PlatformDnsFactory}). */
|
|
479
481
|
dns: PlatformDnsFactory;
|
|
480
482
|
/** F4.tunnels: every tunnel call, through edge at its recorded address (see {@link PlatformTunnelsFactory}). */
|
|
@@ -493,6 +495,15 @@ export interface PlatformArchonDeps {
|
|
|
493
495
|
* Access app nobody can pass.
|
|
494
496
|
*/
|
|
495
497
|
accessEmails: readonly string[];
|
|
498
|
+
/**
|
|
499
|
+
* True when the host process runs inside the Coolify app this step
|
|
500
|
+
* redeploys (Archon's console is `platform:archon`). A deploy that waits
|
|
501
|
+
* would be killed with the old container, so the step then records every
|
|
502
|
+
* applied marker first, and triggers the deploy last, without waiting.
|
|
503
|
+
* The planner puts the step last in the plan. Next plan: the step runs
|
|
504
|
+
* again only if the app is not running (`archonRedeployFailed`).
|
|
505
|
+
*/
|
|
506
|
+
runsInsideApp?: boolean;
|
|
496
507
|
/**
|
|
497
508
|
* Reads a secret an earlier step handed to the host through
|
|
498
509
|
* `deliverSecret`, or `null` when none is held under `ref` yet -- the same
|
|
@@ -1154,7 +1165,7 @@ export declare class ProvidersPlatformCloudflareReader implements PlatformCloudf
|
|
|
1154
1165
|
export declare class ProvidersPlatformCoolifyReader implements PlatformCoolifyReader {
|
|
1155
1166
|
private readonly client;
|
|
1156
1167
|
private readonly platformStore;
|
|
1157
|
-
constructor(client: Pick<CoolifyPort, 'getApplication' | 'getDatabase' | 'listDatabaseBackups' | 'raw'>, platformStore: Pick<PlatformStore, 'getServiceIds'>);
|
|
1168
|
+
constructor(client: Pick<CoolifyPort, 'getApplication' | 'getDatabase' | 'listDatabaseBackups' | 'listEnvs' | 'raw'>, platformStore: Pick<PlatformStore, 'getServiceIds'>);
|
|
1158
1169
|
listResources(): Promise<LiveResource[]>;
|
|
1159
1170
|
}
|
|
1160
1171
|
/** Scope id (no `:`): the share domains the runner's Coolify `domains` was last set to, comma-joined. */
|