@wtfalch/foundry 0.33.1 → 0.34.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/versions.d.ts +4 -0
- package/dist/calls-sfu.d.ts +10 -0
- package/dist/calls-sfu.js +41 -0
- package/dist/index.d.ts +14 -6
- package/dist/index.js +7 -3
- package/dist/migrations/0051_org_services.sql +13 -0
- package/dist/migrations/0052_platform_mail_relay.sql +8 -0
- package/dist/org.d.ts +18 -0
- package/dist/org.js +26 -0
- package/dist/plan/calls-matrix.d.ts +9 -0
- package/dist/plan/calls-matrix.js +17 -0
- package/dist/plan/cloudflared-connector.d.ts +7 -1
- package/dist/plan/cloudflared-connector.js +14 -1
- package/dist/plan/diff.d.ts +29 -0
- package/dist/plan/diff.js +259 -18
- package/dist/plan/executor.d.ts +112 -0
- package/dist/plan/executor.js +594 -33
- package/dist/plan/health-check-settings.d.ts +28 -0
- package/dist/plan/health-check-settings.js +36 -0
- package/dist/plan/health-gaps.d.ts +33 -0
- package/dist/plan/health-gaps.js +89 -0
- package/dist/plan/held-key-refs.d.ts +10 -0
- package/dist/plan/held-key-refs.js +10 -0
- package/dist/plan/index.d.ts +14 -6
- package/dist/plan/index.js +7 -3
- package/dist/plan/live.d.ts +5 -2
- package/dist/plan/live.js +15 -2
- package/dist/plan/locked-hostnames.d.ts +5 -2
- package/dist/plan/locked-hostnames.js +9 -1
- package/dist/plan/login-host.d.ts +62 -2
- package/dist/plan/login-host.js +104 -1
- package/dist/plan/mail-relay.d.ts +120 -0
- package/dist/plan/mail-relay.js +323 -0
- package/dist/plan/org-names.d.ts +24 -0
- package/dist/plan/org-names.js +52 -0
- package/dist/plan/own-diff.d.ts +169 -0
- package/dist/plan/own-diff.js +432 -0
- package/dist/plan/own-dns.d.ts +6 -0
- package/dist/plan/own-dns.js +15 -0
- package/dist/plan/own-limits.d.ts +22 -0
- package/dist/plan/own-limits.js +101 -0
- package/dist/plan/own-services.d.ts +193 -0
- package/dist/plan/own-services.js +167 -0
- package/dist/plan/own-sfu.d.ts +69 -0
- package/dist/plan/own-sfu.js +255 -0
- package/dist/plan/plan-apply.d.ts +78 -7
- package/dist/plan/plan-apply.js +205 -12
- package/dist/plan/platform-diff.d.ts +28 -0
- package/dist/plan/platform-diff.js +45 -3
- package/dist/plan/platform-org.d.ts +34 -0
- package/dist/plan/platform-org.js +94 -2
- package/dist/plan/platform-providers.d.ts +356 -8
- package/dist/plan/platform-providers.js +661 -353
- package/dist/plan/sign-in-org.d.ts +1 -1
- package/dist/plan/sign-in-org.js +20 -2
- package/dist/plan/zitadel-login-policy.js +1 -1
- package/dist/plan/zitadel-system-api.d.ts +22 -1
- package/dist/plan/zitadel-system-api.js +49 -8
- package/dist/platform.d.ts +8 -0
- package/dist/slug.js +1 -0
- package/dist/store/postgres.js +51 -2
- package/dist/store/schema.d.ts +51 -0
- package/dist/store/schema.js +5 -0
- package/package.json +1 -1
- package/versions.json +3 -1
package/dist/plan/diff.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { VERSIONS, floatingTagBlocked } from '../bootstrap/versions.js';
|
|
2
|
-
import { APP_ENTRY_KEY, ORG_POSTGRES_SERVER_SCOPE_KEY, ORG_SERVICE_SCHEMAS, ownerMailboxRecorded, ownerMailboxes, } from '../org.js';
|
|
2
|
+
import { APP_ENTRY_KEY, ORG_POSTGRES_SERVER_SCOPE_KEY, ORG_SERVICE_SCHEMAS, ownerMailboxRecorded, ownerMailboxes, ownsServices, } from '../org.js';
|
|
3
3
|
import { AGORA_ACTIVATED_ENV_SCOPE_KEY, AGORA_APP_PORTS_EXPOSES, CALLS_INTROSPECTION_SCOPE_KEY, CALLS_INTROSPECTION_UNDELIVERED_SCOPE_KEY, CALLS_SERVICE_DEPLOYED_SCOPE_KEY, CHAT_CALLS_APPLIED_SCOPE_KEY, CHAT_CALLS_TOKEN_SCOPE_KEY, CHAT_CALLS_TOKEN_UNDELIVERED_SCOPE_KEY, CHAT_INTROSPECTION_SCOPE_KEY, NO_AGORA_HOST_VALUES, ZITADEL_ORG_SCOPE_KEY, agoraEnvDrift, agoraEnvRevision, agoraHostMissing, agoraHostnameFor, callsServiceHasName, callsServiceHasTokenEnv, callsTokenEnvMovedMessage, callsTokenHostFrom, chatCallsApplied, chatHasUpstreams, desiredAgoraEnv, withRecordedMessagingProject, } from './agora.js';
|
|
4
4
|
import { BOULE_BRIDGE_SECRET_ENV, BOULE_BROKER_ENTRY_KEY, BOULE_MAIL_TOKEN_ENV, BOULE_PEER_PASSWORD_ENV, BOULE_SECRETS_RESTARTED_SCOPE_KEY, bouleEnvDrift, bouleMailTokenDrift, bouleMailWanted, desiredBouleEnv, wantedBouleBrokerEntry, } from './boule.js';
|
|
5
5
|
import { brokerEntryAppliedScopeKey, brokerEntryWarningsScopeKey, entryAuthEnv, entryAuthEnvAppliedScopeKey, wantedBrokerEntry, } from './broker-entries.js';
|
|
@@ -10,9 +10,11 @@ import { planFilesKeys } from './entry-files-key.js';
|
|
|
10
10
|
import { hasWildcardGate, planGates } from './gates.js';
|
|
11
11
|
import { appHostname, entryHostnames, tunnelDnsHostnames, tunnelDomainList } from './hostnames.js';
|
|
12
12
|
import { lockedHostnames } from './locked-hostnames.js';
|
|
13
|
-
import { LOGIN_BINDING_SCOPE_KEY, companyLoginTunnelHost, loginBindingState, loginHostClientIds, loginHostnameFor, pendingRemovalHosts, } from './login-host.js';
|
|
13
|
+
import { LOGIN_BINDING_SCOPE_KEY, agoraIssuerHostFor, companyLoginTunnelHost, loginBindingState, loginHostClientIds, loginHostServesAgora, loginHostnameFor, orgLoginHost, ownAuthApplied, pendingRemovalHosts, } from './login-host.js';
|
|
14
14
|
import { MAIL_COPY_COMPLETED_SCOPE_KEY, MAIL_COPY_DONE_SCOPE_KEY, parseMailCopyCompleted, } from './mail-copy.js';
|
|
15
|
-
import {
|
|
15
|
+
import { OWN_AUTH_BLOCKED_KEY, OWN_FILES_BLOCKED_KEY, OWN_FILES_R2_BLOCKED_KEY, OWN_JOBS_BLOCKED_KEY, OWN_MAIL_BLOCKED_KEY, OWN_SFU_BLOCKED_KEY, OWN_SFU_FIREWALL_KEY, planOwnAuth, planOwnFiles, planOwnFilesR2, planOwnJobs, planOwnMail, planOwnSfu, } from './own-diff.js';
|
|
16
|
+
import { ownDnsHostnames } from './own-dns.js';
|
|
17
|
+
import { CALLS_MAS_APPLIED_IMAGE_TAG_SCOPE_KEY, CALLS_SYNAPSE_APPLIED_IMAGE_TAG_SCOPE_KEY, ORG_MAIL_APPLIED_APEX_SCOPE_KEY, ORG_MAIL_APPLIED_HOST_SCOPE_KEY, ORG_MAIL_APPLIED_MODE_SCOPE_KEY, ORG_MAIL_APPLIED_USES_DOMAIN_SCOPE_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, appEnvAppliedScopeKey, appFolderDiffers, appSettingsOf, appStepKey, appliedAppDomains, appliedAppSettings, dbEnvAppliedScopeKey, resolveAppEnv, serviceDbEnvAppliedScopeKey, versionNotAppliedLine, zitadelAppIdTokenUserinfoAppliedScopeKey, } from './platform-diff.js';
|
|
16
18
|
import { DEFAULT_ORG_PLATFORM_READINESS, } from './platform-org.js';
|
|
17
19
|
import { PLATFORM_AUTH_BROKER_MAIL_KEY_REF } from './platform-providers.js';
|
|
18
20
|
import { appLabelsAppliedScopeKey, entryLabelHash, entryLabelSpec } from './proxy-labels.js';
|
|
@@ -56,6 +58,10 @@ function resourceKey(system, kind, key) {
|
|
|
56
58
|
function madeByFoundry(org, r, recordedIds, recordedValues, platformNames) {
|
|
57
59
|
if (PROVIDER_KINDS[r.system]?.includes(r.kind) && recordedValues.has(r.key))
|
|
58
60
|
return true;
|
|
61
|
+
// The reader maps an applied own step's app onto its marker (`coolify:own-<service>:<org.id>`); only this org's id is its own.
|
|
62
|
+
if (r.system === 'coolify' && OWN_MARKER_KINDS.has(r.kind) && r.key === org.id) {
|
|
63
|
+
return Boolean(recordedIds[`coolify:${r.kind}:${org.id}`]);
|
|
64
|
+
}
|
|
59
65
|
if (r.system === 'coolify') {
|
|
60
66
|
if (r.kind === 'environment')
|
|
61
67
|
return true;
|
|
@@ -78,6 +84,11 @@ function madeByFoundry(org, r, recordedIds, recordedValues, platformNames) {
|
|
|
78
84
|
if (type === 'CNAME' && content.endsWith('.cfargotunnel.com') && tunnelHostnames.includes(name)) {
|
|
79
85
|
return true;
|
|
80
86
|
}
|
|
87
|
+
if (type === 'CNAME' &&
|
|
88
|
+
content.endsWith('.cfargotunnel.com') &&
|
|
89
|
+
ownDnsHostnames(recordedIds).includes(name)) {
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
81
92
|
const mailDomain = recordedIds[MAIL_DOMAIN_SCOPE_KEY]?.toLowerCase();
|
|
82
93
|
if (!mailDomain)
|
|
83
94
|
return false;
|
|
@@ -86,6 +97,14 @@ function madeByFoundry(org, r, recordedIds, recordedValues, platformNames) {
|
|
|
86
97
|
}
|
|
87
98
|
return name === `_dmarc.${mailDomain}` || name.endsWith(`._domainkey.${mailDomain}`);
|
|
88
99
|
}
|
|
100
|
+
const OWN_MARKER_KINDS = new Set([
|
|
101
|
+
'own-auth',
|
|
102
|
+
'own-mail',
|
|
103
|
+
'own-files',
|
|
104
|
+
'own-files-r2',
|
|
105
|
+
'own-jobs',
|
|
106
|
+
'own-sfu',
|
|
107
|
+
]);
|
|
89
108
|
/** The kinds a provider lists a resource under before a recorded `system:kind:key` maps it to foundry's own. */
|
|
90
109
|
const PROVIDER_KINDS = {
|
|
91
110
|
coolify: ['application', 'database'],
|
|
@@ -167,6 +186,21 @@ export function computePlanDetail(org, live, recordedIds = {}, agoraHost = NO_AG
|
|
|
167
186
|
// #491 reads for mail). It signs in on the shared instance's own host, so
|
|
168
187
|
// `issuer.usesDomain` never moves it.
|
|
169
188
|
const isPlatformOrg = isPlatformOwnOrg(org, platformReadiness.platformOwnerOrgId);
|
|
189
|
+
// An own org's sign-in comes before every step that signs in through it.
|
|
190
|
+
const ownAuth = planOwnAuth({
|
|
191
|
+
org,
|
|
192
|
+
recordedIds,
|
|
193
|
+
readiness: platformReadiness,
|
|
194
|
+
isPlatformOrg,
|
|
195
|
+
ownServices: options.ownServices ?? false,
|
|
196
|
+
zone: org.domain ? zoneReadiness(org.domain, byKey) : null,
|
|
197
|
+
mailKeyHeld: options.ownMailKeyHeld ?? false,
|
|
198
|
+
mailKeyMintable: options.ownMailKeyMintable ?? false,
|
|
199
|
+
});
|
|
200
|
+
if (ownAuth.step)
|
|
201
|
+
plannedSteps.push(ownAuth.step);
|
|
202
|
+
// An own org's ZITADEL is the company's own instance: nothing that needs it plans before it exists.
|
|
203
|
+
const ownSignInPending = ownsServices(org) && !isPlatformOrg && !ownAuthApplied(org, recordedIds);
|
|
170
204
|
const issuer = planIssuer(org, byKey, claimed, platformReadiness.authReady, isPlatformOrg);
|
|
171
205
|
if (issuer.step)
|
|
172
206
|
plannedSteps.push(issuer.step);
|
|
@@ -213,6 +247,17 @@ export function computePlanDetail(org, live, recordedIds = {}, agoraHost = NO_AG
|
|
|
213
247
|
else if (zone.step) {
|
|
214
248
|
plannedSteps.push(zone.step);
|
|
215
249
|
}
|
|
250
|
+
const ownMail = planOwnMail({
|
|
251
|
+
org,
|
|
252
|
+
recordedIds,
|
|
253
|
+
readiness: platformReadiness,
|
|
254
|
+
isPlatformOrg,
|
|
255
|
+
ownServices: options.ownServices ?? false,
|
|
256
|
+
mailRelayApplied: options.mailRelayApplied ?? false,
|
|
257
|
+
zoneActive: zone.step === null && !zoneWaiting,
|
|
258
|
+
});
|
|
259
|
+
if (ownMail.step)
|
|
260
|
+
plannedSteps.push(ownMail.step);
|
|
216
261
|
const mail = planMail(org, byKey, claimed, recordedIds, platformReadiness.mailHost, platformReadiness.platformApex, platformReadiness.platformLettermintProjectId);
|
|
217
262
|
// #238: own-domain mail publishes DNS records into the zone, so it waits for
|
|
218
263
|
// an active zone -- blocked, not planned (rule 2).
|
|
@@ -238,6 +283,19 @@ export function computePlanDetail(org, live, recordedIds = {}, agoraHost = NO_AG
|
|
|
238
283
|
const calls = planCalls(org, byKey, claimed, platformReadiness.callsReady);
|
|
239
284
|
if (calls.step)
|
|
240
285
|
plannedSteps.push(calls.step);
|
|
286
|
+
// An own org's media server, before the calls service that reads its addresses.
|
|
287
|
+
const ownSfu = planOwnSfu({
|
|
288
|
+
org,
|
|
289
|
+
recordedIds,
|
|
290
|
+
isPlatformOrg,
|
|
291
|
+
ownServices: options.ownServices ?? false,
|
|
292
|
+
zoneActive: zone.step === null && !zoneWaiting,
|
|
293
|
+
readiness: platformReadiness,
|
|
294
|
+
sfuPortsOpen: options.sfuPortsOpen ?? false,
|
|
295
|
+
keysHeld: options.ownSfuKeysHeld ?? false,
|
|
296
|
+
});
|
|
297
|
+
if (ownSfu.step)
|
|
298
|
+
plannedSteps.push(ownSfu.step);
|
|
241
299
|
// Right after the Matrix stack (#145): its own executor
|
|
242
300
|
// (`executor.ts`'s `coolifyCallsService`) reads the Synapse/MAS addresses
|
|
243
301
|
// and MAS client credentials `coolifyCalls` just recorded, the same
|
|
@@ -269,7 +327,30 @@ export function computePlanDetail(org, live, recordedIds = {}, agoraHost = NO_AG
|
|
|
269
327
|
if (masChange)
|
|
270
328
|
versionChanges.push(masChange);
|
|
271
329
|
}
|
|
272
|
-
|
|
330
|
+
// An own org's files host, before its organisation on it.
|
|
331
|
+
const ownFiles = planOwnFiles({
|
|
332
|
+
org,
|
|
333
|
+
recordedIds,
|
|
334
|
+
readiness: platformReadiness,
|
|
335
|
+
isPlatformOrg,
|
|
336
|
+
ownServices: options.ownServices ?? false,
|
|
337
|
+
filesDeps: options.ownFilesDeps ?? { keys: false, bucket: false },
|
|
338
|
+
});
|
|
339
|
+
if (ownFiles.step)
|
|
340
|
+
plannedSteps.push(ownFiles.step);
|
|
341
|
+
// After the host is applied: its storage key is delivered to the running host.
|
|
342
|
+
const ownFilesR2 = options.ownFilesR2Ports
|
|
343
|
+
? planOwnFilesR2({
|
|
344
|
+
org,
|
|
345
|
+
recordedIds,
|
|
346
|
+
isPlatformOrg,
|
|
347
|
+
ownServices: options.ownServices ?? false,
|
|
348
|
+
ports: options.ownFilesR2Ports,
|
|
349
|
+
})
|
|
350
|
+
: { step: null, blocked: null };
|
|
351
|
+
if (ownFilesR2.step)
|
|
352
|
+
plannedSteps.push(ownFilesR2.step);
|
|
353
|
+
const filesOrganisation = planFilesOrganisation(org, byKey, claimed, platformReadiness.filesReady, platformReadiness.missingKeys.files, platformReadiness.noKeyReader, ownsServices(org) && !isPlatformOrg && !recordedIds[`coolify:own-files:${org.id}`]);
|
|
273
354
|
if (filesOrganisation.step)
|
|
274
355
|
plannedSteps.push(filesOrganisation.step);
|
|
275
356
|
const aiOrganisation = planAiOrganisation(org, byKey, claimed, platformReadiness.aiReady, platformReadiness.missingKeys.ai, platformReadiness.noKeyReader);
|
|
@@ -411,7 +492,9 @@ export function computePlanDetail(org, live, recordedIds = {}, agoraHost = NO_AG
|
|
|
411
492
|
const agoraGuarded = guardApp(agoraClient);
|
|
412
493
|
if (agoraGuarded)
|
|
413
494
|
plannedSteps.push(agoraGuarded);
|
|
414
|
-
const agora = planAgora(org, byKey, claimed, recordedIds, agoraHost
|
|
495
|
+
const agora = planAgora(org, byKey, claimed, recordedIds, agoraHost, isPlatformOrg, {
|
|
496
|
+
servesDiscovery: options.loginHostServesDiscovery ?? false,
|
|
497
|
+
});
|
|
415
498
|
if (agora.step)
|
|
416
499
|
plannedSteps.push(agora.step);
|
|
417
500
|
// #161 (Boule part): every org's own Boule -- always on, no toggle, the same
|
|
@@ -433,10 +516,23 @@ export function computePlanDetail(org, live, recordedIds = {}, agoraHost = NO_AG
|
|
|
433
516
|
const bouleBroker = planBouleBrokerEntry(org, recordedIds, plannedSteps, options.broker ?? false);
|
|
434
517
|
if (bouleBroker.step)
|
|
435
518
|
plannedSteps.push(bouleBroker.step);
|
|
519
|
+
// An own org's scheduler, before its registration below.
|
|
520
|
+
const ownJobs = planOwnJobs({
|
|
521
|
+
org,
|
|
522
|
+
recordedIds,
|
|
523
|
+
readiness: platformReadiness,
|
|
524
|
+
isPlatformOrg,
|
|
525
|
+
ownServices: options.ownServices ?? false,
|
|
526
|
+
tokenHeld: options.ownJobsTokenHeld ?? false,
|
|
527
|
+
});
|
|
528
|
+
if (ownJobs.step)
|
|
529
|
+
plannedSteps.push(ownJobs.step);
|
|
436
530
|
// #173: register the org's own app with the platform's jobs scheduler.
|
|
437
531
|
// After Agora, so a failing registration can never stop Agora from
|
|
438
532
|
// provisioning (`runSteps` stops at the first failing step).
|
|
439
|
-
const jobsRegistration = planJobsRegistration(org, recordedIds,
|
|
533
|
+
const jobsRegistration = planJobsRegistration(org, recordedIds,
|
|
534
|
+
// An own org never registers with the shared scheduler the host supplies.
|
|
535
|
+
(jobsAvailable && !ownsServices(org)) || platformReadiness.jobsReady, platformReadiness.missingKeys.jobs, platformReadiness.noKeyReader, platformReadiness.jobsUrlMissing ?? false);
|
|
440
536
|
if (jobsRegistration.step)
|
|
441
537
|
plannedSteps.push(jobsRegistration.step);
|
|
442
538
|
// F8 (#318), after Agora and Boule, not right after the zone: a route
|
|
@@ -489,6 +585,8 @@ export function computePlanDetail(org, live, recordedIds = {}, agoraHost = NO_AG
|
|
|
489
585
|
// item list, so it rides along with the unmanaged items.
|
|
490
586
|
if (agora.blocked)
|
|
491
587
|
unmanaged.push({ description: agora.blocked, key: 'coolify:agora' });
|
|
588
|
+
if (agora.issuerNote)
|
|
589
|
+
unmanaged.push({ description: agora.issuerNote, key: 'coolify:agora' });
|
|
492
590
|
if (boule.blocked)
|
|
493
591
|
unmanaged.push({ description: boule.blocked, key: 'coolify:boule' });
|
|
494
592
|
if (boule.mailBlocked) {
|
|
@@ -503,6 +601,22 @@ export function computePlanDetail(org, live, recordedIds = {}, agoraHost = NO_AG
|
|
|
503
601
|
unmanaged.push(...edgeWorkers.blocked);
|
|
504
602
|
unmanaged.push(...entryEdgeKeys.blocked);
|
|
505
603
|
unmanaged.push(...edgeReservedLabels.blocked);
|
|
604
|
+
if (ownAuth.blocked)
|
|
605
|
+
unmanaged.push({ description: ownAuth.blocked, key: OWN_AUTH_BLOCKED_KEY });
|
|
606
|
+
if (ownJobs.blocked)
|
|
607
|
+
unmanaged.push({ description: ownJobs.blocked, key: OWN_JOBS_BLOCKED_KEY });
|
|
608
|
+
if (ownMail.blocked)
|
|
609
|
+
unmanaged.push({ description: ownMail.blocked, key: OWN_MAIL_BLOCKED_KEY });
|
|
610
|
+
if (ownSfu.blocked)
|
|
611
|
+
unmanaged.push({ description: ownSfu.blocked, key: OWN_SFU_BLOCKED_KEY });
|
|
612
|
+
if (ownSfu.firewall)
|
|
613
|
+
unmanaged.push({ description: ownSfu.firewall, key: OWN_SFU_FIREWALL_KEY });
|
|
614
|
+
if (ownFiles.blocked) {
|
|
615
|
+
unmanaged.push({ description: ownFiles.blocked, key: OWN_FILES_BLOCKED_KEY });
|
|
616
|
+
}
|
|
617
|
+
if (ownFilesR2.blocked) {
|
|
618
|
+
unmanaged.push({ description: ownFilesR2.blocked, key: OWN_FILES_R2_BLOCKED_KEY });
|
|
619
|
+
}
|
|
506
620
|
// Same for a jobs registration that cannot run (no scheduler wired) or must
|
|
507
621
|
// not run again (minted, never delivered).
|
|
508
622
|
if (jobsRegistration.blocked) {
|
|
@@ -513,9 +627,20 @@ export function computePlanDetail(org, live, recordedIds = {}, agoraHost = NO_AG
|
|
|
513
627
|
// `platformReadiness` instead of a host-supplied port. Each is reported at
|
|
514
628
|
// most once even though a dependent step (owner, calls-service, storage,
|
|
515
629
|
// budget, key) would otherwise also have nothing to act on.
|
|
516
|
-
if (
|
|
517
|
-
|
|
518
|
-
|
|
630
|
+
if (ownSignInPending) {
|
|
631
|
+
// The sign-in step's own reason, when it has one, already says why the rest waits.
|
|
632
|
+
if (!ownAuth.blocked) {
|
|
633
|
+
unmanaged.push({
|
|
634
|
+
description: `${org.slug}'s ZITADEL steps are blocked, not planned: its own sign-in service is not applied yet`,
|
|
635
|
+
key: 'coolify:own-auth',
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
else {
|
|
640
|
+
if (issuer.blocked)
|
|
641
|
+
unmanaged.push({ description: issuer.blocked, key: 'zitadel:issuer' });
|
|
642
|
+
unmanaged.push(...owners.blocked);
|
|
643
|
+
}
|
|
519
644
|
const domainBlocked = orgDomainBlocked(org, recordedIds);
|
|
520
645
|
if (domainBlocked)
|
|
521
646
|
unmanaged.push(domainBlocked);
|
|
@@ -568,12 +693,31 @@ export function computePlanDetail(org, live, recordedIds = {}, agoraHost = NO_AG
|
|
|
568
693
|
unmanaged.push(...bouleBroker.blocked);
|
|
569
694
|
return {
|
|
570
695
|
orgId: org.id,
|
|
571
|
-
plannedSteps:
|
|
696
|
+
plannedSteps: (ownSignInPending
|
|
697
|
+
? plannedSteps.filter((s) => !NEEDS_OWN_ZITADEL.has(`${s.action.system}:${s.action.kind}`))
|
|
698
|
+
: plannedSteps).map(withStepSystem),
|
|
572
699
|
unmanaged,
|
|
573
700
|
versionChanges,
|
|
574
701
|
orgPostgres,
|
|
575
702
|
};
|
|
576
703
|
}
|
|
704
|
+
/** Steps that run against the org's own ZITADEL instance (its projects, apps and broker entries). */
|
|
705
|
+
const NEEDS_OWN_ZITADEL = new Set([
|
|
706
|
+
'zitadel:issuer',
|
|
707
|
+
'zitadel:registration-closed',
|
|
708
|
+
'zitadel:domain',
|
|
709
|
+
'zitadel:owner',
|
|
710
|
+
'zitadel:app',
|
|
711
|
+
'zitadel:customers',
|
|
712
|
+
'zitadel:id-token-userinfo',
|
|
713
|
+
'zitadel:login-host',
|
|
714
|
+
'coolify:chat',
|
|
715
|
+
'coolify:calls-service',
|
|
716
|
+
'coolify:calls-chat',
|
|
717
|
+
'coolify:broker-entry',
|
|
718
|
+
'coolify:boule-broker-entry',
|
|
719
|
+
'files:organisation',
|
|
720
|
+
]);
|
|
577
721
|
/**
|
|
578
722
|
* #553: a plain ZITADEL app provision (not a `recreate`) is planned only when
|
|
579
723
|
* the live listing lacks the app by name. If an app id is recorded for it,
|
|
@@ -926,6 +1070,9 @@ function platformAuthHostnameFrom(issuer) {
|
|
|
926
1070
|
*/
|
|
927
1071
|
function planLoginHost(org, recordedIds, authReady, issuerPlanned, bindingsWired, platformAuthHostname, isPlatformOrg) {
|
|
928
1072
|
const none = { step: null, blocked: null };
|
|
1073
|
+
// An own org's instance has no shared login host to bind: its issuer is the sign-in service's own.
|
|
1074
|
+
if (ownsServices(org) && !isPlatformOrg)
|
|
1075
|
+
return none;
|
|
929
1076
|
const key = resourceKey('zitadel', 'login-host', org.id);
|
|
930
1077
|
// The platform's own org signs in on the shared instance's host whatever
|
|
931
1078
|
// `issuer.usesDomain` says (turning it on would move the issuer).
|
|
@@ -938,7 +1085,7 @@ function planLoginHost(org, recordedIds, authReady, issuerPlanned, bindingsWired
|
|
|
938
1085
|
};
|
|
939
1086
|
}
|
|
940
1087
|
const sharedInstanceHost = isPlatformOrg && platformAuthHostname !== null;
|
|
941
|
-
const host = sharedInstanceHost ? platformAuthHostname :
|
|
1088
|
+
const host = orgLoginHost(org, sharedInstanceHost ? platformAuthHostname : null);
|
|
942
1089
|
// #488: the guard in `loginHostnameFor` fired -- the org's own domain host
|
|
943
1090
|
// would have been the platform's shared ZITADEL instance host.
|
|
944
1091
|
if (sharedInstanceHost) {
|
|
@@ -1495,7 +1642,9 @@ function planCalls(org, byKey, claimed, callsReady) {
|
|
|
1495
1642
|
if (org.calls.enabled && !byKey.get(key) && !callsReady) {
|
|
1496
1643
|
return {
|
|
1497
1644
|
step: null,
|
|
1498
|
-
blocked:
|
|
1645
|
+
blocked: ownsServices(org)
|
|
1646
|
+
? `${org.slug}'s calls Matrix stack is blocked, not planned: its own media server (coolify:own-sfu) is not applied yet`
|
|
1647
|
+
: `${org.slug}'s calls Matrix stack is blocked, not planned: the platform's shared calls SFU is off`,
|
|
1499
1648
|
};
|
|
1500
1649
|
}
|
|
1501
1650
|
// #248: the two images this stack's own apply would run, checked the same
|
|
@@ -1544,6 +1693,13 @@ function planCallsService(org, byKey, claimed, callsReady, recordedIds, callsBlo
|
|
|
1544
1693
|
if (org.calls.enabled && !byKey.get(key) && !callsReady) {
|
|
1545
1694
|
return { step: null, blocked: null };
|
|
1546
1695
|
}
|
|
1696
|
+
// An own org's service names its own media server, so it waits for it even when already provisioned.
|
|
1697
|
+
if (org.calls.enabled && ownsServices(org) && !callsReady) {
|
|
1698
|
+
return {
|
|
1699
|
+
step: null,
|
|
1700
|
+
blocked: `${org.slug}'s calls service is blocked, not planned: its own media server (coolify:own-sfu) is not applied yet`,
|
|
1701
|
+
};
|
|
1702
|
+
}
|
|
1547
1703
|
// The introspection secret is shown once. One minted but never written to
|
|
1548
1704
|
// the app is lost, so this reports it and mints no second (platform rule 1).
|
|
1549
1705
|
const undelivered = recordedIds[CALLS_INTROSPECTION_UNDELIVERED_SCOPE_KEY];
|
|
@@ -1771,13 +1927,21 @@ function notReadyReason(service, keyName, missingKeyRef, noKeyReader) {
|
|
|
1771
1927
|
* Files organisation already exists -- the old origin is left allowed
|
|
1772
1928
|
* rather than silently dropped.
|
|
1773
1929
|
*/
|
|
1774
|
-
function planFilesOrganisation(org, byKey, claimed, filesReady, missingKeyRef, noKeyReader = false
|
|
1930
|
+
function planFilesOrganisation(org, byKey, claimed, filesReady, missingKeyRef, noKeyReader = false,
|
|
1931
|
+
/** An own org whose files host (`coolify:own-files:<org.id>`) is not recorded yet. */
|
|
1932
|
+
ownFilesPending = false) {
|
|
1775
1933
|
const key = resourceKey('files', 'organisation', org.id);
|
|
1776
1934
|
const live = byKey.get(key);
|
|
1777
1935
|
if (live) {
|
|
1778
1936
|
claimed.add(key);
|
|
1779
1937
|
return { step: null, blocked: null };
|
|
1780
1938
|
}
|
|
1939
|
+
if (ownFilesPending) {
|
|
1940
|
+
return {
|
|
1941
|
+
step: null,
|
|
1942
|
+
blocked: `${org.slug}'s Files organisation is blocked, not planned: its own files host is not applied yet`,
|
|
1943
|
+
};
|
|
1944
|
+
}
|
|
1781
1945
|
if (!filesReady) {
|
|
1782
1946
|
return {
|
|
1783
1947
|
step: null,
|
|
@@ -2150,14 +2314,32 @@ function planAgoraClient(org, byKey, claimed) {
|
|
|
2150
2314
|
* message naming each missing value, which `computePlanDetail` lists with the
|
|
2151
2315
|
* plan's non-step items, so every other step still applies.
|
|
2152
2316
|
*/
|
|
2153
|
-
function planAgora(org, byKey, claimed, recordedIds, reading) {
|
|
2317
|
+
function planAgora(org, byKey, claimed, recordedIds, reading, isPlatformOrg, issuerSwitch) {
|
|
2154
2318
|
const key = resourceKey('coolify', 'agora', org.id);
|
|
2155
2319
|
const live = byKey.get(key);
|
|
2156
2320
|
if (live)
|
|
2157
2321
|
claimed.add(key);
|
|
2322
|
+
const ownSignIn = ownsServices(org) && !isPlatformOrg;
|
|
2323
|
+
if (ownSignIn && !ownAuthApplied(org, recordedIds)) {
|
|
2324
|
+
return {
|
|
2325
|
+
step: null,
|
|
2326
|
+
blocked: `${org.slug}'s Agora is blocked, not planned: its own sign-in service is not applied yet`,
|
|
2327
|
+
issuerNote: null,
|
|
2328
|
+
};
|
|
2329
|
+
}
|
|
2330
|
+
// The files host records its internal URL only while it applies, and Agora's
|
|
2331
|
+
// env takes its files upstream from it: planned in the same pass, the first
|
|
2332
|
+
// deploy would start without one. The company's files host exists only with a domain.
|
|
2333
|
+
if (ownSignIn && org.domain && !recordedIds[PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY]) {
|
|
2334
|
+
return {
|
|
2335
|
+
step: null,
|
|
2336
|
+
blocked: `${org.slug}'s Agora is blocked, not planned: its own files host is not applied yet`,
|
|
2337
|
+
issuerNote: null,
|
|
2338
|
+
};
|
|
2339
|
+
}
|
|
2158
2340
|
const error = 'error' in reading ? reading.error : null;
|
|
2159
|
-
const
|
|
2160
|
-
const missing = agoraHostMissing(
|
|
2341
|
+
const platformHost = withRecordedMessagingProject('error' in reading ? NO_AGORA_HOST_VALUES : reading, recordedIds);
|
|
2342
|
+
const missing = agoraHostMissing(platformHost);
|
|
2161
2343
|
if (missing.length > 0) {
|
|
2162
2344
|
// A fixed text, never the port's own message: this line is part of the
|
|
2163
2345
|
// stale-plan comparison, and an error that varies per call would refuse
|
|
@@ -2166,8 +2348,41 @@ function planAgora(org, byKey, claimed, recordedIds, reading) {
|
|
|
2166
2348
|
return {
|
|
2167
2349
|
step: null,
|
|
2168
2350
|
blocked: `${org.slug}'s Agora is blocked, not planned: the host supplies no ${missing.join(', ')}${why}`,
|
|
2351
|
+
issuerNote: null,
|
|
2169
2352
|
};
|
|
2170
2353
|
}
|
|
2354
|
+
// Agora signs in at the company's own login host, and refuses any issuer off
|
|
2355
|
+
// its own address's domain. Only this value changes: the calls token env,
|
|
2356
|
+
// chat and Boule keep reading the platform issuer from `reading`, and Agora
|
|
2357
|
+
// always plans. The company host is written only once the login host is
|
|
2358
|
+
// applied with Agora's client AND a live read said it serves discovery as
|
|
2359
|
+
// that issuer. An Agora already on the company host keeps it while the host
|
|
2360
|
+
// stays applied: a later failing read never flips it back (no oscillation).
|
|
2361
|
+
let host = platformHost;
|
|
2362
|
+
let issuerNote = null;
|
|
2363
|
+
const choice = agoraIssuerHostFor(org, isPlatformOrg);
|
|
2364
|
+
if (choice.mismatch) {
|
|
2365
|
+
issuerNote = `${org.slug}'s Agora cannot sign in: its login host ${loginHostnameFor(org)} is not on its Agora address ${agoraHostnameFor(org)}; the login host must be on the org's domain`;
|
|
2366
|
+
}
|
|
2367
|
+
else if (choice.host) {
|
|
2368
|
+
const loginHost = choice.host;
|
|
2369
|
+
const target = `https://${loginHost}`;
|
|
2370
|
+
const applied = loginHostServesAgora(loginHost, org, recordedIds);
|
|
2371
|
+
if (applied && (liveAgoraIssuer(live) === target || issuerSwitch.servesDiscovery)) {
|
|
2372
|
+
host = { ...platformHost, issuer: target };
|
|
2373
|
+
}
|
|
2374
|
+
else if (ownSignIn) {
|
|
2375
|
+
// No platform issuer to fall back on: Agora waits for the company's own.
|
|
2376
|
+
return {
|
|
2377
|
+
step: null,
|
|
2378
|
+
blocked: `${org.slug}'s Agora is blocked, not planned: ${target} does not answer OIDC discovery yet`,
|
|
2379
|
+
issuerNote: null,
|
|
2380
|
+
};
|
|
2381
|
+
}
|
|
2382
|
+
else {
|
|
2383
|
+
issuerNote = `${org.slug}'s Agora keeps the platform issuer; the switch to ${target} is blocked, not planned, until ${loginHost} is applied with Agora's client and answers OIDC discovery`;
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2171
2386
|
// The executor writes exactly these host values, so what the plan showed
|
|
2172
2387
|
// is what gets applied; the ids only the apply itself records are read at
|
|
2173
2388
|
// run time.
|
|
@@ -2188,6 +2403,7 @@ function planAgora(org, byKey, claimed, recordedIds, reading) {
|
|
|
2188
2403
|
},
|
|
2189
2404
|
},
|
|
2190
2405
|
blocked: null,
|
|
2406
|
+
issuerNote,
|
|
2191
2407
|
};
|
|
2192
2408
|
}
|
|
2193
2409
|
const liveEnv = (live.config.env ?? {});
|
|
@@ -2215,8 +2431,9 @@ function planAgora(org, byKey, claimed, recordedIds, reading) {
|
|
|
2215
2431
|
// actually serving this exact `env`; while it disagrees, the step stays
|
|
2216
2432
|
// planned no matter what Coolify's env store already shows.
|
|
2217
2433
|
const activated = recordedIds[AGORA_ACTIVATED_ENV_SCOPE_KEY] === agoraEnvRevision(env);
|
|
2218
|
-
if (drift.length === 0 && activated && !portDrift)
|
|
2219
|
-
return { step: null, blocked: null };
|
|
2434
|
+
if (drift.length === 0 && activated && !portDrift) {
|
|
2435
|
+
return { step: null, blocked: null, issuerNote };
|
|
2436
|
+
}
|
|
2220
2437
|
const reasons = [
|
|
2221
2438
|
...(drift.length > 0 ? [`${drift.join(', ')} missing or different`] : []),
|
|
2222
2439
|
...(portDrift ? ["ports_exposes wrong -- Traefik can't reach it"] : []),
|
|
@@ -2233,8 +2450,22 @@ function planAgora(org, byKey, claimed, recordedIds, reading) {
|
|
|
2233
2450
|
},
|
|
2234
2451
|
},
|
|
2235
2452
|
blocked: null,
|
|
2453
|
+
issuerNote,
|
|
2236
2454
|
};
|
|
2237
2455
|
}
|
|
2456
|
+
/** The issuer a live Agora's `AGORA_CONFIG` names, or null when it has none or is not JSON. */
|
|
2457
|
+
function liveAgoraIssuer(live) {
|
|
2458
|
+
const raw = (live?.config.env ?? {}).AGORA_CONFIG;
|
|
2459
|
+
if (raw === undefined)
|
|
2460
|
+
return null;
|
|
2461
|
+
try {
|
|
2462
|
+
const issuer = JSON.parse(raw)?.issuer;
|
|
2463
|
+
return typeof issuer === 'string' ? issuer : null;
|
|
2464
|
+
}
|
|
2465
|
+
catch {
|
|
2466
|
+
return null;
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2238
2469
|
/**
|
|
2239
2470
|
* #161 (Boule part): every org gets one fixed ZITADEL web client named
|
|
2240
2471
|
* `boule`, so its Boule (https://github.com/wtfalch/boule) can sign anyone in.
|
|
@@ -2871,6 +3102,16 @@ function planJobsRegistration(org, recordedIds, jobsAvailable, missingKeyRef = n
|
|
|
2871
3102
|
// The host says whether its executor has a scheduler to register with. A
|
|
2872
3103
|
// fixed text: it is part of the stale-plan comparison.
|
|
2873
3104
|
if (!jobsAvailable) {
|
|
3105
|
+
if (ownsServices(org)) {
|
|
3106
|
+
return {
|
|
3107
|
+
step: null,
|
|
3108
|
+
blocked: `${org.slug}'s jobs registration is blocked, not planned: ${addressMissing
|
|
3109
|
+
? `${org.slug}'s own jobs scheduler has not recorded its internal address yet; apply its own jobs step first`
|
|
3110
|
+
: missingKeyRef
|
|
3111
|
+
? `${org.slug}'s own jobs admin token ${missingKeyRef} is not held yet`
|
|
3112
|
+
: `${org.slug}'s own jobs scheduler is not applied yet`}`,
|
|
3113
|
+
};
|
|
3114
|
+
}
|
|
2874
3115
|
return {
|
|
2875
3116
|
step: null,
|
|
2876
3117
|
blocked: addressMissing
|
package/dist/plan/executor.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { type EdgePlatformTunnels, type TunnelPort } from './edge-tunnels.js';
|
|
|
12
12
|
import { type EdgePlatformZones, type ZonePort } from './edge-zones.js';
|
|
13
13
|
import { type LoginHostBindingsPort } from './login-host.js';
|
|
14
14
|
import { type MailCopyPort } from './mail-copy.js';
|
|
15
|
+
import { type OwnServicesPort } from './own-services.js';
|
|
15
16
|
import type { DestroyTarget, StepDestroyer, StepExecutionContext, StepExecutor } from './plan-apply.js';
|
|
16
17
|
import type { EdgePortName } from './platform-org.js';
|
|
17
18
|
export declare function coolifyProjectMark(org: Pick<DeclaredOrg, 'id'>): string;
|
|
@@ -107,6 +108,9 @@ export interface CoolifyPort {
|
|
|
107
108
|
custom_network_aliases?: string | null;
|
|
108
109
|
health_check_enabled?: boolean;
|
|
109
110
|
ports_exposes?: string;
|
|
111
|
+
/** Coolify's own record of the resource limits (`plan/own-limits.ts`). */
|
|
112
|
+
limits_memory?: string;
|
|
113
|
+
limits_cpus?: string;
|
|
110
114
|
}>;
|
|
111
115
|
createGithubAppApplication(input: {
|
|
112
116
|
project_uuid: string;
|
|
@@ -162,6 +166,8 @@ export interface CoolifyPort {
|
|
|
162
166
|
uuid: string;
|
|
163
167
|
internal_db_url?: string;
|
|
164
168
|
status?: string;
|
|
169
|
+
limits_memory?: string;
|
|
170
|
+
limits_cpus?: string;
|
|
165
171
|
}>;
|
|
166
172
|
/**
|
|
167
173
|
* Every database Coolify holds. `coolifyDatabase` finds its own by name and
|
|
@@ -1144,6 +1150,16 @@ export interface ZitadelListAppsRawApp {
|
|
|
1144
1150
|
* an operator's `MANAGEMENT_PUBLIC_KEYS`/`MANAGEMENT_ISSUER` pins.
|
|
1145
1151
|
*/
|
|
1146
1152
|
export interface FilesOrganisationPort {
|
|
1153
|
+
/**
|
|
1154
|
+
* An own files host's R2 storage key, sent to that host once (the files route
|
|
1155
|
+
* `PUT /v1/manage/r2-credentials`). Absent on a port that cannot: the own R2 key step is then
|
|
1156
|
+
* blocked by name. The same pair may be sent again after a failed send.
|
|
1157
|
+
*/
|
|
1158
|
+
storeR2Credentials?(input: {
|
|
1159
|
+
accessKeyId: string;
|
|
1160
|
+
secretAccessKey: string;
|
|
1161
|
+
tokenId: string;
|
|
1162
|
+
}): Promise<void>;
|
|
1147
1163
|
provisionOrganisation(input: {
|
|
1148
1164
|
organisationId: string;
|
|
1149
1165
|
ceilingBytes: string | null;
|
|
@@ -1447,6 +1463,12 @@ export interface PlatformOrgPortSource {
|
|
|
1447
1463
|
}
|
|
1448
1464
|
export interface FoundryStepExecutorPorts {
|
|
1449
1465
|
coolify: CoolifyPort;
|
|
1466
|
+
/**
|
|
1467
|
+
* Ports for the instances an own org (`services.own`) runs itself. Without
|
|
1468
|
+
* it, a step for an own org is refused rather than run against the shared
|
|
1469
|
+
* services, and the planner reports the org's own services blocked.
|
|
1470
|
+
*/
|
|
1471
|
+
ownServices?: OwnServicesPort;
|
|
1450
1472
|
zitadel: ZitadelPort;
|
|
1451
1473
|
mail: MailPort;
|
|
1452
1474
|
lettermint: LettermintPort;
|
|
@@ -1694,11 +1716,32 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
|
|
|
1694
1716
|
private readonly handledSecrets;
|
|
1695
1717
|
constructor(ports: FoundryStepExecutorPorts);
|
|
1696
1718
|
secretsHandled(): readonly string[];
|
|
1719
|
+
/**
|
|
1720
|
+
* An own org's step runs on a copy of this executor whose ZITADEL, mail,
|
|
1721
|
+
* broker, files, jobs and calls ports are the org's own. A port the org has
|
|
1722
|
+
* not got yet is absent or throws, never the shared one.
|
|
1723
|
+
*/
|
|
1724
|
+
private executeScoped;
|
|
1725
|
+
/** Whether `ensureOwnAuthMailToken` can mint: the same two inputs it blocks on. */
|
|
1726
|
+
ownMailKeyMintable(): boolean;
|
|
1727
|
+
/** What the executor's own files dependencies supply, for the planner's blocked lines. */
|
|
1728
|
+
ownFilesDeps(): {
|
|
1729
|
+
keys: boolean;
|
|
1730
|
+
bucket: boolean;
|
|
1731
|
+
};
|
|
1732
|
+
/** Which ports the own files storage key step needs this executor has, for the planner's blocked line. */
|
|
1733
|
+
ownFilesR2Ports(org: DeclaredOrg, recordedIds: Readonly<Record<string, string>>): Promise<{
|
|
1734
|
+
r2Keys: boolean;
|
|
1735
|
+
storeR2Credentials: boolean;
|
|
1736
|
+
}>;
|
|
1737
|
+
/** Whether the host's scoped ports for this own org include a files-organisation port. A held key still missing counts as no port. */
|
|
1738
|
+
ownFilesOrganisationPort(org: DeclaredOrg, recordedIds: Readonly<Record<string, string>>): Promise<boolean>;
|
|
1697
1739
|
/** #555: the edge-routed ports this executor was built with itself (they win over edge). */
|
|
1698
1740
|
ownEdgePorts(): Partial<Record<EdgePortName, boolean>>;
|
|
1699
1741
|
execute(action: StepAction, context: StepExecutionContext): Promise<{
|
|
1700
1742
|
resourceId?: string;
|
|
1701
1743
|
} | undefined>;
|
|
1744
|
+
private run;
|
|
1702
1745
|
/**
|
|
1703
1746
|
* `plan/diff.ts`'s `planAccess` (#246): puts a `hosting.access` rule's
|
|
1704
1747
|
* hostname behind a Cloudflare Access app named
|
|
@@ -2189,6 +2232,22 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
|
|
|
2189
2232
|
* (which must never delete the platform's own project) change.
|
|
2190
2233
|
*/
|
|
2191
2234
|
private ensureOrgLettermintRelay;
|
|
2235
|
+
/**
|
|
2236
|
+
* The company's own sign-in mail token: a Lettermint project `<slug>-auth`
|
|
2237
|
+
* whose token goes to `orgTarget(org).refs.brokerMailKey`, where the broker
|
|
2238
|
+
* and ZITADEL's SMTP settings read it. The platform-wide Lettermint key
|
|
2239
|
+
* stays with the executor and never reaches a company. A held ref is left
|
|
2240
|
+
* alone. Otherwise the mint rule of {@link ensureOrgAppMailToken}: create
|
|
2241
|
+
* the project, restrict the domain, deliver its token, and only then
|
|
2242
|
+
* delete every other `<slug>-auth` project (a token cannot be read back,
|
|
2243
|
+
* so a project without a held token is a leftover). The restriction comes
|
|
2244
|
+
* before the delivery so a failure there leaves no held ref: the next run
|
|
2245
|
+
* starts over instead of skipping it. A failure before the delivery leaves
|
|
2246
|
+
* that project for the next run to delete. Every Lettermint call is
|
|
2247
|
+
* bounded; the token is registered for redaction before anything can throw
|
|
2248
|
+
* with it.
|
|
2249
|
+
*/
|
|
2250
|
+
private ensureOwnAuthMailToken;
|
|
2192
2251
|
/**
|
|
2193
2252
|
* Deletes a just-minted project from inside a `catch`. A failed delete is
|
|
2194
2253
|
* dropped so it never replaces the error being rethrown; the orphan is
|
|
@@ -2684,6 +2743,54 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
|
|
|
2684
2743
|
*/
|
|
2685
2744
|
private zonePort;
|
|
2686
2745
|
private jobsPort;
|
|
2746
|
+
/**
|
|
2747
|
+
* `plan/own-diff.ts`'s `planOwnJobs`: the org's own scheduler, in the org's
|
|
2748
|
+
* own project, with the names and the held-key ref of `orgTarget(org)`. The
|
|
2749
|
+
* admin token goes to `jobs:org:<slug>:adminToken` and nowhere else.
|
|
2750
|
+
*/
|
|
2751
|
+
private ownJobs;
|
|
2752
|
+
/**
|
|
2753
|
+
* `plan/own-diff.ts`'s `planOwnFiles`: the org's own files host in the
|
|
2754
|
+
* org's own project, trusting the company's issuer `https://auth.<domain>`.
|
|
2755
|
+
* Its ZITADEL project `<slug>-files` and introspection app are made in the
|
|
2756
|
+
* company's instance (`ownServices.zitadelFor`), never the shared one. The
|
|
2757
|
+
* bucket comes from the host's bucket port under the target's name. The
|
|
2758
|
+
* management key goes to `files:org:<slug>:managementKey` and the
|
|
2759
|
+
* introspection secret to `zitadel:org:<slug>:files:introspection`, both
|
|
2760
|
+
* once, and neither is recorded or returned.
|
|
2761
|
+
*/
|
|
2762
|
+
private ownFiles;
|
|
2763
|
+
/**
|
|
2764
|
+
* `plan/own-diff.ts`'s `planOwnFilesR2`: the storage key of the org's own files bucket. A token
|
|
2765
|
+
* recorded as undelivered is deleted first (its secret cannot be read back, so it can never be
|
|
2766
|
+
* finished), then one is minted, registered as a secret, recorded by id as undelivered and
|
|
2767
|
+
* persisted, and only then sent to the files host, the same pair on every retry. The secret is
|
|
2768
|
+
* never recorded and never written to a Coolify env. The id is recorded once the host has it.
|
|
2769
|
+
*/
|
|
2770
|
+
private ownFilesR2;
|
|
2771
|
+
/**
|
|
2772
|
+
* `plan/own-diff.ts`'s `planOwnAuth`: the org's own sign-in at
|
|
2773
|
+
* `auth.<domain>`, in the org's own project, through the org's own edge
|
|
2774
|
+
* routed DNS and tunnel ports. The instance owner token goes to
|
|
2775
|
+
* `zitadel:org:<slug>:instanceBearer` and is never recorded. A changed
|
|
2776
|
+
* declared auth ref patches both git apps' branch and redeploys them; the
|
|
2777
|
+
* applied marker comes last, so a run that died halfway is planned again.
|
|
2778
|
+
*/
|
|
2779
|
+
private ownAuth;
|
|
2780
|
+
/**
|
|
2781
|
+
* `plan/own-diff.ts`'s `planOwnMail`: the org's own Stalwart in the org's own
|
|
2782
|
+
* project, with no host port (the shared relay reaches it by internal name)
|
|
2783
|
+
* and a directory that trusts the org's own sign-in. The admin key goes to
|
|
2784
|
+
* `orgTarget(org).refs.mailAdminKey` and nowhere else. The address the relay
|
|
2785
|
+
* routes to is recorded last, and the step's marker after it.
|
|
2786
|
+
*/
|
|
2787
|
+
private ownMail;
|
|
2788
|
+
/**
|
|
2789
|
+
* `plan/own-diff.ts`'s `planOwnSfu`: the org's own LiveKit server in the
|
|
2790
|
+
* org's project, on the ports of its slot. The company's key pair goes to
|
|
2791
|
+
* `livekit:org:<slug>:keys` and the app's env, nowhere else.
|
|
2792
|
+
*/
|
|
2793
|
+
private ownSfu;
|
|
2687
2794
|
private jobsRegistration;
|
|
2688
2795
|
/** `deliverSecret`, tried up to {@link JOBS_DELIVERY_ATTEMPTS} times; true once one attempt succeeds. */
|
|
2689
2796
|
private deliverWithRetry;
|
|
@@ -2937,6 +3044,11 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
|
|
|
2937
3044
|
* #491: when `mailDomain` is the platform's own apex, this must never
|
|
2938
3045
|
* delete the platform's own project (the delete loop skips it by id) or
|
|
2939
3046
|
* the platform's own domain (returns before the domain lookup/delete).
|
|
3047
|
+
*
|
|
3048
|
+
* An org whose own sign-in is applied keeps its `<slug>-auth` project, and
|
|
3049
|
+
* keeps the Lettermint domain when that is the domain the sign-in sends
|
|
3050
|
+
* from. Every other org deletes `<slug>-auth` too, which only a failed
|
|
3051
|
+
* first sign-in run can have left behind.
|
|
2940
3052
|
*/
|
|
2941
3053
|
private parkLettermintMail;
|
|
2942
3054
|
/**
|