@wtfalch/foundry 0.19.0 → 0.20.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 +8 -6
- package/dist/index.d.ts +5 -3
- package/dist/index.js +3 -2
- package/dist/plan/agora.d.ts +11 -0
- package/dist/plan/agora.js +13 -0
- package/dist/plan/chat-matrix.d.ts +3 -0
- package/dist/plan/chat-matrix.js +2 -0
- package/dist/plan/cloudflared-connector.d.ts +3 -2
- package/dist/plan/cloudflared-connector.js +2 -2
- package/dist/plan/diff.js +26 -5
- package/dist/plan/edge-access.d.ts +68 -0
- package/dist/plan/edge-access.js +123 -0
- package/dist/plan/edge-dns.d.ts +89 -0
- package/dist/plan/edge-dns.js +227 -0
- package/dist/plan/edge-ratelimits.d.ts +54 -0
- package/dist/plan/edge-ratelimits.js +79 -0
- package/dist/plan/edge-tunnels.d.ts +44 -0
- package/dist/plan/edge-tunnels.js +108 -0
- package/dist/plan/edge-workers.d.ts +31 -0
- package/dist/plan/edge-workers.js +165 -0
- package/dist/plan/edge-zones.d.ts +37 -0
- package/dist/plan/edge-zones.js +64 -0
- package/dist/plan/entry-edge-key.d.ts +10 -0
- package/dist/plan/entry-edge-key.js +38 -8
- package/dist/plan/executor.d.ts +124 -143
- package/dist/plan/executor.js +288 -89
- package/dist/plan/index.d.ts +15 -3
- package/dist/plan/index.js +8 -2
- package/dist/plan/plan-apply.js +5 -1
- package/dist/plan/platform-diff.d.ts +9 -0
- package/dist/plan/platform-diff.js +32 -8
- package/dist/plan/platform-providers.d.ts +53 -6
- package/dist/plan/platform-providers.js +87 -44
- package/dist/plan/providers-readers.d.ts +23 -1
- package/dist/plan/providers-readers.js +29 -0
- package/package.json +2 -1
package/dist/plan/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export { computePlanDetail, toPlan } from './diff.js';
|
|
|
5
5
|
export type { StepSystem } from './step-system.js';
|
|
6
6
|
export { stepSystemOf } from './step-system.js';
|
|
7
7
|
export type { PlatformPlanDetail } from './platform-diff.js';
|
|
8
|
-
export { planPlatform, toPlatformPlan, JOBS_SCHEDULER_KEY, PLATFORM_AI_KEY, PLATFORM_AI_INTERNAL_URL_SCOPE_KEY, PLATFORM_AI_MANAGEMENT_KEY_SCOPE_KEY, PLATFORM_AI_ORGANISATION_SCOPE_KEY, PLATFORM_AUTH_KEY, PLATFORM_FILES_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, PLATFORM_FILES_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_MAIL_KEY, PLATFORM_MAIL_INTERNAL_URL_SCOPE_KEY, PLATFORM_MAIL_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_EDGE_KEY, PLATFORM_EDGE_KEY_REF, PLATFORM_EDGE_CLOUDFLARE_TOKEN_REF, } from './platform-diff.js';
|
|
8
|
+
export { planPlatform, toPlatformPlan, JOBS_SCHEDULER_KEY, PLATFORM_AI_KEY, PLATFORM_AI_INTERNAL_URL_SCOPE_KEY, PLATFORM_AI_MANAGEMENT_KEY_SCOPE_KEY, PLATFORM_AI_ORGANISATION_SCOPE_KEY, PLATFORM_AUTH_KEY, PLATFORM_FILES_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, PLATFORM_FILES_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_MAIL_KEY, PLATFORM_MAIL_INTERNAL_URL_SCOPE_KEY, PLATFORM_MAIL_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_EDGE_KEY, PLATFORM_EDGE_INTERNAL_URL_SCOPE_KEY, PLATFORM_EDGE_KEY_REF, PLATFORM_EDGE_CLOUDFLARE_TOKEN_REF, } from './platform-diff.js';
|
|
9
9
|
export type { HeldKeyResolver, OrgPlatformReadiness } from './platform-org.js';
|
|
10
10
|
export { DEFAULT_ORG_PLATFORM_READINESS, ORG_PLATFORM_KEY_REFS, agoraHostValuesFromPlatform, heldPlatformKeyRefs, orgPlatformReadinessFrom, } from './platform-org.js';
|
|
11
11
|
export type { PlatformOrgClientFactories } from './platform-org-ports.js';
|
|
@@ -15,7 +15,7 @@ export { AdoptRefusedError } from './adopt.js';
|
|
|
15
15
|
export type { AuditActor, AuditLog, AuthzGate, StepExecutor, StepDestroyer, DestroyTarget, StepExecutionContext, PlatformStepExecutor, PlatformStepExecutionContext, FoundryPlanApplyOptions, } from './plan-apply.js';
|
|
16
16
|
export { FoundryPlanApply, DeclaredOrgNotFoundError, StalePlanError, ConfirmationRequiredError, NotAuthorizedError, NotATestOrgError, DeclaredPlatformNotFoundError, PlatformStalePlanError, PlatformConfirmationRequiredError, planNeedsConfirmation, } from './plan-apply.js';
|
|
17
17
|
export { FoundryPlatformStepExecutor, ProvidersPlatformCloudflareReader, ProvidersPlatformCoolifyReader, PLATFORM_MAIL_ADMIN_KEY_DESCRIPTION, filesPublicHostsValue, filesShareDomains, } from './platform-providers.js';
|
|
18
|
-
export type { PlatformAuthDeps, PlatformAuthRuntime, PlatformFilesDeps, PlatformFilesRuntime, PlatformEdgeDeps, PlatformEdgeRuntime, } from './platform-providers.js';
|
|
18
|
+
export type { PlatformAuthDeps, PlatformAuthRuntime, PlatformFilesDeps, PlatformFilesRuntime, PlatformEdgeDeps, PlatformDnsFactory, PlatformAccessFactory, PlatformAccessPort, PlatformTunnelsFactory, PlatformEdgeRuntime, } from './platform-providers.js';
|
|
19
19
|
export type { PlatformMailDeps, PlatformMailAdminPort, PlatformMailRuntime, } from './platform-providers.js';
|
|
20
20
|
export { FILES_INTROSPECTION_SECRET_REF, FILES_MANAGEMENT_ISSUER, FILES_MANAGEMENT_KEY_ID, FILES_MANAGEMENT_KEY_REF, PLATFORM_AUTH_INSTANCE_BEARER_REF, } from './platform-providers.js';
|
|
21
21
|
export { AI_MANAGEMENT_ISSUER, AI_MANAGEMENT_KEY_ID, AI_SIGNING_KEY_REF, AiManagementKeyExistsError, createAiManagementKeyMinter, PLATFORM_AI_MANAGEMENT_KEY_REF, PLATFORM_AI_MANAGEMENT_PERMISSIONS, } from './platform-providers.js';
|
|
@@ -27,7 +27,7 @@ export { MissingCallsSfuConnectionError, applyCallsSfuPlatformStep } from './pla
|
|
|
27
27
|
export type { MailCopyJob, MailCopyPort, MailCopyStepResult } from './mail-copy.js';
|
|
28
28
|
export { MAIL_COPY_COMPLETED_SCOPE_KEY, MAIL_COPY_DONE_SCOPE_KEY, MissingMailCopyAccountsError, MissingMailCopySourceTokenError, MissingMailCopyStalwartTokenError, parseMailCopyCompleted, runMailCopyStep, } from './mail-copy.js';
|
|
29
29
|
export type { ScopeResolver, RecordedIds } from './providers-readers.js';
|
|
30
|
-
export { ProvidersCloudflareReader, ProvidersCmsReader, ProvidersCoolifyReader, ProvidersZitadelReader, storeScopeResolver, storeRecordedIds, } from './providers-readers.js';
|
|
30
|
+
export { ProvidersAiReader, ProvidersCloudflareReader, ProvidersCmsReader, ProvidersCoolifyReader, ProvidersZitadelReader, storeScopeResolver, storeRecordedIds, } from './providers-readers.js';
|
|
31
31
|
export type { AgoraHostPort, AgoraHostValues } from './agora.js';
|
|
32
32
|
export type { BrokerPort } from './broker-entries.js';
|
|
33
33
|
export { PlatformBrokerPort } from './broker-entries.js';
|
|
@@ -39,6 +39,18 @@ export type { MailAllowlistApp } from './mail-identity-allowlist.js';
|
|
|
39
39
|
export { checkMailAppsAllowlist } from './mail-identity-allowlist.js';
|
|
40
40
|
export type { CoolifyPort, CloudflarePort, ZitadelPort, CallsSfuPort, JobsSchedulerPort, FilesOrganisationPort, AiOrganisationPort, FoundryStepExecutorPorts, PlatformOrgPortSource, } from './executor.js';
|
|
41
41
|
export { FoundryStepExecutor, NotBuiltError, healthUrls } from './executor.js';
|
|
42
|
+
export { createEdgeDns, EDGE_DNS_WRITE_CONCURRENCY } from './edge-dns.js';
|
|
43
|
+
export type { DnsPort, DnsRecordInput, DnsRecordSummary, EdgePlatformDns } from './edge-dns.js';
|
|
44
|
+
export { createEdgeAccess } from './edge-access.js';
|
|
45
|
+
export type { AccessAppSummary, AccessDecision, AccessPolicyRule, AccessPort, EdgePlatformAccess, } from './edge-access.js';
|
|
46
|
+
export { createEdgeWorkers } from './edge-workers.js';
|
|
47
|
+
export type { EdgePlatformWorkers, EdgeWorkersOptions } from './edge-workers.js';
|
|
48
|
+
export { createEdgeRateLimits } from './edge-ratelimits.js';
|
|
49
|
+
export type { EdgePlatformRateLimits, EdgeRateLimitsOptions, RateLimitPort, RateLimitRuleInput, } from './edge-ratelimits.js';
|
|
50
|
+
export { createEdgeZones } from './edge-zones.js';
|
|
51
|
+
export type { EdgePlatformZones, EdgeZonesOptions, ZonePort } from './edge-zones.js';
|
|
52
|
+
export { createEdgeTunnels } from './edge-tunnels.js';
|
|
53
|
+
export type { EdgePlatformTunnels, TunnelIngress, TunnelPort, TunnelSummary, } from './edge-tunnels.js';
|
|
42
54
|
export type { ZitadelListAppsRawApp } from './executor.js';
|
|
43
55
|
export { adaptZitadelListApps } from './executor.js';
|
|
44
56
|
export { HttpJobsSchedulerClient } from './jobs-scheduler-http.js';
|
package/dist/plan/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { readLiveResources, readLivePlatformResources } from './live.js';
|
|
2
2
|
export { computePlanDetail, toPlan } from './diff.js';
|
|
3
3
|
export { stepSystemOf } from './step-system.js';
|
|
4
|
-
export { planPlatform, toPlatformPlan, JOBS_SCHEDULER_KEY, PLATFORM_AI_KEY, PLATFORM_AI_INTERNAL_URL_SCOPE_KEY, PLATFORM_AI_MANAGEMENT_KEY_SCOPE_KEY, PLATFORM_AI_ORGANISATION_SCOPE_KEY, PLATFORM_AUTH_KEY, PLATFORM_FILES_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, PLATFORM_FILES_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_MAIL_KEY, PLATFORM_MAIL_INTERNAL_URL_SCOPE_KEY, PLATFORM_MAIL_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_EDGE_KEY, PLATFORM_EDGE_KEY_REF, PLATFORM_EDGE_CLOUDFLARE_TOKEN_REF, } from './platform-diff.js';
|
|
4
|
+
export { planPlatform, toPlatformPlan, JOBS_SCHEDULER_KEY, PLATFORM_AI_KEY, PLATFORM_AI_INTERNAL_URL_SCOPE_KEY, PLATFORM_AI_MANAGEMENT_KEY_SCOPE_KEY, PLATFORM_AI_ORGANISATION_SCOPE_KEY, PLATFORM_AUTH_KEY, PLATFORM_FILES_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, PLATFORM_FILES_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_MAIL_KEY, PLATFORM_MAIL_INTERNAL_URL_SCOPE_KEY, PLATFORM_MAIL_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_EDGE_KEY, PLATFORM_EDGE_INTERNAL_URL_SCOPE_KEY, PLATFORM_EDGE_KEY_REF, PLATFORM_EDGE_CLOUDFLARE_TOKEN_REF, } from './platform-diff.js';
|
|
5
5
|
export { DEFAULT_ORG_PLATFORM_READINESS, ORG_PLATFORM_KEY_REFS, agoraHostValuesFromPlatform, heldPlatformKeyRefs, orgPlatformReadinessFrom, } from './platform-org.js';
|
|
6
6
|
export { PlatformOrgBlockedError, PlatformOrgPorts } from './platform-org-ports.js';
|
|
7
7
|
export { AdoptRefusedError } from './adopt.js';
|
|
@@ -14,11 +14,17 @@ export { createZitadelAdminMinter } from './zitadel-machine-user.js';
|
|
|
14
14
|
export { createZitadelProjectRoles } from './zitadel-project-roles.js';
|
|
15
15
|
export { MissingCallsSfuConnectionError, applyCallsSfuPlatformStep } from './platform-sfu.js';
|
|
16
16
|
export { MAIL_COPY_COMPLETED_SCOPE_KEY, MAIL_COPY_DONE_SCOPE_KEY, MissingMailCopyAccountsError, MissingMailCopySourceTokenError, MissingMailCopyStalwartTokenError, parseMailCopyCompleted, runMailCopyStep, } from './mail-copy.js';
|
|
17
|
-
export { ProvidersCloudflareReader, ProvidersCmsReader, ProvidersCoolifyReader, ProvidersZitadelReader, storeScopeResolver, storeRecordedIds, } from './providers-readers.js';
|
|
17
|
+
export { ProvidersAiReader, ProvidersCloudflareReader, ProvidersCmsReader, ProvidersCoolifyReader, ProvidersZitadelReader, storeScopeResolver, storeRecordedIds, } from './providers-readers.js';
|
|
18
18
|
export { PlatformBrokerPort } from './broker-entries.js';
|
|
19
19
|
export { loginHostnameFor } from './login-host.js';
|
|
20
20
|
export { checkMailIdentityAudience } from './mail-identity.js';
|
|
21
21
|
export { checkMailAppsAllowlist } from './mail-identity-allowlist.js';
|
|
22
22
|
export { FoundryStepExecutor, NotBuiltError, healthUrls } from './executor.js';
|
|
23
|
+
export { createEdgeDns, EDGE_DNS_WRITE_CONCURRENCY } from './edge-dns.js';
|
|
24
|
+
export { createEdgeAccess } from './edge-access.js';
|
|
25
|
+
export { createEdgeWorkers } from './edge-workers.js';
|
|
26
|
+
export { createEdgeRateLimits } from './edge-ratelimits.js';
|
|
27
|
+
export { createEdgeZones } from './edge-zones.js';
|
|
28
|
+
export { createEdgeTunnels } from './edge-tunnels.js';
|
|
23
29
|
export { adaptZitadelListApps } from './executor.js';
|
|
24
30
|
export { HttpJobsSchedulerClient } from './jobs-scheduler-http.js';
|
package/dist/plan/plan-apply.js
CHANGED
|
@@ -8,7 +8,7 @@ import { AdoptRefusedError, adoptCandidates, adoptedKeysUnreadable, adoptedScope
|
|
|
8
8
|
import { NO_AGORA_HOST_VALUES, readAgoraHost, } from './agora.js';
|
|
9
9
|
import { AI_KEY_UNDELIVERED_SCOPE_KEY, JOBS_REGISTRATION_UNDELIVERED_SCOPE_KEY, computePlanDetail, toPlan, } from './diff.js';
|
|
10
10
|
import { entryAiUndeliveredScopeKey } from './entry-ai-key.js';
|
|
11
|
-
import { entryEdgeRetiredScopeKey, entryEdgeUndeliveredScopeKey } from './entry-edge-key.js';
|
|
11
|
+
import { BOULE_EDGE_ENTRY_KEY, entryEdgeRetiredScopeKey, entryEdgeUndeliveredScopeKey, } from './entry-edge-key.js';
|
|
12
12
|
import { FILES_ROOT_UNDELIVERED_SCOPE_KEY, entryFilesUndeliveredScopeKey, } from './entry-files-key.js';
|
|
13
13
|
import { readLivePlatformResources, readLiveResources, } from './live.js';
|
|
14
14
|
import { lockedHostnames } from './locked-hostnames.js';
|
|
@@ -1376,6 +1376,10 @@ function failedFields(org, detail, completed, serviceIds) {
|
|
|
1376
1376
|
failed.add('hosting.entries.uses.edge');
|
|
1377
1377
|
}
|
|
1378
1378
|
}
|
|
1379
|
+
// F3: Boule's own key, wanted whenever the org has a domain.
|
|
1380
|
+
if (org.domain && serviceIds[entryEdgeUndeliveredScopeKey(BOULE_EDGE_ENTRY_KEY)]) {
|
|
1381
|
+
failed.add('hosting.entries.uses.edge');
|
|
1382
|
+
}
|
|
1379
1383
|
// The same for a jobs registration whose credentials never reached the host.
|
|
1380
1384
|
if (serviceIds[JOBS_REGISTRATION_UNDELIVERED_SCOPE_KEY] &&
|
|
1381
1385
|
!serviceIds[`jobs:registration:${org.id}`]) {
|
|
@@ -665,6 +665,15 @@ export declare const PLATFORM_EDGE_PLATFORM_KEY_SET_SCOPE_KEY = "edge_platform_k
|
|
|
665
665
|
*/
|
|
666
666
|
export declare const PLATFORM_EDGE_APPLIED_HOSTNAME_SCOPE_KEY = "edge_applied_hostname";
|
|
667
667
|
export declare const PLATFORM_EDGE_APPLIED_GIT_REF_SCOPE_KEY = "edge_applied_git_ref";
|
|
668
|
+
/**
|
|
669
|
+
* F4.dns: where edge answers on the Coolify network
|
|
670
|
+
* (`http://<custom_internal_name>:3000`), recorded by the edge step once the
|
|
671
|
+
* host reports healthy there. Edge is the only caller of Cloudflare, so every
|
|
672
|
+
* platform step that writes a DNS record (auth, mail, archon, and edge's own
|
|
673
|
+
* CNAME) reaches edge at this address, before any public hostname exists
|
|
674
|
+
* (the {@link PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY} pattern). Not a secret.
|
|
675
|
+
*/
|
|
676
|
+
export declare const PLATFORM_EDGE_INTERNAL_URL_SCOPE_KEY = "edge_platform_internal_url";
|
|
668
677
|
/**
|
|
669
678
|
* Options for {@link planPlatform}, replacing four positional arguments that
|
|
670
679
|
* shifted on every merge that added a platform service (#186, #187, #188) --
|
|
@@ -741,6 +741,15 @@ export const PLATFORM_EDGE_PLATFORM_KEY_SET_SCOPE_KEY = 'edge_platform_key_set';
|
|
|
741
741
|
*/
|
|
742
742
|
export const PLATFORM_EDGE_APPLIED_HOSTNAME_SCOPE_KEY = 'edge_applied_hostname';
|
|
743
743
|
export const PLATFORM_EDGE_APPLIED_GIT_REF_SCOPE_KEY = 'edge_applied_git_ref';
|
|
744
|
+
/**
|
|
745
|
+
* F4.dns: where edge answers on the Coolify network
|
|
746
|
+
* (`http://<custom_internal_name>:3000`), recorded by the edge step once the
|
|
747
|
+
* host reports healthy there. Edge is the only caller of Cloudflare, so every
|
|
748
|
+
* platform step that writes a DNS record (auth, mail, archon, and edge's own
|
|
749
|
+
* CNAME) reaches edge at this address, before any public hostname exists
|
|
750
|
+
* (the {@link PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY} pattern). Not a secret.
|
|
751
|
+
*/
|
|
752
|
+
export const PLATFORM_EDGE_INTERNAL_URL_SCOPE_KEY = 'edge_platform_internal_url';
|
|
744
753
|
/**
|
|
745
754
|
* #167: the platform's own diff engine, mirroring `diff.ts`'s
|
|
746
755
|
* `computePlanDetail` for the one shared {@link DeclaredPlatform} record
|
|
@@ -782,7 +791,24 @@ export function planPlatform(platform, live, options = {}) {
|
|
|
782
791
|
const sfu = platform.sfu.enabled && sfuBlocked ? null : planSfu(platform);
|
|
783
792
|
if (sfu)
|
|
784
793
|
plannedSteps.push(sfu);
|
|
785
|
-
|
|
794
|
+
// #478, F4.dns: first of the services, ahead of auth. Edge is the only
|
|
795
|
+
// caller of Cloudflare, so auth, mail and archon write their DNS records
|
|
796
|
+
// through it; it needs none of them (a Coolify database and app, its own
|
|
797
|
+
// tunnel and DNS record, a held Cloudflare token) and records the address
|
|
798
|
+
// they reach it at.
|
|
799
|
+
const edge = planEdge(platform, recordedIds, blocked.edge);
|
|
800
|
+
if (edge.step)
|
|
801
|
+
plannedSteps.push(edge.step);
|
|
802
|
+
// Auth, mail and archon would build their tunnels and then fail at the DNS
|
|
803
|
+
// write, so while edge is neither applied nor planned in this plan they are
|
|
804
|
+
// blocked, not planned (rule 2), naming edge as the cause.
|
|
805
|
+
const edgeApplied = Boolean(recordedIds[PLATFORM_EDGE_INTERNAL_URL_SCOPE_KEY]);
|
|
806
|
+
const dnsBlocked = edgeApplied || edge.step
|
|
807
|
+
? undefined
|
|
808
|
+
: (service) => `the platform's ${service} is blocked, not planned: every DNS record is written through edge, and edge is neither applied nor planned (${platform.edge.enabled
|
|
809
|
+
? (edge.blocked ?? 'its step is not planned')
|
|
810
|
+
: 'the platform does not enable edge'})`;
|
|
811
|
+
const auth = planAuth(platform, recordedIds, blocked.auth ?? dnsBlocked?.('sign-in'), blocked.authExecutorMissing ?? false, drifted.auth ?? false, blocked.authOtherInputsMissing ?? []);
|
|
786
812
|
if (auth.step)
|
|
787
813
|
plannedSteps.push(auth.step);
|
|
788
814
|
// #174: the integrations host. Planned after auth; neither step reads the
|
|
@@ -798,21 +824,17 @@ export function planPlatform(platform, live, options = {}) {
|
|
|
798
824
|
plannedSteps.push(files.step);
|
|
799
825
|
// #169: the platform's own mail server. After auth, because its OIDC
|
|
800
826
|
// directory is built against the platform's sign-in.
|
|
801
|
-
const mail = planMail(platform, recordedIds, blocked.mail, authReady);
|
|
827
|
+
const mail = planMail(platform, recordedIds, blocked.mail ?? dnsBlocked?.('mail server'), authReady);
|
|
802
828
|
if (mail.step)
|
|
803
829
|
plannedSteps.push(mail.step);
|
|
804
830
|
// #172: a throw halts every step after it, so this one goes late.
|
|
805
831
|
const ai = planAi(platform, recordedIds, blocked.ai);
|
|
806
832
|
if (ai.step)
|
|
807
833
|
plannedSteps.push(ai.step);
|
|
808
|
-
// #478: independent of auth and files -- the edge host needs neither.
|
|
809
|
-
const edge = planEdge(platform, recordedIds, blocked.edge);
|
|
810
|
-
if (edge.step)
|
|
811
|
-
plannedSteps.push(edge.step);
|
|
812
834
|
// #175: last, on purpose. Archon's sign-in client lives in the sign-in
|
|
813
835
|
// `planAuth` builds, and its env reads what every other service's step
|
|
814
836
|
// records or delivers, so in one apply it runs after all of them.
|
|
815
|
-
const archon = planArchon(platform, recordedIds, blocked.archon, authReady, drifted.archon);
|
|
837
|
+
const archon = planArchon(platform, recordedIds, blocked.archon ?? dnsBlocked?.('Archon'), authReady, drifted.archon);
|
|
816
838
|
if (archon.step)
|
|
817
839
|
plannedSteps.push(archon.step);
|
|
818
840
|
// #257: after every service above, so a database any of them just
|
|
@@ -1780,7 +1802,9 @@ function planEdge(platform, recordedIds, executorBlocked) {
|
|
|
1780
1802
|
}
|
|
1781
1803
|
const applied = Boolean(recordedIds[resourceKey('coolify', 'app', PLATFORM_EDGE_KEY)]) &&
|
|
1782
1804
|
recordedIds[PLATFORM_EDGE_APPLIED_HOSTNAME_SCOPE_KEY] === platform.edge.hostname &&
|
|
1783
|
-
recordedIds[PLATFORM_EDGE_APPLIED_GIT_REF_SCOPE_KEY] === platform.edge.gitRef
|
|
1805
|
+
recordedIds[PLATFORM_EDGE_APPLIED_GIT_REF_SCOPE_KEY] === platform.edge.gitRef &&
|
|
1806
|
+
// F4.dns: an edge applied before its address was recorded runs once more to record it.
|
|
1807
|
+
Boolean(recordedIds[PLATFORM_EDGE_INTERNAL_URL_SCOPE_KEY]);
|
|
1784
1808
|
if (applied)
|
|
1785
1809
|
return { step: null, blocked: null };
|
|
1786
1810
|
if (executorBlocked)
|
|
@@ -3,6 +3,9 @@ import type { DeclaredOrg } from '../org.js';
|
|
|
3
3
|
import type { DeclaredPlatform } from '../platform.js';
|
|
4
4
|
import type { DeclaredOrgStore, PlatformStore } from '../store.js';
|
|
5
5
|
import type { StepAction } from './diff.js';
|
|
6
|
+
import type { AccessPort } from './edge-access.js';
|
|
7
|
+
import type { DnsPort } from './edge-dns.js';
|
|
8
|
+
import type { TunnelPort } from './edge-tunnels.js';
|
|
6
9
|
import type { CloudflarePort, CoolifyPort, FoundryStepExecutorPorts, ZitadelPort } from './executor.js';
|
|
7
10
|
import type { LiveResource, PlatformCloudflareReader, PlatformCoolifyReader } from './live.js';
|
|
8
11
|
import type { PlatformStepExecutionContext, PlatformStepExecutor } from './plan-apply.js';
|
|
@@ -14,6 +17,34 @@ import { type CallsSfuPlatformDeps } from './platform-sfu.js';
|
|
|
14
17
|
* positions, so every #167-era caller of {@link FoundryPlatformStepExecutor}
|
|
15
18
|
* (zone only) keeps compiling with this third argument simply omitted.
|
|
16
19
|
*/
|
|
20
|
+
/**
|
|
21
|
+
* F4.dns: builds the {@link DnsPort} a platform step writes its records
|
|
22
|
+
* through, given the address edge answers at on the Coolify network
|
|
23
|
+
* ({@link PLATFORM_EDGE_INTERNAL_URL_SCOPE_KEY}). The host builds it with
|
|
24
|
+
* `createEdgeDns(createEdgeClient({ baseUrl, key }).platform.dns)`, `key`
|
|
25
|
+
* being the platform key held under {@link PLATFORM_EDGE_KEY_REF}.
|
|
26
|
+
*/
|
|
27
|
+
export type PlatformDnsFactory = (edgeUrl: string) => DnsPort | Promise<DnsPort>;
|
|
28
|
+
/**
|
|
29
|
+
* F4.tunnels: builds the {@link TunnelPort} a platform step creates, configures
|
|
30
|
+
* and reads the connector token of its tunnel through, given the same address
|
|
31
|
+
* as {@link PlatformDnsFactory}. The host builds it with
|
|
32
|
+
* `createEdgeTunnels(createEdgeClient({ baseUrl, key }).platform.tunnels)`,
|
|
33
|
+
* `key` being the platform key held under {@link PLATFORM_EDGE_KEY_REF}.
|
|
34
|
+
*/
|
|
35
|
+
export type PlatformTunnelsFactory = (edgeUrl: string) => PlatformTunnelPort | Promise<PlatformTunnelPort>;
|
|
36
|
+
/** The tunnel calls a platform step makes; it never deletes one. */
|
|
37
|
+
export type PlatformTunnelPort = Pick<TunnelPort, 'findTunnelByName' | 'createTunnel' | 'putTunnelConfig' | 'getTunnelToken'>;
|
|
38
|
+
/**
|
|
39
|
+
* F4.access: builds the {@link AccessPort} a platform step finds, creates and
|
|
40
|
+
* deletes its Access app through, given the same address as
|
|
41
|
+
* {@link PlatformDnsFactory}. The host builds it with
|
|
42
|
+
* `createEdgeAccess(createEdgeClient({ baseUrl, key }).platform.access)`,
|
|
43
|
+
* `key` being the platform key held under {@link PLATFORM_EDGE_KEY_REF}.
|
|
44
|
+
*/
|
|
45
|
+
export type PlatformAccessFactory = (edgeUrl: string) => PlatformAccessPort | Promise<PlatformAccessPort>;
|
|
46
|
+
/** The Access calls a platform step makes. */
|
|
47
|
+
export type PlatformAccessPort = Pick<AccessPort, 'listAccessApps' | 'createAccessApp' | 'listAccessPolicies' | 'createAccessPolicy' | 'deleteAccessApp'>;
|
|
17
48
|
/**
|
|
18
49
|
* The SMTP call `provisionPlatformAuth` makes on the instance's ZITADEL.
|
|
19
50
|
* `@wtfalch/providers` gains it in 0.7.2; stated here so foundry compiles
|
|
@@ -35,9 +66,11 @@ export interface PlatformZitadelSmtp {
|
|
|
35
66
|
}
|
|
36
67
|
export interface PlatformAuthDeps {
|
|
37
68
|
coolify: Pick<CoolifyPort, 'findProjectByName' | 'createProject' | 'findOrCreateEnvironment' | 'listServers' | 'findApplicationByName' | 'getApplication' | 'requireGithubApp' | 'createGithubAppApplication' | 'createDockerImageApplication' | 'createDockerfileApplication' | 'createPostgresDatabase' | 'getDatabase' | 'bulkUpsertEnvs' | 'listEnvs' | 'updateApplication' | 'deploy' | 'raw'>;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
69
|
+
/** F4.dns: every DNS record call, through edge at its recorded address (see {@link PlatformDnsFactory}). */
|
|
70
|
+
dns: PlatformDnsFactory;
|
|
71
|
+
/** F4.tunnels: every tunnel call, through edge at its recorded address (see {@link PlatformTunnelsFactory}). */
|
|
72
|
+
tunnels: PlatformTunnelsFactory;
|
|
73
|
+
cloudflare: Pick<CloudflarePort, 'findZoneByApex'>;
|
|
41
74
|
/** Must target the platform's own new instance (its issuer), or `zitadelForInstance` must be set. */
|
|
42
75
|
zitadel: Pick<ZitadelPort, 'findOrCreateOrg' | 'mintAdminMachineUser' | 'mintLoginClientMachineUser' | 'listLoginClientPats' | 'inviteOrgOwner'> & PlatformZitadelSmtp;
|
|
43
76
|
/**
|
|
@@ -377,7 +410,11 @@ export declare function defaultStalwartForInstance(input: {
|
|
|
377
410
|
*/
|
|
378
411
|
export interface PlatformMailDeps {
|
|
379
412
|
coolify: Pick<CoolifyPort, 'findProjectByName' | 'createProject' | 'findOrCreateEnvironment' | 'listServers' | 'findApplicationByName' | 'getApplication' | 'createDockerImageApplication' | 'createDockerfileApplication' | 'raw' | 'listStorages' | 'createPersistentStorage' | 'bulkUpsertEnvs' | 'listEnvs' | 'updateApplication' | 'deploy' | 'deployAndWait'>;
|
|
380
|
-
|
|
413
|
+
/** F4.dns: every DNS record call, through edge at its recorded address (see {@link PlatformDnsFactory}). */
|
|
414
|
+
dns: PlatformDnsFactory;
|
|
415
|
+
/** F4.tunnels: every tunnel call, through edge at its recorded address (see {@link PlatformTunnelsFactory}). */
|
|
416
|
+
tunnels: PlatformTunnelsFactory;
|
|
417
|
+
cloudflare: Pick<CloudflarePort, 'findZoneByApex'>;
|
|
381
418
|
/**
|
|
382
419
|
* Must target the platform's own ZITADEL instance, or `zitadelForInstance`
|
|
383
420
|
* must be set. `provisionPlatformMail` finds or creates a `mail` project
|
|
@@ -421,7 +458,13 @@ export interface PlatformMailRuntime {
|
|
|
421
458
|
*/
|
|
422
459
|
export interface PlatformArchonDeps {
|
|
423
460
|
coolify: Pick<CoolifyPort, 'findProjectByName' | 'createProject' | 'findOrCreateEnvironment' | 'listServers' | 'findApplicationByName' | 'getApplication' | 'requireGithubApp' | 'createGithubAppApplication' | 'createDockerfileApplication' | 'listDatabases' | 'createPostgresDatabase' | 'getDatabase' | 'raw' | 'bulkUpsertEnvs' | 'listEnvs' | 'deleteEnv' | 'updateApplication' | 'deploy' | 'deployAndWait'>;
|
|
424
|
-
|
|
461
|
+
/** F4.dns: every DNS record call, through edge at its recorded address (see {@link PlatformDnsFactory}). */
|
|
462
|
+
dns: PlatformDnsFactory;
|
|
463
|
+
/** F4.tunnels: every tunnel call, through edge at its recorded address (see {@link PlatformTunnelsFactory}). */
|
|
464
|
+
tunnels: PlatformTunnelsFactory;
|
|
465
|
+
/** F4.access: every Access app call, through edge at its recorded address (see {@link PlatformAccessFactory}). */
|
|
466
|
+
access: PlatformAccessFactory;
|
|
467
|
+
cloudflare: Pick<CloudflarePort, 'findZoneByApex'>;
|
|
425
468
|
/** Must target the platform's own ZITADEL instance, or `zitadelForInstance` must be set. */
|
|
426
469
|
zitadel: Pick<ZitadelPort, 'findOrCreateOrg' | 'findOrCreateProject' | 'createOidcApp' | 'listApps' | 'deleteApp'>;
|
|
427
470
|
/** {@link PlatformAuthDeps.zitadelForInstance}, for Archon's sign-in client. */
|
|
@@ -834,7 +877,11 @@ export interface PlatformEdgeRuntime {
|
|
|
834
877
|
}
|
|
835
878
|
export interface PlatformEdgeDeps {
|
|
836
879
|
coolify: Pick<CoolifyPort, 'findProjectByName' | 'createProject' | 'findOrCreateEnvironment' | 'listServers' | 'findApplicationByName' | 'getApplication' | 'requireGithubApp' | 'createGithubAppApplication' | 'createDockerfileApplication' | 'listDatabases' | 'createPostgresDatabase' | 'getDatabase' | 'bulkUpsertEnvs' | 'listEnvs' | 'updateApplication' | 'deploy' | 'deployAndWait' | 'raw'>;
|
|
837
|
-
|
|
880
|
+
/** F4.dns: every DNS record call, through edge at its recorded address (see {@link PlatformDnsFactory}). */
|
|
881
|
+
dns: PlatformDnsFactory;
|
|
882
|
+
/** F4.tunnels: every tunnel call, through edge at its recorded address (see {@link PlatformTunnelsFactory}). */
|
|
883
|
+
tunnels: PlatformTunnelsFactory;
|
|
884
|
+
cloudflare: Pick<CloudflarePort, 'findZoneByApex'>;
|
|
838
885
|
deliverSecret: FoundryStepExecutorPorts['deliverSecret'];
|
|
839
886
|
/**
|
|
840
887
|
* Reads the edge service's own Cloudflare API token back, held under
|
|
@@ -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_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';
|
|
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_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_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`). */
|
|
@@ -16,6 +16,35 @@ function apexZoneKey(apex) {
|
|
|
16
16
|
}
|
|
17
17
|
/** The key `runPlatformSteps` records the jobs scheduler app's id under (`system:kind:key`, #173). */
|
|
18
18
|
const JOBS_APP_KEY = `coolify:jobs:${JOBS_SCHEDULER_KEY}`;
|
|
19
|
+
/**
|
|
20
|
+
* The DNS port for a platform step: edge's recorded address, never a public
|
|
21
|
+
* hostname, because auth, mail and archon write their records before any of
|
|
22
|
+
* those exist. Edge's step runs first and records the address
|
|
23
|
+
* (`planPlatform`), so a missing one means that step has not finished.
|
|
24
|
+
*/
|
|
25
|
+
async function platformDns(deps, recorded, step) {
|
|
26
|
+
const edgeUrl = recorded[PLATFORM_EDGE_INTERNAL_URL_SCOPE_KEY];
|
|
27
|
+
if (!edgeUrl) {
|
|
28
|
+
throw new Error(`${step}: edge has not been applied yet (no recorded ${PLATFORM_EDGE_INTERNAL_URL_SCOPE_KEY}); platform:edge runs first and every DNS record is written through it`);
|
|
29
|
+
}
|
|
30
|
+
return deps.dns(edgeUrl);
|
|
31
|
+
}
|
|
32
|
+
/** The tunnel port for a platform step; same address and same reason as {@link platformDns}. */
|
|
33
|
+
async function platformTunnels(deps, recorded, step) {
|
|
34
|
+
const edgeUrl = recorded[PLATFORM_EDGE_INTERNAL_URL_SCOPE_KEY];
|
|
35
|
+
if (!edgeUrl) {
|
|
36
|
+
throw new Error(`${step}: edge has not been applied yet (no recorded ${PLATFORM_EDGE_INTERNAL_URL_SCOPE_KEY}); platform:edge runs first and every tunnel is managed through it`);
|
|
37
|
+
}
|
|
38
|
+
return deps.tunnels(edgeUrl);
|
|
39
|
+
}
|
|
40
|
+
/** The Access port for a platform step; same address and same reason as {@link platformDns}. */
|
|
41
|
+
async function platformAccess(deps, recorded, step) {
|
|
42
|
+
const edgeUrl = recorded[PLATFORM_EDGE_INTERNAL_URL_SCOPE_KEY];
|
|
43
|
+
if (!edgeUrl) {
|
|
44
|
+
throw new Error(`${step}: edge has not been applied yet (no recorded ${PLATFORM_EDGE_INTERNAL_URL_SCOPE_KEY}); platform:edge runs first and every Access app is managed through it`);
|
|
45
|
+
}
|
|
46
|
+
return deps.access(edgeUrl);
|
|
47
|
+
}
|
|
19
48
|
/**
|
|
20
49
|
* #173: the jobs scheduler's own Coolify project and database, flat scope
|
|
21
50
|
* ids recorded via `PlatformStepExecutionContext.recordScopeId` -- not
|
|
@@ -271,7 +300,7 @@ export class FoundryPlatformStepExecutor {
|
|
|
271
300
|
if (platform.archon.enabled &&
|
|
272
301
|
this.archon &&
|
|
273
302
|
recordedIds[`coolify:app:${PLATFORM_ARCHON_KEY}`]) {
|
|
274
|
-
const accessDrifted = await archonAccessDrifted(this.archon
|
|
303
|
+
const accessDrifted = await archonAccessDrifted(this.archon, recordedIds, platform.archon.hostname, this.archon.accessEmails);
|
|
275
304
|
const brokerDrifted = !accessDrifted && (await archonBrokerRegistrationDrifted(this.archon.coolify, recordedIds));
|
|
276
305
|
const bootstrapOperatorDrifted = !accessDrifted &&
|
|
277
306
|
!brokerDrifted &&
|
|
@@ -1193,10 +1222,11 @@ async function provisionPlatformAuth(context, deps, owners = []) {
|
|
|
1193
1222
|
await record(PLATFORM_AUTH_BROKER_DEPLOYED_SCOPE_KEY, brokerDeployRecordValue(brokerAppId, authAppsRaw));
|
|
1194
1223
|
}
|
|
1195
1224
|
// ---- tunnel and DNS ------------------------------------------------------
|
|
1196
|
-
const
|
|
1197
|
-
const
|
|
1225
|
+
const tunnels = await platformTunnels(deps, context.recordedIds(), 'platform:auth');
|
|
1226
|
+
const existingTunnel = await tunnels.findTunnelByName(PLATFORM_AUTH_TUNNEL_NAME);
|
|
1227
|
+
const tunnel = existingTunnel ?? (await tunnels.createTunnel(PLATFORM_AUTH_TUNNEL_NAME));
|
|
1198
1228
|
await record(PLATFORM_AUTH_TUNNEL_SCOPE_KEY, tunnel.id);
|
|
1199
|
-
await
|
|
1229
|
+
await tunnels.putTunnelConfig(tunnel.id, {
|
|
1200
1230
|
ingress: [
|
|
1201
1231
|
{
|
|
1202
1232
|
hostname,
|
|
@@ -1221,7 +1251,7 @@ async function provisionPlatformAuth(context, deps, owners = []) {
|
|
|
1221
1251
|
// step's own tunnel only (see this function's doc comment).
|
|
1222
1252
|
await ensureCloudflaredConnector({
|
|
1223
1253
|
coolify: deps.coolify,
|
|
1224
|
-
|
|
1254
|
+
tunnels,
|
|
1225
1255
|
tunnelId: tunnel.id,
|
|
1226
1256
|
appName: PLATFORM_AUTH_CLOUDFLARED_APP_NAME,
|
|
1227
1257
|
projectUuid,
|
|
@@ -1237,9 +1267,10 @@ async function provisionPlatformAuth(context, deps, owners = []) {
|
|
|
1237
1267
|
throw new Error(`platform:auth: no Cloudflare zone for ${platform.apex} to put ${hostname} in`);
|
|
1238
1268
|
}
|
|
1239
1269
|
const dnsTarget = `${tunnel.id}.cfargotunnel.com`;
|
|
1240
|
-
const
|
|
1270
|
+
const dns = await platformDns(deps, context.recordedIds(), 'platform:auth');
|
|
1271
|
+
const [dnsRecord] = await dns.findDnsRecord(platform.apex, hostname, 'CNAME');
|
|
1241
1272
|
if (!dnsRecord) {
|
|
1242
|
-
await
|
|
1273
|
+
await dns.createDnsRecord(platform.apex, {
|
|
1243
1274
|
type: 'CNAME',
|
|
1244
1275
|
name: hostname,
|
|
1245
1276
|
content: dnsTarget,
|
|
@@ -1247,7 +1278,7 @@ async function provisionPlatformAuth(context, deps, owners = []) {
|
|
|
1247
1278
|
});
|
|
1248
1279
|
}
|
|
1249
1280
|
else if (dnsRecord.content !== dnsTarget) {
|
|
1250
|
-
await
|
|
1281
|
+
await dns.updateDnsRecord(platform.apex, dnsRecord.id, {
|
|
1251
1282
|
type: 'CNAME',
|
|
1252
1283
|
name: hostname,
|
|
1253
1284
|
content: dnsTarget,
|
|
@@ -1884,10 +1915,11 @@ async function provisionPlatformMail(context, deps) {
|
|
|
1884
1915
|
await record(PLATFORM_MAIL_DEPLOYED_GIT_REF_SCOPE_KEY, imageTag);
|
|
1885
1916
|
}
|
|
1886
1917
|
// ---- tunnel and DNS: web/JMAP/management ------------------------------------
|
|
1887
|
-
const
|
|
1888
|
-
const
|
|
1918
|
+
const tunnels = await platformTunnels(deps, context.recordedIds(), 'platform:mail');
|
|
1919
|
+
const existingTunnel = await tunnels.findTunnelByName(PLATFORM_MAIL_TUNNEL_NAME);
|
|
1920
|
+
const tunnel = existingTunnel ?? (await tunnels.createTunnel(PLATFORM_MAIL_TUNNEL_NAME));
|
|
1889
1921
|
await record(PLATFORM_MAIL_TUNNEL_SCOPE_KEY, tunnel.id);
|
|
1890
|
-
await
|
|
1922
|
+
await tunnels.putTunnelConfig(tunnel.id, {
|
|
1891
1923
|
ingress: [
|
|
1892
1924
|
{
|
|
1893
1925
|
hostname,
|
|
@@ -1899,7 +1931,7 @@ async function provisionPlatformMail(context, deps) {
|
|
|
1899
1931
|
// #197: without a connector, `hostname` answers with a Cloudflare error.
|
|
1900
1932
|
await ensureCloudflaredConnector({
|
|
1901
1933
|
coolify: deps.coolify,
|
|
1902
|
-
|
|
1934
|
+
tunnels,
|
|
1903
1935
|
tunnelId: tunnel.id,
|
|
1904
1936
|
appName: PLATFORM_MAIL_CLOUDFLARED_APP_NAME,
|
|
1905
1937
|
projectUuid,
|
|
@@ -1914,9 +1946,10 @@ async function provisionPlatformMail(context, deps) {
|
|
|
1914
1946
|
throw new Error(`platform:mail: no Cloudflare zone for ${platform.apex} to put ${hostname} in`);
|
|
1915
1947
|
}
|
|
1916
1948
|
const dnsTarget = `${tunnel.id}.cfargotunnel.com`;
|
|
1917
|
-
const
|
|
1949
|
+
const dns = await platformDns(deps, context.recordedIds(), 'platform:mail');
|
|
1950
|
+
const [dnsRecord] = await dns.findDnsRecord(platform.apex, hostname, 'CNAME');
|
|
1918
1951
|
if (!dnsRecord) {
|
|
1919
|
-
await
|
|
1952
|
+
await dns.createDnsRecord(platform.apex, {
|
|
1920
1953
|
type: 'CNAME',
|
|
1921
1954
|
name: hostname,
|
|
1922
1955
|
content: dnsTarget,
|
|
@@ -1924,7 +1957,7 @@ async function provisionPlatformMail(context, deps) {
|
|
|
1924
1957
|
});
|
|
1925
1958
|
}
|
|
1926
1959
|
else if (dnsRecord.content !== dnsTarget) {
|
|
1927
|
-
await
|
|
1960
|
+
await dns.updateDnsRecord(platform.apex, dnsRecord.id, {
|
|
1928
1961
|
type: 'CNAME',
|
|
1929
1962
|
name: hostname,
|
|
1930
1963
|
content: dnsTarget,
|
|
@@ -1933,9 +1966,9 @@ async function provisionPlatformMail(context, deps) {
|
|
|
1933
1966
|
}
|
|
1934
1967
|
// ---- the MX hostname: a direct, unproxied A record --------------------------
|
|
1935
1968
|
const serverIp = (platform.serverIpv4 ?? server.ip); // checked by mailServerIpBlocked above
|
|
1936
|
-
const [mxRecord] = await
|
|
1969
|
+
const [mxRecord] = await dns.findDnsRecord(platform.apex, mailHost, 'A');
|
|
1937
1970
|
if (!mxRecord) {
|
|
1938
|
-
await
|
|
1971
|
+
await dns.createDnsRecord(platform.apex, {
|
|
1939
1972
|
type: 'A',
|
|
1940
1973
|
name: mailHost,
|
|
1941
1974
|
content: serverIp,
|
|
@@ -1943,7 +1976,7 @@ async function provisionPlatformMail(context, deps) {
|
|
|
1943
1976
|
});
|
|
1944
1977
|
}
|
|
1945
1978
|
else if (mxRecord.content !== serverIp) {
|
|
1946
|
-
await
|
|
1979
|
+
await dns.updateDnsRecord(platform.apex, mxRecord.id, {
|
|
1947
1980
|
type: 'A',
|
|
1948
1981
|
name: mailHost,
|
|
1949
1982
|
content: serverIp,
|
|
@@ -2878,17 +2911,19 @@ async function provisionPlatformArchon(context, deps) {
|
|
|
2878
2911
|
await record(PLATFORM_AUTH_BROKER_DEPLOYED_SCOPE_KEY, brokerDeployRecordValue(brokerAppId, desiredAuthAppsRaw));
|
|
2879
2912
|
}
|
|
2880
2913
|
// ---- Cloudflare Access, before the hostname resolves ----------------------
|
|
2914
|
+
const tunnels = await platformTunnels(deps, context.recordedIds(), 'platform:archon');
|
|
2915
|
+
const access = await platformAccess(deps, context.recordedIds(), 'platform:archon');
|
|
2881
2916
|
let gateClosed = Boolean(recorded[PLATFORM_ARCHON_GATE_CLOSED_SCOPE_KEY]);
|
|
2882
|
-
const accessAppId = await ensureArchonAccess(
|
|
2917
|
+
const accessAppId = await ensureArchonAccess(access, hostname, deps.accessEmails, async () => {
|
|
2883
2918
|
// The Access app is about to be rebuilt: answer 404 for every hostname
|
|
2884
2919
|
// meanwhile, so it is never served without the gate. The marker is
|
|
2885
2920
|
// persisted first: a failure before the real ingress is written back
|
|
2886
2921
|
// leaves it set, and the next plan repairs the step.
|
|
2887
2922
|
gateClosed = true;
|
|
2888
2923
|
await record(PLATFORM_ARCHON_GATE_CLOSED_SCOPE_KEY, '1');
|
|
2889
|
-
const tunnel = await
|
|
2924
|
+
const tunnel = await tunnels.findTunnelByName(PLATFORM_ARCHON_TUNNEL_NAME);
|
|
2890
2925
|
if (tunnel)
|
|
2891
|
-
await
|
|
2926
|
+
await tunnels.putTunnelConfig(tunnel.id, {
|
|
2892
2927
|
ingress: [{ service: 'http_status:404' }],
|
|
2893
2928
|
});
|
|
2894
2929
|
});
|
|
@@ -2940,12 +2975,12 @@ async function provisionPlatformArchon(context, deps) {
|
|
|
2940
2975
|
await waitForDeploymentFinished(deps.coolify, appId, runtime, 'platform:archon', `archon stays at ${appliedGitRef ?? 'no recorded ref'} on ${recorded[PLATFORM_ARCHON_APPLIED_HOSTNAME_SCOPE_KEY] ?? 'no recorded hostname'}`);
|
|
2941
2976
|
}
|
|
2942
2977
|
// ---- tunnel and DNS ---------------------------------------------------------
|
|
2943
|
-
const existingTunnel = await
|
|
2944
|
-
const tunnel = existingTunnel ?? (await
|
|
2978
|
+
const existingTunnel = await tunnels.findTunnelByName(PLATFORM_ARCHON_TUNNEL_NAME);
|
|
2979
|
+
const tunnel = existingTunnel ?? (await tunnels.createTunnel(PLATFORM_ARCHON_TUNNEL_NAME));
|
|
2945
2980
|
if (recorded[PLATFORM_ARCHON_TUNNEL_SCOPE_KEY] !== tunnel.id) {
|
|
2946
2981
|
await record(PLATFORM_ARCHON_TUNNEL_SCOPE_KEY, tunnel.id);
|
|
2947
2982
|
}
|
|
2948
|
-
await
|
|
2983
|
+
await tunnels.putTunnelConfig(tunnel.id, {
|
|
2949
2984
|
ingress: [
|
|
2950
2985
|
{
|
|
2951
2986
|
hostname,
|
|
@@ -2960,7 +2995,7 @@ async function provisionPlatformArchon(context, deps) {
|
|
|
2960
2995
|
// #197: without a connector, `hostname` answers with a Cloudflare error.
|
|
2961
2996
|
await ensureCloudflaredConnector({
|
|
2962
2997
|
coolify: deps.coolify,
|
|
2963
|
-
|
|
2998
|
+
tunnels,
|
|
2964
2999
|
tunnelId: tunnel.id,
|
|
2965
3000
|
appName: PLATFORM_ARCHON_CLOUDFLARED_APP_NAME,
|
|
2966
3001
|
projectUuid,
|
|
@@ -2974,9 +3009,10 @@ async function provisionPlatformArchon(context, deps) {
|
|
|
2974
3009
|
throw new Error(`platform:archon: no Cloudflare zone for ${platform.apex} to put ${hostname} in`);
|
|
2975
3010
|
}
|
|
2976
3011
|
const dnsTarget = `${tunnel.id}.cfargotunnel.com`;
|
|
2977
|
-
const
|
|
3012
|
+
const dns = await platformDns(deps, context.recordedIds(), 'platform:archon');
|
|
3013
|
+
const [dnsRecord] = await dns.findDnsRecord(platform.apex, hostname, 'CNAME');
|
|
2978
3014
|
if (!dnsRecord) {
|
|
2979
|
-
await
|
|
3015
|
+
await dns.createDnsRecord(platform.apex, {
|
|
2980
3016
|
type: 'CNAME',
|
|
2981
3017
|
name: hostname,
|
|
2982
3018
|
content: dnsTarget,
|
|
@@ -2984,7 +3020,7 @@ async function provisionPlatformArchon(context, deps) {
|
|
|
2984
3020
|
});
|
|
2985
3021
|
}
|
|
2986
3022
|
else if (dnsRecord.content !== dnsTarget) {
|
|
2987
|
-
await
|
|
3023
|
+
await dns.updateDnsRecord(platform.apex, dnsRecord.id, {
|
|
2988
3024
|
type: 'CNAME',
|
|
2989
3025
|
name: hostname,
|
|
2990
3026
|
content: dnsTarget,
|
|
@@ -3365,8 +3401,9 @@ function sameEmails(include, emails) {
|
|
|
3365
3401
|
* exactly the declared emails. A failed read is not drift: the next plan reads
|
|
3366
3402
|
* again.
|
|
3367
3403
|
*/
|
|
3368
|
-
async function archonAccessDrifted(
|
|
3404
|
+
async function archonAccessDrifted(deps, recorded, domain, emails) {
|
|
3369
3405
|
try {
|
|
3406
|
+
const cf = await platformAccess(deps, recorded, 'platform:archon');
|
|
3370
3407
|
const app = (await cf.listAccessApps()).find((a) => a.domain === domain && a.name === PLATFORM_ARCHON_ACCESS_APP_NAME);
|
|
3371
3408
|
if (!app)
|
|
3372
3409
|
return true;
|
|
@@ -5359,11 +5396,22 @@ async function provisionPlatformEdge(context, deps) {
|
|
|
5359
5396
|
{ key: 'EDGE_PLATFORM_KEY_HASH', value: keyHash },
|
|
5360
5397
|
...(await missingEnvs(deps.coolify, appId, PLATFORM_EDGE_RUNNER_DEFAULTS)),
|
|
5361
5398
|
]);
|
|
5362
|
-
// ----
|
|
5363
|
-
|
|
5364
|
-
|
|
5399
|
+
// ---- deploy and wait, on edge's own address ---------------------------------
|
|
5400
|
+
// F4.dns: edge is the only caller of Cloudflare, so its own CNAME is
|
|
5401
|
+
// written through edge. Edge has to be up before that, and no public name
|
|
5402
|
+
// routes to it yet: health is checked on its Coolify-network address, which
|
|
5403
|
+
// is then recorded for every step that writes DNS (auth, mail, archon). #252:
|
|
5404
|
+
// the combined helper checks the deployment record, then the `/api/health`
|
|
5405
|
+
// probe.
|
|
5406
|
+
const internalUrl = `http://${EDGE_RUNNER_INTERNAL_NAME}:${EDGE_PORT}`;
|
|
5407
|
+
await waitForNewDeployment(deps.coolify, appId, { kind: 'ready', url: `${internalUrl}/api/health` }, runtime, sleep, 'platform:edge', `edge stays at ${recorded[PLATFORM_EDGE_APPLIED_GIT_REF_SCOPE_KEY] ?? 'no recorded ref'}`);
|
|
5408
|
+
await record(PLATFORM_EDGE_INTERNAL_URL_SCOPE_KEY, internalUrl);
|
|
5409
|
+
// ---- tunnel and DNS, now that edge answers ---------------------------------
|
|
5410
|
+
const tunnels = await platformTunnels(deps, { [PLATFORM_EDGE_INTERNAL_URL_SCOPE_KEY]: internalUrl }, 'platform:edge');
|
|
5411
|
+
const existingTunnel = await tunnels.findTunnelByName(EDGE_TUNNEL_NAME);
|
|
5412
|
+
const tunnel = existingTunnel ?? (await tunnels.createTunnel(EDGE_TUNNEL_NAME));
|
|
5365
5413
|
await record(PLATFORM_EDGE_TUNNEL_SCOPE_KEY, tunnel.id);
|
|
5366
|
-
await
|
|
5414
|
+
await tunnels.putTunnelConfig(tunnel.id, {
|
|
5367
5415
|
ingress: [
|
|
5368
5416
|
{ hostname, service: `http://${EDGE_RUNNER_INTERNAL_NAME}:${EDGE_PORT}` },
|
|
5369
5417
|
{ service: 'http_status:404' },
|
|
@@ -5372,7 +5420,7 @@ async function provisionPlatformEdge(context, deps) {
|
|
|
5372
5420
|
// #197: without a connector, `hostname` answers with a Cloudflare error.
|
|
5373
5421
|
await ensureCloudflaredConnector({
|
|
5374
5422
|
coolify: deps.coolify,
|
|
5375
|
-
|
|
5423
|
+
tunnels,
|
|
5376
5424
|
tunnelId: tunnel.id,
|
|
5377
5425
|
appName: EDGE_CLOUDFLARED_APP_NAME,
|
|
5378
5426
|
projectUuid,
|
|
@@ -5386,9 +5434,10 @@ async function provisionPlatformEdge(context, deps) {
|
|
|
5386
5434
|
throw new Error(`platform:edge: no Cloudflare zone for ${platform.apex} to put ${hostname} in`);
|
|
5387
5435
|
}
|
|
5388
5436
|
const dnsTarget = `${tunnel.id}.cfargotunnel.com`;
|
|
5389
|
-
const
|
|
5437
|
+
const dns = await deps.dns(internalUrl);
|
|
5438
|
+
const [dnsRecord] = await dns.findDnsRecord(platform.apex, hostname, 'CNAME');
|
|
5390
5439
|
if (!dnsRecord) {
|
|
5391
|
-
await
|
|
5440
|
+
await dns.createDnsRecord(platform.apex, {
|
|
5392
5441
|
type: 'CNAME',
|
|
5393
5442
|
name: hostname,
|
|
5394
5443
|
content: dnsTarget,
|
|
@@ -5396,19 +5445,13 @@ async function provisionPlatformEdge(context, deps) {
|
|
|
5396
5445
|
});
|
|
5397
5446
|
}
|
|
5398
5447
|
else if (dnsRecord.content !== dnsTarget) {
|
|
5399
|
-
await
|
|
5448
|
+
await dns.updateDnsRecord(platform.apex, dnsRecord.id, {
|
|
5400
5449
|
type: 'CNAME',
|
|
5401
5450
|
name: hostname,
|
|
5402
5451
|
content: dnsTarget,
|
|
5403
5452
|
proxied: true,
|
|
5404
5453
|
});
|
|
5405
5454
|
}
|
|
5406
|
-
// ---- deploy and wait -------------------------------------------------------
|
|
5407
|
-
// #252: the tunnel and DNS above already route `hostname`, so the combined
|
|
5408
|
-
// helper (deployment-record check, then the `/api/health` probe) can run
|
|
5409
|
-
// in one call -- unlike `platform:mail`, nothing here needs to happen
|
|
5410
|
-
// between the two phases.
|
|
5411
|
-
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'}`);
|
|
5412
5455
|
// Last: a run that failed anywhere above is planned again.
|
|
5413
5456
|
context.recordScopeId(PLATFORM_EDGE_APPLIED_HOSTNAME_SCOPE_KEY, hostname);
|
|
5414
5457
|
await record(PLATFORM_EDGE_APPLIED_GIT_REF_SCOPE_KEY, gitRef);
|