@wtfalch/foundry 0.33.1 → 0.35.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bootstrap/bootstrap.d.ts +1 -1
- package/dist/bootstrap/bootstrap.js +49 -11
- package/dist/bootstrap/declare.d.ts +12 -5
- package/dist/bootstrap/declare.js +33 -18
- package/dist/bootstrap/versions.d.ts +24 -0
- package/dist/bootstrap/versions.js +46 -0
- package/dist/calls-sfu.d.ts +10 -0
- package/dist/calls-sfu.js +44 -3
- package/dist/index.d.ts +21 -8
- package/dist/index.js +11 -4
- package/dist/migrations/0051_org_services.sql +13 -0
- package/dist/migrations/0052_platform_mail_relay.sql +8 -0
- package/dist/migrations/0053_heartbeats_provider.sql +9 -0
- package/dist/migrations/0053_org_version_pin.sql +6 -0
- package/dist/org.d.ts +76 -12
- package/dist/org.js +136 -20
- package/dist/plan/backup-gate.d.ts +93 -0
- package/dist/plan/backup-gate.js +261 -0
- package/dist/plan/broker-entries.d.ts +9 -1
- package/dist/plan/broker-entries.js +20 -3
- package/dist/plan/build-record.d.ts +56 -0
- package/dist/plan/build-record.js +109 -0
- package/dist/plan/calls-matrix.d.ts +9 -0
- package/dist/plan/calls-matrix.js +18 -1
- package/dist/plan/cloudflared-connector.d.ts +7 -1
- package/dist/plan/cloudflared-connector.js +15 -2
- package/dist/plan/company-backups.d.ts +31 -0
- package/dist/plan/company-backups.js +101 -0
- package/dist/plan/deploy-health.d.ts +12 -2
- package/dist/plan/deploy-health.js +3 -1
- package/dist/plan/destinations.d.ts +73 -0
- package/dist/plan/destinations.js +217 -0
- package/dist/plan/diff.d.ts +46 -8
- package/dist/plan/diff.js +367 -126
- package/dist/plan/edge-access.d.ts +15 -0
- package/dist/plan/edge-access.js +21 -2
- package/dist/plan/executor.d.ts +276 -5
- package/dist/plan/executor.js +1111 -109
- 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/health-targets.d.ts +1 -3
- package/dist/plan/health-targets.js +36 -36
- package/dist/plan/held-key-refs.d.ts +15 -0
- package/dist/plan/held-key-refs.js +15 -0
- package/dist/plan/hostnames.d.ts +10 -0
- package/dist/plan/hostnames.js +14 -0
- package/dist/plan/index.d.ts +19 -6
- package/dist/plan/index.js +10 -3
- package/dist/plan/live.d.ts +11 -2
- package/dist/plan/live.js +16 -2
- package/dist/plan/locked-hostnames.d.ts +7 -3
- package/dist/plan/locked-hostnames.js +25 -4
- package/dist/plan/login-host.d.ts +61 -12
- package/dist/plan/login-host.js +99 -11
- package/dist/plan/mail-relay.d.ts +139 -0
- package/dist/plan/mail-relay.js +400 -0
- package/dist/plan/network-move.d.ts +20 -0
- package/dist/plan/network-move.js +41 -0
- package/dist/plan/oikos-ingress.d.ts +111 -0
- package/dist/plan/oikos-ingress.js +231 -0
- package/dist/plan/org-names.d.ts +30 -0
- package/dist/plan/org-names.js +108 -0
- package/dist/plan/own-diff.d.ts +170 -0
- package/dist/plan/own-diff.js +440 -0
- package/dist/plan/own-dns.d.ts +6 -0
- package/dist/plan/own-dns.js +15 -0
- package/dist/plan/own-hostnames.d.ts +27 -0
- package/dist/plan/own-hostnames.js +24 -0
- package/dist/plan/own-limits.d.ts +213 -0
- package/dist/plan/own-limits.js +232 -0
- package/dist/plan/own-services.d.ts +193 -0
- package/dist/plan/own-services.js +169 -0
- package/dist/plan/own-sfu.d.ts +69 -0
- package/dist/plan/own-sfu.js +256 -0
- package/dist/plan/plan-apply.d.ts +121 -10
- package/dist/plan/plan-apply.js +357 -18
- package/dist/plan/platform-diff.d.ts +155 -11
- package/dist/plan/platform-diff.js +271 -66
- package/dist/plan/platform-org.d.ts +107 -4
- package/dist/plan/platform-org.js +175 -3
- package/dist/plan/platform-providers.d.ts +474 -81
- package/dist/plan/platform-providers.js +1407 -560
- package/dist/plan/proxy-labels.d.ts +57 -11
- package/dist/plan/proxy-labels.js +94 -14
- package/dist/plan/run-options.d.ts +19 -0
- package/dist/plan/run-options.js +46 -0
- 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.d.ts +1 -1
- package/dist/plan/zitadel-login-policy.js +77 -9
- package/dist/plan/zitadel-system-api.d.ts +22 -1
- package/dist/plan/zitadel-system-api.js +49 -8
- package/dist/platform.d.ts +40 -0
- package/dist/platform.js +16 -0
- package/dist/provision.d.ts +2 -0
- package/dist/provision.js +3 -1
- package/dist/slug.d.ts +3 -1
- package/dist/slug.js +26 -3
- package/dist/store/postgres.js +93 -2
- package/dist/store/schema.d.ts +87 -0
- package/dist/store/schema.js +9 -0
- package/package.json +3 -3
- package/versions.digests.json +11 -0
- package/versions.json +3 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { type DeclaredOrg, type VersionPin } from '../org.js';
|
|
1
2
|
import type { DeclaredPlatform } from '../platform.js';
|
|
2
|
-
import type
|
|
3
|
+
import { type AgoraHostValues } from './agora.js';
|
|
3
4
|
/**
|
|
4
5
|
* #443: the no-domain company mail apex -- `<slug>.oikos.host`, the same
|
|
5
6
|
* fixed suffix `agora.ts`'s `agoraHostnameFor`, `boule.ts`'s
|
|
@@ -75,8 +76,8 @@ export interface OrgPlatformReadiness {
|
|
|
75
76
|
/** The AI host (#172), the same always-on shape as {@link filesReady}. */
|
|
76
77
|
aiReady: boolean;
|
|
77
78
|
/**
|
|
78
|
-
* The AI host's
|
|
79
|
-
* `AI_URL`. `null` or absent until the platform has
|
|
79
|
+
* The AI host's public origin (`https://<platform.ai.hostname>`), which an entry with `uses.ai`
|
|
80
|
+
* gets as `AI_URL`. `null` or absent until the platform has applied the AI host.
|
|
80
81
|
*/
|
|
81
82
|
aiUrl?: string | null;
|
|
82
83
|
/**
|
|
@@ -86,8 +87,30 @@ export interface OrgPlatformReadiness {
|
|
|
86
87
|
* read, so "applied" has no separate check.
|
|
87
88
|
*/
|
|
88
89
|
callsReady: boolean;
|
|
90
|
+
/** The shared oikos.host ingress (tunnel, wildcard record, rate limit) is applied. Absent reads as false. */
|
|
91
|
+
oikosIngressApplied?: boolean;
|
|
92
|
+
/**
|
|
93
|
+
* The broker app Archon last registered on (`PLATFORM_ARCHON_APPLIED_BROKER_SCOPE_KEY`). The
|
|
94
|
+
* owner's own sign-in counts that registration as its broker's first entry: the broker starts
|
|
95
|
+
* on it and the login client is minted once it is there. Absent or `null` when Archon has not
|
|
96
|
+
* registered.
|
|
97
|
+
*/
|
|
98
|
+
archonBrokerAppId?: string | null;
|
|
99
|
+
/**
|
|
100
|
+
* The ZITADEL project and client ids the platform made in the owner company's own ZITADEL
|
|
101
|
+
* (Archon's and the Access provider's). The owner company's page counts them as the
|
|
102
|
+
* platform's, not as strangers. Absent when none is recorded.
|
|
103
|
+
*/
|
|
104
|
+
platformZitadelIds?: readonly string[];
|
|
89
105
|
/** The platform's own sign-in (#168), which every org's own ZITADEL org lives inside. */
|
|
90
106
|
authReady: boolean;
|
|
107
|
+
/**
|
|
108
|
+
* The host of the platform's own shared sign-in when its Coolify app is still recorded
|
|
109
|
+
* (`coolify:app:platform:auth`), whether or not this build declares it. Absent on a platform
|
|
110
|
+
* built from empty. The owner company's own sign-in takes the same name on the apex, so it
|
|
111
|
+
* waits while this is set.
|
|
112
|
+
*/
|
|
113
|
+
platformAuthRecordedHost?: string;
|
|
91
114
|
/** `https://<platform.auth.hostname>`, the platform's own sign-in issuer; `null` until {@link authReady}. The owner invite reads its issuer here, never from Agora's host config. */
|
|
92
115
|
authIssuer: string | null;
|
|
93
116
|
/** The Access provider id the platform recorded (`ACCESS_PROVIDER_RECORD_KEY`), which every gate requires. `null` or absent until the platform has recorded it. */
|
|
@@ -141,7 +164,7 @@ export interface OrgPlatformReadiness {
|
|
|
141
164
|
};
|
|
142
165
|
/**
|
|
143
166
|
* The platform's Lettermint key (`PLATFORM_AUTH_BROKER_MAIL_KEY_REF`) is held,
|
|
144
|
-
* so
|
|
167
|
+
* so the owner company's mail can be planned. Absent reads as not held.
|
|
145
168
|
*/
|
|
146
169
|
mailKeyHeld?: boolean;
|
|
147
170
|
/** The host's held keys can `put`, so an entry env from `sharedSecret` can be filled. Absent reads as false. */
|
|
@@ -151,6 +174,19 @@ export interface OrgPlatformReadiness {
|
|
|
151
174
|
* A missing key is then reported as "no reader given" instead of "not held".
|
|
152
175
|
*/
|
|
153
176
|
noKeyReader?: boolean;
|
|
177
|
+
/** The platform's declared git ref for each service, which an own org's services deploy. */
|
|
178
|
+
serviceGitRefs?: {
|
|
179
|
+
auth: string;
|
|
180
|
+
mail: string;
|
|
181
|
+
files: string;
|
|
182
|
+
jobs: string;
|
|
183
|
+
};
|
|
184
|
+
/**
|
|
185
|
+
* The user id an own org's files host takes as `AUTHZ_BOOTSTRAP_OPERATOR`: the owner company's
|
|
186
|
+
* owner in the owner company's own ZITADEL ({@link platformOwnerUserIdFrom}), or `'none'` when
|
|
187
|
+
* the owner company has no owner to invite. `null` or absent until recorded.
|
|
188
|
+
*/
|
|
189
|
+
platformOwnerUserId?: string | null;
|
|
154
190
|
}
|
|
155
191
|
/**
|
|
156
192
|
* What an org's plan reads when no platform is declared: nothing is ready, so
|
|
@@ -207,6 +243,26 @@ heldRefs?: ReadonlySet<string>,
|
|
|
207
243
|
noKeyReader?: boolean,
|
|
208
244
|
/** #317: true when the host's executor was built with an `EdgePort` ({@link OrgPlatformReadiness.edgeReady}). */
|
|
209
245
|
edgePortPresent?: boolean): OrgPlatformReadiness;
|
|
246
|
+
/** The git refs the platform declares for the services an own org deploys; a company's `pin` wins per part. */
|
|
247
|
+
export declare function serviceGitRefsOf(platform: DeclaredPlatform, pin?: VersionPin | null): {
|
|
248
|
+
auth: string;
|
|
249
|
+
mail: string;
|
|
250
|
+
files: string;
|
|
251
|
+
jobs: string;
|
|
252
|
+
};
|
|
253
|
+
/** The runner app both the platform's and an own org's files step record. */
|
|
254
|
+
export declare const OWN_FILES_RUNNER_SCOPE_KEY = "coolify_platform_files_runner_app";
|
|
255
|
+
/**
|
|
256
|
+
* An own org (`services.own`) plans against its own services, so its readiness
|
|
257
|
+
* reads only its own recorded ids and its own held keys; nothing the shared
|
|
258
|
+
* services recorded reaches it. AI, edge and Access stay shared and come from
|
|
259
|
+
* `base`. Any other org gets `base` back unchanged.
|
|
260
|
+
*/
|
|
261
|
+
export declare function orgPlatformReadinessFor(org: DeclaredOrg, base: OrgPlatformReadiness, orgRecordedIds: Readonly<Record<string, string>>, heldRefs: ReadonlySet<string>): OrgPlatformReadiness;
|
|
262
|
+
/** The own org's held-key refs `heldKeys` resolves right now. A resolver that throws counts as not held, and its error goes nowhere. */
|
|
263
|
+
export declare function heldOwnKeyRefs(heldKeys: HeldKeyResolver, org: Pick<DeclaredOrg, 'slug'>): Promise<Set<string>>;
|
|
264
|
+
/** {@link agoraHostValuesFromPlatform}'s counterpart for an own org: every value is the org's own, null until recorded. */
|
|
265
|
+
export declare function agoraHostValuesForOwn(org: DeclaredOrg, orgRecordedIds: Readonly<Record<string, string>>): AgoraHostValues;
|
|
210
266
|
/**
|
|
211
267
|
* #177: {@link AgoraHostValues} derived from the same platform state, rather
|
|
212
268
|
* than requiring a bespoke host-supplied `AgoraHostPort` for values the
|
|
@@ -223,3 +279,50 @@ edgePortPresent?: boolean): OrgPlatformReadiness;
|
|
|
223
279
|
* host-supplied `agora` port overrides it.
|
|
224
280
|
*/
|
|
225
281
|
export declare function agoraHostValuesFromPlatform(platform: DeclaredPlatform, platformRecordedIds: Readonly<Record<string, string>>): AgoraHostValues;
|
|
282
|
+
/**
|
|
283
|
+
* The owner company's own sign-in, as the platform plan reads it. `null` (the
|
|
284
|
+
* platform plan's input) until the owner's own sign-in step and its ZITADEL
|
|
285
|
+
* organisation are applied. `bearerRef` is a held-key reference, never a value.
|
|
286
|
+
*/
|
|
287
|
+
export interface OwnerSignIn {
|
|
288
|
+
issuerUrl: string;
|
|
289
|
+
organisationId: string;
|
|
290
|
+
brokerAppId: string;
|
|
291
|
+
bearerRef: string;
|
|
292
|
+
hostname: string;
|
|
293
|
+
/**
|
|
294
|
+
* The company's first owner in its own organisation (the CI-side half of
|
|
295
|
+
* Archon's first-owner link): the recorded user id, `'none'` when no owner
|
|
296
|
+
* is declared, absent while the owner step has recorded nothing.
|
|
297
|
+
*/
|
|
298
|
+
firstOwnerUserId?: string | null;
|
|
299
|
+
}
|
|
300
|
+
/** The owner company's own files host, as Archon's `FILES_*` env reads it. */
|
|
301
|
+
export interface OwnerFiles {
|
|
302
|
+
internalUrl: string;
|
|
303
|
+
audience: string;
|
|
304
|
+
organisationId: string;
|
|
305
|
+
managementKeyRef: string;
|
|
306
|
+
}
|
|
307
|
+
/** What the platform plan reads about the owner company. */
|
|
308
|
+
export interface PlatformOwnerInput {
|
|
309
|
+
signIn?: OwnerSignIn | null;
|
|
310
|
+
files?: OwnerFiles | null;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* The owner's sign-in from its declared org and the ids its own steps recorded.
|
|
314
|
+
* The host and issuer are the host the own-auth step last applied, and only
|
|
315
|
+
* while that is the declared `ownHostnames(org).auth`: a domain edit gives
|
|
316
|
+
* `null` until the step has applied the new host, so the owner's instance
|
|
317
|
+
* token is never sent to a host the step has not set up.
|
|
318
|
+
*/
|
|
319
|
+
export declare function ownerSignInFrom(owner: DeclaredOrg | null, recorded: Readonly<Record<string, string>>): OwnerSignIn | null;
|
|
320
|
+
/**
|
|
321
|
+
* The platform's operator identity for a company's files host: the owner company's first owner,
|
|
322
|
+
* as the owner's `zitadel:owner` step recorded it in the owner company's own ZITADEL. `'none'`
|
|
323
|
+
* when the owner company declares no owner; `null` until the step has recorded the id, and
|
|
324
|
+
* when there is no owner company running its own services.
|
|
325
|
+
*/
|
|
326
|
+
export declare function platformOwnerUserIdFrom(owner: DeclaredOrg | null, recorded: Readonly<Record<string, string>>): string | null;
|
|
327
|
+
/** The owner's files host once its own files step has applied; `null` before. */
|
|
328
|
+
export declare function ownerFilesFrom(owner: DeclaredOrg | null, recorded: Readonly<Record<string, string>>): OwnerFiles | null;
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
+
import { ownsServices } from '../org.js';
|
|
2
|
+
import { ZITADEL_ORG_SCOPE_KEY } from './agora.js';
|
|
1
3
|
import { ACCESS_PROVIDER_RECORD_KEY } from './gates.js';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
+
import { FILES_MANAGEMENT_KEY_REF, JOBS_ADMIN_TOKEN_REF, PLATFORM_AI_MANAGEMENT_KEY_REF, } from './held-key-refs.js';
|
|
5
|
+
import { OWN_AUTH_APPLIED_HOSTNAME_SCOPE_KEY, ownAuthApplied } from './login-host.js';
|
|
6
|
+
import { oikosIngressApplied } from './oikos-ingress.js';
|
|
7
|
+
import { ownHostnames } from './own-hostnames.js';
|
|
8
|
+
import { OWN_SERVICES_APPLIED_SCOPE_KEY, orgTarget } from './own-services.js';
|
|
9
|
+
import { JOBS_INTERNAL_URL_SCOPE_KEY, JOBS_SCHEDULER_KEY, PLATFORM_ACCESS_ZITADEL_APP_SCOPE_KEY, PLATFORM_ACCESS_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_AI_KEY, PLATFORM_AI_TUNNEL_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_BROKER_SCOPE_KEY, PLATFORM_ARCHON_ZITADEL_APP_SCOPE_KEY, PLATFORM_ARCHON_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_AUTH_BROKER_SCOPE_KEY, PLATFORM_AUTH_KEY, PLATFORM_EDGE_KEY, PLATFORM_FILES_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, PLATFORM_FILES_KEY, PLATFORM_MAIL_INTERNAL_URL_SCOPE_KEY, PLATFORM_MAIL_KEY, PLATFORM_MAIL_ZITADEL_PROJECT_SCOPE_KEY, platformMailMxHost, } from './platform-diff.js';
|
|
4
10
|
/**
|
|
5
11
|
* #443: the no-domain company mail apex -- `<slug>.oikos.host`, the same
|
|
6
12
|
* fixed suffix `agora.ts`'s `agoraHostnameFor`, `boule.ts`'s
|
|
@@ -132,11 +138,27 @@ edgePortPresent = false) {
|
|
|
132
138
|
? (platformRecordedIds[PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY] ?? null)
|
|
133
139
|
: null,
|
|
134
140
|
aiReady: aiApplied && missingKeys.ai === null,
|
|
135
|
-
|
|
141
|
+
// An entry runs on its company's own network and cannot dial the platform's container name, so it
|
|
142
|
+
// is given the host's public origin.
|
|
143
|
+
// Only once the route (tunnel, connector, DNS record) is recorded: before it the hostname answers nothing.
|
|
144
|
+
aiUrl: aiApplied && platformRecordedIds[PLATFORM_AI_TUNNEL_SCOPE_KEY]
|
|
145
|
+
? `https://${platform.ai.hostname}`
|
|
146
|
+
: null,
|
|
136
147
|
callsReady: platform.sfu.enabled,
|
|
148
|
+
oikosIngressApplied: oikosIngressApplied(platformRecordedIds),
|
|
149
|
+
archonBrokerAppId: platformRecordedIds[PLATFORM_ARCHON_APPLIED_BROKER_SCOPE_KEY] ?? null,
|
|
150
|
+
platformZitadelIds: [
|
|
151
|
+
PLATFORM_ARCHON_ZITADEL_PROJECT_SCOPE_KEY,
|
|
152
|
+
PLATFORM_ARCHON_ZITADEL_APP_SCOPE_KEY,
|
|
153
|
+
PLATFORM_ACCESS_ZITADEL_PROJECT_SCOPE_KEY,
|
|
154
|
+
PLATFORM_ACCESS_ZITADEL_APP_SCOPE_KEY,
|
|
155
|
+
].flatMap((key) => platformRecordedIds[key] || []),
|
|
137
156
|
...(platformRecordedIds[ACCESS_PROVIDER_RECORD_KEY]
|
|
138
157
|
? { accessProviderId: platformRecordedIds[ACCESS_PROVIDER_RECORD_KEY] }
|
|
139
158
|
: {}),
|
|
159
|
+
...(coolifyAppApplied(platformRecordedIds, PLATFORM_AUTH_KEY)
|
|
160
|
+
? { platformAuthRecordedHost: platform.auth.hostname }
|
|
161
|
+
: {}),
|
|
140
162
|
authReady,
|
|
141
163
|
authIssuer: authReady ? `https://${platform.auth.hostname}` : null,
|
|
142
164
|
jobsReady: jobsApplied && jobsUrl !== undefined && missingKeys.jobs === null,
|
|
@@ -147,6 +169,100 @@ edgePortPresent = false) {
|
|
|
147
169
|
...(noKeyReader ? { noKeyReader } : {}),
|
|
148
170
|
};
|
|
149
171
|
}
|
|
172
|
+
/** The git refs the platform declares for the services an own org deploys; a company's `pin` wins per part. */
|
|
173
|
+
export function serviceGitRefsOf(platform, pin) {
|
|
174
|
+
return {
|
|
175
|
+
auth: pin?.auth ?? platform.auth.gitRef,
|
|
176
|
+
mail: pin?.mail ?? platform.mail.gitRef,
|
|
177
|
+
files: pin?.files ?? platform.files.gitRef,
|
|
178
|
+
jobs: pin?.jobs ?? platform.jobs.gitRef,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
/** The runner app both the platform's and an own org's files step record. */
|
|
182
|
+
export const OWN_FILES_RUNNER_SCOPE_KEY = 'coolify_platform_files_runner_app';
|
|
183
|
+
const OWN_MAIL_APP_SCOPE_KEY = 'coolify_platform_mail_app';
|
|
184
|
+
/** Resource id of an own-service step, in `AppliedState.serviceIds`' `system:kind:key` form. */
|
|
185
|
+
function ownStepRecorded(ids, org, kind) {
|
|
186
|
+
return Boolean(ids[`coolify:${kind}:${org.id}`]);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* The own org's sign-in is up: applied, so its ZITADEL answers. The broker and the login client
|
|
190
|
+
* come after the org's first broker entry, as the platform's do after its first registration, so
|
|
191
|
+
* waiting for them here would wait for the very steps this gate lets run.
|
|
192
|
+
*/
|
|
193
|
+
function ownAuthReady(ids) {
|
|
194
|
+
return Boolean(ids[OWN_SERVICES_APPLIED_SCOPE_KEY]);
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* An own org (`services.own`) plans against its own services, so its readiness
|
|
198
|
+
* reads only its own recorded ids and its own held keys; nothing the shared
|
|
199
|
+
* services recorded reaches it. AI, edge and Access stay shared and come from
|
|
200
|
+
* `base`. Any other org gets `base` back unchanged.
|
|
201
|
+
*/
|
|
202
|
+
export function orgPlatformReadinessFor(org, base, orgRecordedIds, heldRefs) {
|
|
203
|
+
if (!ownsServices(org))
|
|
204
|
+
return base;
|
|
205
|
+
const refs = orgTarget(org).refs;
|
|
206
|
+
const authReady = ownAuthReady(orgRecordedIds);
|
|
207
|
+
const filesApplied = ownStepRecorded(orgRecordedIds, org, 'own-files') &&
|
|
208
|
+
Boolean(orgRecordedIds[OWN_FILES_RUNNER_SCOPE_KEY]);
|
|
209
|
+
const jobsApplied = ownStepRecorded(orgRecordedIds, org, 'own-jobs');
|
|
210
|
+
const mailApplied = ownStepRecorded(orgRecordedIds, org, 'own-mail') &&
|
|
211
|
+
Boolean(orgRecordedIds[OWN_MAIL_APP_SCOPE_KEY]);
|
|
212
|
+
const filesUrl = orgRecordedIds[PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY];
|
|
213
|
+
const jobsUrl = orgRecordedIds[JOBS_INTERNAL_URL_SCOPE_KEY];
|
|
214
|
+
const missing = (applied, ref) => applied && !heldRefs.has(ref) ? ref : null;
|
|
215
|
+
const missingKeys = {
|
|
216
|
+
files: missing(filesApplied, refs.filesManagement),
|
|
217
|
+
ai: base.missingKeys.ai,
|
|
218
|
+
jobs: missing(jobsApplied, refs.jobsAdminToken),
|
|
219
|
+
};
|
|
220
|
+
const { jobsUrlMissing: _shared, ...rest } = base;
|
|
221
|
+
return {
|
|
222
|
+
...rest,
|
|
223
|
+
authReady,
|
|
224
|
+
authIssuer: authReady ? ownHostnames(org).issuerUrl : null,
|
|
225
|
+
filesReady: filesApplied && filesUrl !== undefined && missingKeys.files === null,
|
|
226
|
+
filesUrl: filesApplied ? (filesUrl ?? null) : null,
|
|
227
|
+
jobsReady: jobsApplied && jobsUrl !== undefined && missingKeys.jobs === null,
|
|
228
|
+
...(jobsApplied && jobsUrl === undefined ? { jobsUrlMissing: true } : {}),
|
|
229
|
+
callsReady: ownStepRecorded(orgRecordedIds, org, 'own-sfu'),
|
|
230
|
+
// The MX name is the shared relay's, `mx.<platform apex>`: no platform mail step is needed.
|
|
231
|
+
// The apex is the company's mail zone: its domain when mail runs on it, else oikos.host.
|
|
232
|
+
mailHost: mailApplied && base.platformApex
|
|
233
|
+
? {
|
|
234
|
+
apex: org.domain && org.mail.usesDomain ? org.domain : NO_DOMAIN_MAIL_APEX,
|
|
235
|
+
mailHost: platformMailMxHost(base.platformApex),
|
|
236
|
+
}
|
|
237
|
+
: null,
|
|
238
|
+
missingKeys,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
/** The own org's held-key refs `heldKeys` resolves right now. A resolver that throws counts as not held, and its error goes nowhere. */
|
|
242
|
+
export async function heldOwnKeyRefs(heldKeys, org) {
|
|
243
|
+
const held = new Set();
|
|
244
|
+
for (const ref of Object.values(orgTarget(org).refs)) {
|
|
245
|
+
try {
|
|
246
|
+
if (await heldKeys.resolve(ref))
|
|
247
|
+
held.add(ref);
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
// not held
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return held;
|
|
254
|
+
}
|
|
255
|
+
/** {@link agoraHostValuesFromPlatform}'s counterpart for an own org: every value is the org's own, null until recorded. */
|
|
256
|
+
export function agoraHostValuesForOwn(org, orgRecordedIds) {
|
|
257
|
+
const authReady = ownAuthReady(orgRecordedIds);
|
|
258
|
+
return {
|
|
259
|
+
issuer: authReady ? ownHostnames(org).issuerUrl : null,
|
|
260
|
+
mailProjectId: orgRecordedIds[PLATFORM_MAIL_ZITADEL_PROJECT_SCOPE_KEY] ?? null,
|
|
261
|
+
messagingProjectId: null,
|
|
262
|
+
mailUpstream: orgRecordedIds[PLATFORM_MAIL_INTERNAL_URL_SCOPE_KEY] ?? null,
|
|
263
|
+
filesUpstream: orgRecordedIds[PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY] ?? null,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
150
266
|
/**
|
|
151
267
|
* #177: {@link AgoraHostValues} derived from the same platform state, rather
|
|
152
268
|
* than requiring a bespoke host-supplied `AgoraHostPort` for values the
|
|
@@ -180,3 +296,59 @@ export function agoraHostValuesFromPlatform(platform, platformRecordedIds) {
|
|
|
180
296
|
: null,
|
|
181
297
|
};
|
|
182
298
|
}
|
|
299
|
+
/**
|
|
300
|
+
* The owner's sign-in from its declared org and the ids its own steps recorded.
|
|
301
|
+
* The host and issuer are the host the own-auth step last applied, and only
|
|
302
|
+
* while that is the declared `ownHostnames(org).auth`: a domain edit gives
|
|
303
|
+
* `null` until the step has applied the new host, so the owner's instance
|
|
304
|
+
* token is never sent to a host the step has not set up.
|
|
305
|
+
*/
|
|
306
|
+
export function ownerSignInFrom(owner, recorded) {
|
|
307
|
+
if (!owner || !ownsServices(owner) || !ownAuthApplied(owner, recorded))
|
|
308
|
+
return null;
|
|
309
|
+
const organisationId = recorded[ZITADEL_ORG_SCOPE_KEY];
|
|
310
|
+
const brokerAppId = recorded[PLATFORM_AUTH_BROKER_SCOPE_KEY];
|
|
311
|
+
if (!organisationId || !brokerAppId)
|
|
312
|
+
return null;
|
|
313
|
+
const applied = recorded[OWN_AUTH_APPLIED_HOSTNAME_SCOPE_KEY];
|
|
314
|
+
if (!applied || applied !== ownHostnames(owner).auth)
|
|
315
|
+
return null;
|
|
316
|
+
const hostname = applied;
|
|
317
|
+
return {
|
|
318
|
+
issuerUrl: `https://${applied}`,
|
|
319
|
+
organisationId,
|
|
320
|
+
brokerAppId,
|
|
321
|
+
bearerRef: orgTarget(owner).refs.instanceBearer,
|
|
322
|
+
hostname,
|
|
323
|
+
firstOwnerUserId: platformOwnerUserIdFrom(owner, recorded),
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* The platform's operator identity for a company's files host: the owner company's first owner,
|
|
328
|
+
* as the owner's `zitadel:owner` step recorded it in the owner company's own ZITADEL. `'none'`
|
|
329
|
+
* when the owner company declares no owner; `null` until the step has recorded the id, and
|
|
330
|
+
* when there is no owner company running its own services.
|
|
331
|
+
*/
|
|
332
|
+
export function platformOwnerUserIdFrom(owner, recorded) {
|
|
333
|
+
if (!owner || !ownsServices(owner))
|
|
334
|
+
return null;
|
|
335
|
+
const first = owner.owners[0];
|
|
336
|
+
if (!first)
|
|
337
|
+
return 'none';
|
|
338
|
+
return recorded[`zitadel:owner:${owner.id}:${first.email.toLowerCase()}`] || null;
|
|
339
|
+
}
|
|
340
|
+
/** The owner's files host once its own files step has applied; `null` before. */
|
|
341
|
+
export function ownerFilesFrom(owner, recorded) {
|
|
342
|
+
if (!owner || !ownsServices(owner) || !recorded[`coolify:own-files:${owner.id}`])
|
|
343
|
+
return null;
|
|
344
|
+
const internalUrl = recorded[PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY];
|
|
345
|
+
const hostname = recorded[PLATFORM_FILES_APPLIED_HOSTNAME_SCOPE_KEY];
|
|
346
|
+
if (!internalUrl || !hostname)
|
|
347
|
+
return null;
|
|
348
|
+
return {
|
|
349
|
+
internalUrl,
|
|
350
|
+
audience: `https://${hostname}`,
|
|
351
|
+
organisationId: owner.id,
|
|
352
|
+
managementKeyRef: orgTarget(owner).refs.filesManagement,
|
|
353
|
+
};
|
|
354
|
+
}
|