@wtfalch/foundry 0.21.1 → 0.22.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.
- package/dist/plan/diff.js +202 -30
- package/dist/plan/executor.d.ts +33 -28
- package/dist/plan/executor.js +176 -53
- package/dist/plan/hostnames.d.ts +5 -3
- package/dist/plan/hostnames.js +8 -4
- package/dist/plan/login-host.d.ts +17 -2
- package/dist/plan/login-host.js +24 -3
- package/dist/plan/plan-apply.d.ts +15 -1
- package/dist/plan/plan-apply.js +13 -4
- package/dist/plan/platform-diff.d.ts +28 -0
- package/dist/plan/platform-diff.js +29 -3
- 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 -1
- package/dist/plan/platform-providers.js +95 -8
- package/package.json +3 -2
|
@@ -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
|
|
@@ -845,6 +864,15 @@ export interface PlanPlatformOptions {
|
|
|
845
864
|
* steps" note in `platform-providers.ts`'s `FoundryPlatformStepExecutor.blockedSteps`.
|
|
846
865
|
*/
|
|
847
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;
|
|
848
876
|
};
|
|
849
877
|
/**
|
|
850
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
|
|
@@ -969,8 +988,10 @@ export function planPlatform(platform, live, options = {}) {
|
|
|
969
988
|
// #175: last, on purpose. Archon's sign-in client lives in the sign-in
|
|
970
989
|
// `planAuth` builds, and its env reads what every other service's step
|
|
971
990
|
// records or delivers, so in one apply it runs after all of them.
|
|
972
|
-
const archon = planArchon(platform, recordedIds, blocked.archon ?? dnsBlocked?.('Archon'), authReady, drifted.archon);
|
|
973
|
-
|
|
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)
|
|
974
995
|
plannedSteps.push(archon.step);
|
|
975
996
|
// #257: after every service above, so a database any of them just
|
|
976
997
|
// recorded is already visible to `recordedIds` on this same plan.
|
|
@@ -982,6 +1003,8 @@ export function planPlatform(platform, live, options = {}) {
|
|
|
982
1003
|
const heartbeats = planHeartbeats(platform, recordedIds, hcReady, blocked.heartbeatsExecutorMissing ?? false, blocked.heartbeatsApiKeyMissing ?? false);
|
|
983
1004
|
if (heartbeats.step)
|
|
984
1005
|
plannedSteps.push(heartbeats.step);
|
|
1006
|
+
if (archon.step && blocked.archonRunsInsideApp)
|
|
1007
|
+
plannedSteps.push(archon.step);
|
|
985
1008
|
// #251: one line per service whose recorded applied ref no longer matches
|
|
986
1009
|
// what the next apply would use (or, for zitadel, what is actually live).
|
|
987
1010
|
// Never pushed into `plannedSteps` -- see `VersionChangeItem`'s own doc
|
|
@@ -2227,9 +2250,12 @@ function planAi(platform, recordedIds, executorBlocked) {
|
|
|
2227
2250
|
* ({@link PLATFORM_ARCHON_CONNECTOR_APP_SCOPE_KEY}) plans the step again, as
|
|
2228
2251
|
* {@link planAuth} does.
|
|
2229
2252
|
*/
|
|
2230
|
-
function planArchon(platform, recordedIds, executorBlocked, authReady, drifted) {
|
|
2253
|
+
function planArchon(platform, recordedIds, executorBlocked, authReady, drifted, deployRunning) {
|
|
2231
2254
|
if (!platform.archon.enabled)
|
|
2232
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 };
|
|
2233
2259
|
const key = resourceKey('coolify', 'app', PLATFORM_ARCHON_KEY);
|
|
2234
2260
|
const applied = Boolean(recordedIds[key]) &&
|
|
2235
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.
|
|
@@ -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
|
|
@@ -328,6 +330,8 @@ export declare class FoundryPlatformStepExecutor implements PlatformStepExecutor
|
|
|
328
330
|
private executeSfu;
|
|
329
331
|
private coolifyJobs;
|
|
330
332
|
}
|
|
333
|
+
export declare const PLATFORM_AUTH_LOGIN_INTERNAL_NAME = "platform-auth-login";
|
|
334
|
+
export declare const AUTH_LOGIN_PORT = "3000";
|
|
331
335
|
/**
|
|
332
336
|
* Every ZITADEL OIDC/management/console/asset path -- `deploy/proxy/Caddyfile`'s
|
|
333
337
|
* own explicit list, restated here as one regex since the tunnel routes by a
|
|
@@ -474,7 +478,7 @@ export interface PlatformMailRuntime {
|
|
|
474
478
|
* {@link PlatformStepExecutionContext}, bundled like {@link PlatformAuthDeps}.
|
|
475
479
|
*/
|
|
476
480
|
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'>;
|
|
481
|
+
coolify: Pick<CoolifyPort, 'findProjectByName' | 'createProject' | 'findOrCreateEnvironment' | 'listServers' | 'findApplicationByName' | 'getApplication' | 'requireGithubApp' | 'createGithubAppApplication' | 'createDockerfileApplication' | 'listDatabases' | 'createPostgresDatabase' | 'getDatabase' | 'raw' | 'bulkUpsertEnvs' | 'listEnvs' | 'deleteEnv' | 'updateApplication' | 'deploy' | 'deployAndWait' | 'getDeployment'>;
|
|
478
482
|
/** F4.dns: every DNS record call, through edge at its recorded address (see {@link PlatformDnsFactory}). */
|
|
479
483
|
dns: PlatformDnsFactory;
|
|
480
484
|
/** F4.tunnels: every tunnel call, through edge at its recorded address (see {@link PlatformTunnelsFactory}). */
|
|
@@ -493,6 +497,15 @@ export interface PlatformArchonDeps {
|
|
|
493
497
|
* Access app nobody can pass.
|
|
494
498
|
*/
|
|
495
499
|
accessEmails: readonly string[];
|
|
500
|
+
/**
|
|
501
|
+
* True when the host process runs inside the Coolify app this step
|
|
502
|
+
* redeploys (Archon's console is `platform:archon`). A deploy that waits
|
|
503
|
+
* would be killed with the old container, so the step then records every
|
|
504
|
+
* applied marker first, and triggers the deploy last, without waiting.
|
|
505
|
+
* The planner puts the step last in the plan. Next plan: the step runs
|
|
506
|
+
* again only if the app is not running (`archonRedeployFailed`).
|
|
507
|
+
*/
|
|
508
|
+
runsInsideApp?: boolean;
|
|
496
509
|
/**
|
|
497
510
|
* Reads a secret an earlier step handed to the host through
|
|
498
511
|
* `deliverSecret`, or `null` when none is held under `ref` yet -- the same
|
|
@@ -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_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';
|
|
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_DEPLOYMENT_SCOPE_KEY, 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`). */
|
|
@@ -318,6 +318,18 @@ export class FoundryPlatformStepExecutor {
|
|
|
318
318
|
if (!apiKey)
|
|
319
319
|
blocked.heartbeatsApiKeyMissing = true;
|
|
320
320
|
}
|
|
321
|
+
// Not a block: tells the planner to order Archon's step last (`PlanPlatformOptions.blocked.archonRunsInsideApp`).
|
|
322
|
+
if (this.archon?.runsInsideApp) {
|
|
323
|
+
blocked.archonRunsInsideApp = true;
|
|
324
|
+
// A deploy the step triggered and did not wait for: while it runs, a
|
|
325
|
+
// second deploy must not be planned.
|
|
326
|
+
const deployment = recordedIds[PLATFORM_ARCHON_DEPLOYMENT_SCOPE_KEY];
|
|
327
|
+
if (platform.archon.enabled &&
|
|
328
|
+
deployment &&
|
|
329
|
+
(await archonDeploymentState(this.archon.coolify, deployment)) === 'running') {
|
|
330
|
+
blocked.archonDeployRunning = `Archon is blocked, not planned: its deployment ${deployment} is still running; plan again when it has finished`;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
321
333
|
return blocked;
|
|
322
334
|
}
|
|
323
335
|
/**
|
|
@@ -347,7 +359,18 @@ export class FoundryPlatformStepExecutor {
|
|
|
347
359
|
!brokerDrifted &&
|
|
348
360
|
!bootstrapOperatorDrifted &&
|
|
349
361
|
(await archonRollingUpdateDrifted(this.archon.coolify, recordedIds[`coolify:app:${PLATFORM_ARCHON_KEY}`]));
|
|
350
|
-
|
|
362
|
+
// The deployment the step triggered failed or Coolify does not know it:
|
|
363
|
+
// plan the step again, which deploys again. A finished one is confirmed
|
|
364
|
+
// by the next run of the step (keys-synced), see `provisionPlatformArchon`.
|
|
365
|
+
const deployment = recordedIds[PLATFORM_ARCHON_DEPLOYMENT_SCOPE_KEY];
|
|
366
|
+
const redeployFailed = this.archon.runsInsideApp === true &&
|
|
367
|
+
Boolean(deployment) &&
|
|
368
|
+
(await archonDeploymentState(this.archon.coolify, deployment)) === 'failed';
|
|
369
|
+
if (accessDrifted ||
|
|
370
|
+
brokerDrifted ||
|
|
371
|
+
bootstrapOperatorDrifted ||
|
|
372
|
+
rollingUpdateDrifted ||
|
|
373
|
+
redeployFailed)
|
|
351
374
|
result.archon = true;
|
|
352
375
|
}
|
|
353
376
|
if (platform.auth.enabled && this.auth) {
|
|
@@ -672,7 +695,7 @@ const PLATFORM_AUTH_BROKER_APP_NAME = 'platform-auth-broker';
|
|
|
672
695
|
* header and so cannot give two different apps the same public hostname.
|
|
673
696
|
*/
|
|
674
697
|
const PLATFORM_AUTH_ZITADEL_INTERNAL_NAME = 'platform-auth-zitadel';
|
|
675
|
-
const PLATFORM_AUTH_LOGIN_INTERNAL_NAME = 'platform-auth-login';
|
|
698
|
+
export const PLATFORM_AUTH_LOGIN_INTERNAL_NAME = 'platform-auth-login';
|
|
676
699
|
const PLATFORM_AUTH_BROKER_INTERNAL_NAME = 'platform-auth-broker';
|
|
677
700
|
const PLATFORM_AUTH_TUNNEL_NAME = 'platform-auth-tunnel';
|
|
678
701
|
/** #197: the one Coolify app that runs `cloudflared` for {@link PLATFORM_AUTH_TUNNEL_NAME}. */
|
|
@@ -691,7 +714,7 @@ const AUTH_BROKER_DOCKERFILE = '/broker/Dockerfile';
|
|
|
691
714
|
* own `ZITADEL_PORT: 8080` and the login image's own `EXPOSE`.
|
|
692
715
|
*/
|
|
693
716
|
const AUTH_ZITADEL_PORT = '8080';
|
|
694
|
-
const AUTH_LOGIN_PORT = '3000';
|
|
717
|
+
export const AUTH_LOGIN_PORT = '3000';
|
|
695
718
|
/** `broker/Dockerfile`'s own `ENV PORT=3000` / `EXPOSE 3000`. */
|
|
696
719
|
const AUTH_BROKER_PORT = '3000';
|
|
697
720
|
/**
|
|
@@ -2600,6 +2623,7 @@ async function provisionPlatformArchon(context, deps) {
|
|
|
2600
2623
|
const appliedGitRef = recorded[PLATFORM_ARCHON_APPLIED_GIT_REF_SCOPE_KEY];
|
|
2601
2624
|
const runtime = deps.runtime ?? {};
|
|
2602
2625
|
const sleep = runtime.sleep ?? defaultSleep;
|
|
2626
|
+
const selfRedeploy = deps.runsInsideApp === true;
|
|
2603
2627
|
if (deps.accessEmails.length === 0) {
|
|
2604
2628
|
throw new Error('platform:archon: no Access emails given; refusing to put Archon behind an Access app nobody can pass');
|
|
2605
2629
|
}
|
|
@@ -3030,7 +3054,17 @@ async function provisionPlatformArchon(context, deps) {
|
|
|
3030
3054
|
// patch above actually takes effect on the running container, not just on
|
|
3031
3055
|
// Coolify's stored config.
|
|
3032
3056
|
const rollingUpdateApplied = recorded[PLATFORM_ARCHON_APPLIED_ROLLING_UPDATE_SCOPE_KEY] === '1';
|
|
3033
|
-
|
|
3057
|
+
// The deployment an earlier run triggered without waiting: read before
|
|
3058
|
+
// deciding whether to deploy again.
|
|
3059
|
+
const priorDeployment = selfRedeploy ? recorded[PLATFORM_ARCHON_DEPLOYMENT_SCOPE_KEY] : undefined;
|
|
3060
|
+
const deployState = priorDeployment
|
|
3061
|
+
? await archonDeploymentState(deps.coolify, priorDeployment)
|
|
3062
|
+
: 'none';
|
|
3063
|
+
if (deployState === 'running' || deployState === 'unreadable') {
|
|
3064
|
+
throw new Error(`platform:archon: the deployment ${priorDeployment} cannot be confirmed yet (${deployState === 'running' ? 'still running' : 'status unreadable'}); plan again later`);
|
|
3065
|
+
}
|
|
3066
|
+
const archonDeployedNow = (keyUnsynced && deployState !== 'finished') ||
|
|
3067
|
+
deployState === 'failed' ||
|
|
3034
3068
|
app.justCreated ||
|
|
3035
3069
|
!app.running ||
|
|
3036
3070
|
changed ||
|
|
@@ -3039,7 +3073,7 @@ async function provisionPlatformArchon(context, deps) {
|
|
|
3039
3073
|
appliedGitRef !== gitRef ||
|
|
3040
3074
|
!rollingUpdateApplied ||
|
|
3041
3075
|
recorded[PLATFORM_ARCHON_APPLIED_HOSTNAME_SCOPE_KEY] !== hostname;
|
|
3042
|
-
if (archonDeployedNow) {
|
|
3076
|
+
if (archonDeployedNow && !selfRedeploy) {
|
|
3043
3077
|
// #467: cleared before the deploy below is even attempted, not after --
|
|
3044
3078
|
// a deploy that then fails, or a crash before `waitForHealthyVersion`
|
|
3045
3079
|
// passes, must leave this run's attempt unconfirmed. Recording it only
|
|
@@ -3111,13 +3145,14 @@ async function provisionPlatformArchon(context, deps) {
|
|
|
3111
3145
|
// Coolify's own deployment record finished, which is what actually closes
|
|
3112
3146
|
// the loophole named in #252 (an old container staying healthy while the
|
|
3113
3147
|
// deployment itself fails).
|
|
3114
|
-
if (archonDeployedNow) {
|
|
3148
|
+
if (archonDeployedNow && !selfRedeploy) {
|
|
3115
3149
|
await waitForHealthyVersion(appId, { kind: 'ready', url: `https://${hostname}/api/health` }, runtime, sleep, 'platform:archon', `archon stays at ${appliedGitRef ?? 'no recorded ref'} on ${recorded[PLATFORM_ARCHON_APPLIED_HOSTNAME_SCOPE_KEY] ?? 'no recorded hostname'}`);
|
|
3116
3150
|
}
|
|
3117
3151
|
// #398: the deployment finished and answers health, so Archon now runs with
|
|
3118
3152
|
// the key id its env carries. `provisionPlatformIntegrations` drops the
|
|
3119
3153
|
// previous public key only on this marker.
|
|
3120
|
-
|
|
3154
|
+
// With `runsInsideApp` only once Coolify's deployment record says it finished.
|
|
3155
|
+
if (keyUnsynced && integrationsKeyId && (!selfRedeploy || deployState === 'finished')) {
|
|
3121
3156
|
await record(PLATFORM_ARCHON_INTEGRATIONS_KEY_SYNCED_SCOPE_KEY, integrationsKeyId);
|
|
3122
3157
|
}
|
|
3123
3158
|
// #475: confirmed only now, after the health check above has passed on a
|
|
@@ -3140,6 +3175,36 @@ async function provisionPlatformArchon(context, deps) {
|
|
|
3140
3175
|
// it.
|
|
3141
3176
|
await record(PLATFORM_ARCHON_APPLIED_BROKER_SCOPE_KEY, brokerAppId);
|
|
3142
3177
|
await record(PLATFORM_ARCHON_APPLIED_OPERATOR_SCOPE_KEY, bootstrapOperator ?? 'none');
|
|
3178
|
+
// The host runs inside this app: every marker is recorded above, so the
|
|
3179
|
+
// deploy goes last and is not waited for -- the old container stops during
|
|
3180
|
+
// it, and the response, the lease and the audit row must be done first.
|
|
3181
|
+
if (selfRedeploy && !archonDeployedNow && deployState === 'finished') {
|
|
3182
|
+
await record(PLATFORM_ARCHON_DEPLOYMENT_SCOPE_KEY, ''); // confirmed above
|
|
3183
|
+
}
|
|
3184
|
+
if (selfRedeploy && archonDeployedNow) {
|
|
3185
|
+
await record(`coolify:app:${PLATFORM_ARCHON_KEY}`, appId);
|
|
3186
|
+
await record(PLATFORM_ARCHON_DEPLOYMENT_SCOPE_KEY, 'pending');
|
|
3187
|
+
let deploymentUuid;
|
|
3188
|
+
try {
|
|
3189
|
+
// `force` takes the branch that always creates a real deployment record
|
|
3190
|
+
// (a skipped one answers with a uuid Coolify never stored).
|
|
3191
|
+
const started = (await deps.coolify.deploy(appId, true));
|
|
3192
|
+
const uuid = Array.isArray(started) ? started[0]?.deployment_uuid : undefined;
|
|
3193
|
+
if (typeof uuid !== 'string' || !uuid) {
|
|
3194
|
+
throw new Error('platform:archon: Coolify answered the deploy with no deployment_uuid');
|
|
3195
|
+
}
|
|
3196
|
+
deploymentUuid = uuid;
|
|
3197
|
+
}
|
|
3198
|
+
catch (error) {
|
|
3199
|
+
// Not triggered: forget the applied ref so the next plan runs the step again.
|
|
3200
|
+
await record(PLATFORM_ARCHON_APPLIED_GIT_REF_SCOPE_KEY, '');
|
|
3201
|
+
await record(PLATFORM_ARCHON_DEPLOYMENT_SCOPE_KEY, '');
|
|
3202
|
+
throw error;
|
|
3203
|
+
}
|
|
3204
|
+
// The next plan reads this deployment's status; a kill before this write
|
|
3205
|
+
// leaves `pending`, which that plan reads as unconfirmed and plans again.
|
|
3206
|
+
await record(PLATFORM_ARCHON_DEPLOYMENT_SCOPE_KEY, deploymentUuid);
|
|
3207
|
+
}
|
|
3143
3208
|
return { resourceId: appId };
|
|
3144
3209
|
}
|
|
3145
3210
|
// #211 round 6: mirrors `wtfalch/auth/broker/src/apps.ts`'s own `isValidFrom`
|
|
@@ -3560,6 +3625,28 @@ async function archonBootstrapOperatorDrifted(coolify, appId, recordedIds) {
|
|
|
3560
3625
|
* version of this check never actually fired. `custom_network_aliases` and
|
|
3561
3626
|
* `health_check_enabled` are both top-level there.
|
|
3562
3627
|
*/
|
|
3628
|
+
/**
|
|
3629
|
+
* The state of the deployment Archon's step triggered without waiting
|
|
3630
|
+
* (`PlatformArchonDeps.runsInsideApp`), from Coolify's own deployment record:
|
|
3631
|
+
* `running` (queued or in progress), `finished`, `failed` (failed, cancelled,
|
|
3632
|
+
* its uuid never recorded, or unknown to Coolify -- a 404), or `unreadable`
|
|
3633
|
+
* for any other error. Status and a served request cannot tell a failed build
|
|
3634
|
+
* from a success (the old container keeps serving), so this record is the
|
|
3635
|
+
* only honest answer.
|
|
3636
|
+
*/
|
|
3637
|
+
async function archonDeploymentState(coolify, deploymentUuid) {
|
|
3638
|
+
if (deploymentUuid === 'pending')
|
|
3639
|
+
return 'failed';
|
|
3640
|
+
try {
|
|
3641
|
+
const { status } = await coolify.getDeployment(deploymentUuid);
|
|
3642
|
+
if (status === 'queued' || status === 'in_progress')
|
|
3643
|
+
return 'running';
|
|
3644
|
+
return status === 'finished' ? 'finished' : 'failed';
|
|
3645
|
+
}
|
|
3646
|
+
catch (error) {
|
|
3647
|
+
return isNotFoundError(error) ? 'failed' : 'unreadable';
|
|
3648
|
+
}
|
|
3649
|
+
}
|
|
3563
3650
|
async function archonRollingUpdateDrifted(coolify, appId) {
|
|
3564
3651
|
if (!appId)
|
|
3565
3652
|
return false;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wtfalch/foundry",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.1",
|
|
4
4
|
"description": "Foundry's SDK: the declared org's types, the plan and apply contracts, and the state store's interface.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"calls-sfu:deploy": "node scripts/calls-sfu-deploy.mjs"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
|
+
"@wtfalch/providers": ">=0.7.4",
|
|
40
41
|
"drizzle-orm": ">=0.45.2"
|
|
41
42
|
},
|
|
42
43
|
"dependencies": {
|
|
@@ -44,7 +45,7 @@
|
|
|
44
45
|
"@wtfalch/auth": "0.16.0",
|
|
45
46
|
"@wtfalch/edge": "0.9.0",
|
|
46
47
|
"@wtfalch/email": "^0.17.0",
|
|
47
|
-
"@wtfalch/providers": "0.7.
|
|
48
|
+
"@wtfalch/providers": "0.7.5",
|
|
48
49
|
"ssh2": "1.17.0",
|
|
49
50
|
"zod": "4.6.5"
|
|
50
51
|
},
|