@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
|
@@ -47,7 +47,7 @@ export interface BootstrapCompanyDeps {
|
|
|
47
47
|
readers: LiveReaders;
|
|
48
48
|
executor: StepExecutor;
|
|
49
49
|
/** The same flags `FoundryPlanApply` takes: a step whose port the host did not build is reported blocked. */
|
|
50
|
-
options?: Pick<FoundryPlanApplyOptions, 'ownerInviteMail' | 'broker'>;
|
|
50
|
+
options?: Pick<FoundryPlanApplyOptions, 'ownerInviteMail' | 'broker' | 'ownServices'>;
|
|
51
51
|
}
|
|
52
52
|
export interface BootstrapPlatformDeps {
|
|
53
53
|
/** Foundry's tables live here, already migrated by the host; the store, the audit log and the plan all use it. */
|
|
@@ -59,13 +59,18 @@ function allowBootstrapApply(withCompany) {
|
|
|
59
59
|
};
|
|
60
60
|
}
|
|
61
61
|
/**
|
|
62
|
-
* The org steps phase two applies: the sign-in
|
|
62
|
+
* The org steps phase two applies: the owner's own sign-in server and its zone,
|
|
63
|
+
* the sign-in org and its domain, the owner
|
|
63
64
|
* (whose user id Boule's `AUTHZ_FIRST_OWNER` carries), Boule's sign-in client,
|
|
64
65
|
* and Boule with its database. Everything else the company declares (chat,
|
|
65
66
|
* mail, Boule's broker entry, which waits for the org's mail domain, ...) is
|
|
66
67
|
* for a later, ordinary org apply.
|
|
67
68
|
*/
|
|
68
69
|
const COMPANY_SLICE_STEPS = new Set([
|
|
70
|
+
// The owner's own sign-in server, before anyone can sign in. Own mail, files,
|
|
71
|
+
// jobs and sfu follow after sign-in, as for every company.
|
|
72
|
+
'cloudflare:zone',
|
|
73
|
+
'coolify:own-auth',
|
|
69
74
|
'zitadel:issuer',
|
|
70
75
|
'zitadel:registration-closed',
|
|
71
76
|
'zitadel:domain',
|
|
@@ -77,8 +82,10 @@ const companySliceOnly = {
|
|
|
77
82
|
step: (action) => COMPANY_SLICE_STEPS.has(`${action.system}:${action.kind}`) ||
|
|
78
83
|
COMPANY_SLICE_STEPS.has(`${action.system}:${action.kind}:${action.key}`),
|
|
79
84
|
// `coolify:boule:mail` only says Boule runs without mail for now.
|
|
80
|
-
|
|
81
|
-
|
|
85
|
+
// `own-auth` is the unmanaged key `planOwnAuth` uses for a blocked sign-in server.
|
|
86
|
+
blocked: (key) => key === 'own-auth' ||
|
|
87
|
+
(key !== 'coolify:boule:mail' &&
|
|
88
|
+
[...COMPANY_SLICE_STEPS].some((step) => key === step || key.startsWith(`${step}:`))),
|
|
82
89
|
};
|
|
83
90
|
/** Shown instead of a phase-two error that could carry a value; the failing step's own name and redacted text are in `failed`. */
|
|
84
91
|
const COMPANY_NOT_FINISHED = "the company's own slice did not finish, so Archon has no people-store keys yet; run onboarding again";
|
|
@@ -118,6 +125,10 @@ function trackedOrgExecutor(inner, progress, redactor) {
|
|
|
118
125
|
}
|
|
119
126
|
},
|
|
120
127
|
...(inner.ownEdgePorts ? { ownEdgePorts: () => inner.ownEdgePorts?.() ?? {} } : {}),
|
|
128
|
+
// The own sign-in step plans without a held Lettermint key only when the executor can mint one.
|
|
129
|
+
...(inner.ownMailKeyMintable
|
|
130
|
+
? { ownMailKeyMintable: () => inner.ownMailKeyMintable?.() ?? false }
|
|
131
|
+
: {}),
|
|
121
132
|
};
|
|
122
133
|
}
|
|
123
134
|
/** The audit log with every free-text field run through the redactor before it is stored. */
|
|
@@ -334,10 +345,14 @@ export async function bootstrapPlatform(input, deps) {
|
|
|
334
345
|
return result;
|
|
335
346
|
},
|
|
336
347
|
...(inner.blockedSteps
|
|
337
|
-
? {
|
|
348
|
+
? {
|
|
349
|
+
blockedSteps: (platform, ids, owner) => inner.blockedSteps?.(platform, ids, owner) ?? {},
|
|
350
|
+
}
|
|
338
351
|
: {}),
|
|
339
352
|
...(inner.driftedSteps
|
|
340
|
-
? {
|
|
353
|
+
? {
|
|
354
|
+
driftedSteps: (platform, ids, owner) => inner.driftedSteps?.(platform, ids, owner) ?? {},
|
|
355
|
+
}
|
|
341
356
|
: {}),
|
|
342
357
|
};
|
|
343
358
|
let orgProgress = { started: 0, completed: 0 };
|
|
@@ -392,10 +407,11 @@ export async function bootstrapPlatform(input, deps) {
|
|
|
392
407
|
const blockedRaw = (driven.failed ? [] : (driven.lastPlan?.unmanaged ?? []))
|
|
393
408
|
.filter((item) => item.key !== undefined)
|
|
394
409
|
.map((item) => ({ step: item.key, message: item.description }));
|
|
410
|
+
const platformBlockedCount = blockedRaw.length;
|
|
395
411
|
if (companyDeps && !failed) {
|
|
396
412
|
// Phase two: the platform's own org. Its Boule needs the platform's
|
|
397
413
|
// sign-in, and Archon's people-store keys need that Boule.
|
|
398
|
-
const
|
|
414
|
+
const driveCompany = (label) => driveApply({
|
|
399
415
|
plan: () => planApply.plan(COMPANY_INCLUDED_ORG_ID),
|
|
400
416
|
apply: (plan, stepProgress) => {
|
|
401
417
|
orgProgress = stepProgress;
|
|
@@ -406,27 +422,30 @@ export async function bootstrapPlatform(input, deps) {
|
|
|
406
422
|
phase: 'plan',
|
|
407
423
|
system: roundSystem(plan),
|
|
408
424
|
round,
|
|
409
|
-
message:
|
|
425
|
+
message: `${label}company round ${round}: ${plan.steps.length} step(s) planned`,
|
|
410
426
|
steps: plan.steps.map((s) => s.description),
|
|
411
427
|
}),
|
|
412
428
|
onApplied: (round, plan) => emit({
|
|
413
429
|
phase: 'apply',
|
|
414
430
|
system: roundSystem(plan),
|
|
415
431
|
round,
|
|
416
|
-
message:
|
|
432
|
+
message: `${label}company round ${round}: applied`,
|
|
417
433
|
steps: plan.steps.map((s) => s.description),
|
|
418
434
|
}),
|
|
419
435
|
});
|
|
436
|
+
const company = await driveCompany('');
|
|
420
437
|
applied.push(...company.applied);
|
|
421
438
|
if (company.failed) {
|
|
422
439
|
failed = company.failed;
|
|
423
440
|
blockedRaw.push({ step: 'company', message: COMPANY_NOT_FINISHED });
|
|
424
441
|
}
|
|
425
442
|
else {
|
|
443
|
+
const companyBlockedStart = blockedRaw.length;
|
|
426
444
|
for (const item of company.lastPlan?.unmanaged ?? []) {
|
|
427
445
|
if (item.key !== undefined)
|
|
428
446
|
blockedRaw.push({ step: item.key, message: item.description });
|
|
429
447
|
}
|
|
448
|
+
const companyBlockedCount = blockedRaw.length - companyBlockedStart;
|
|
430
449
|
// Boule's broker entry needs the org's mail domain, which only the org's mail step
|
|
431
450
|
// records, so it is outside this slice. Until it is applied nobody can sign in to Boule:
|
|
432
451
|
// say so, and never report the onboarding complete.
|
|
@@ -444,12 +463,31 @@ export async function bootstrapPlatform(input, deps) {
|
|
|
444
463
|
failed = again.failed;
|
|
445
464
|
lastPlan = again.lastPlan;
|
|
446
465
|
if (!again.failed) {
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
466
|
+
// Archon's registration put the first entry on the owner's broker. Only now can the
|
|
467
|
+
// owner's sign-in mint its login client and start that broker, so its plan runs once more.
|
|
468
|
+
const signIn = await driveCompany('after Archon: ');
|
|
469
|
+
applied.push(...signIn.applied);
|
|
470
|
+
if (signIn.failed) {
|
|
471
|
+
failed = signIn.failed;
|
|
472
|
+
blockedRaw.push({ step: 'company', message: COMPANY_NOT_FINISHED });
|
|
473
|
+
}
|
|
474
|
+
else {
|
|
475
|
+
const fresh = [];
|
|
476
|
+
for (const item of signIn.lastPlan?.unmanaged ?? []) {
|
|
477
|
+
if (item.key !== undefined)
|
|
478
|
+
fresh.push({ step: item.key, message: item.description });
|
|
450
479
|
}
|
|
480
|
+
blockedRaw.splice(companyBlockedStart, companyBlockedCount, ...fresh);
|
|
451
481
|
}
|
|
452
482
|
}
|
|
483
|
+
if (!failed) {
|
|
484
|
+
// Archon was blocked on the owner's sign-in in phase one; this plan is the current truth.
|
|
485
|
+
blockedRaw.splice(0, platformBlockedCount);
|
|
486
|
+
const platformBlocked = (again.lastPlan?.unmanaged ?? [])
|
|
487
|
+
.filter((item) => item.key !== undefined)
|
|
488
|
+
.map((item) => ({ step: item.key, message: item.description }));
|
|
489
|
+
blockedRaw.unshift(...platformBlocked);
|
|
490
|
+
}
|
|
453
491
|
}
|
|
454
492
|
}
|
|
455
493
|
const blocked = blockedRaw.map((item) => ({
|
|
@@ -38,6 +38,11 @@ export interface BootstrapCompany {
|
|
|
38
38
|
name: string;
|
|
39
39
|
slug: string;
|
|
40
40
|
apex: string;
|
|
41
|
+
/**
|
|
42
|
+
* The owner company's own domain: absent means `apex`, `null` means none (it
|
|
43
|
+
* then runs on oikos.host). `apex` stays the platform's.
|
|
44
|
+
*/
|
|
45
|
+
domain?: string | null;
|
|
41
46
|
}
|
|
42
47
|
export interface BootstrapOwner {
|
|
43
48
|
givenName: string;
|
|
@@ -54,13 +59,15 @@ export interface BootstrapOwner {
|
|
|
54
59
|
*/
|
|
55
60
|
export declare function ownerIdentity(owner: BootstrapOwner): OwnerIdentity;
|
|
56
61
|
/**
|
|
57
|
-
* Company Included's declared org: the platform's owner,
|
|
58
|
-
*
|
|
59
|
-
*
|
|
62
|
+
* Company Included's declared org: the platform's owner, a company like any
|
|
63
|
+
* other (`services.own`: its own sign-in, mail, files and jobs). Tier 3 on the
|
|
64
|
+
* platform's apex from the start (#512) -- it runs on that domain with
|
|
65
|
+
* hosted mail, not a level-1 org whose domain gets set by hand afterward --
|
|
66
|
+
* unless `company.domain` is `null`, which runs it on oikos.host.
|
|
60
67
|
* `issuer`/`calls`/`site` go on the domain too, the same tier-3 defaults
|
|
61
68
|
* archon#388 gives every company declared there. `calls` and `site` stay
|
|
62
|
-
* disabled (calls is not in v1; no site is declared)
|
|
63
|
-
*
|
|
69
|
+
* disabled (calls is not in v1; no site is declared). Without a domain
|
|
70
|
+
* `calls.usesDomain` is off, as `validateCalls` requires. Chat stays off the domain: its
|
|
64
71
|
* re-key onto a per-org homeserver is not built yet (#463). The org's own
|
|
65
72
|
* sign-in still never collides with the shared ZITADEL instance's host --
|
|
66
73
|
* `loginHostnameFor` (#488) falls back to `<slug>-auth.oikos.host` whenever
|
|
@@ -36,10 +36,11 @@ export const DEFAULT_MAIL_IMAGE_TAG = VERSIONS.stalwart;
|
|
|
36
36
|
*/
|
|
37
37
|
export const DEFAULT_HC_IMAGE_TAG = VERSIONS.hc;
|
|
38
38
|
/**
|
|
39
|
-
* Every platform service
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
39
|
+
* Every platform service is enabled except `auth`, `mail`, `files`, `jobs`
|
|
40
|
+
* and `sfu`: the owner company runs its own copies of those like any
|
|
41
|
+
* company (`validatePlatform` refuses them on), so the platform keeps only
|
|
42
|
+
* what no company owns. A service whose dependencies the host did not supply
|
|
43
|
+
* is reported blocked, not planned. `edge` is on because every
|
|
43
44
|
* platform DNS record is written through it (F4.dns); its own Cloudflare API
|
|
44
45
|
* token is a held key (`cloudflare:edge:apiToken`), and until a human holds
|
|
45
46
|
* it the planner reports edge blocked, and auth, mail and archon with it.
|
|
@@ -47,16 +48,23 @@ export const DEFAULT_HC_IMAGE_TAG = VERSIONS.hc;
|
|
|
47
48
|
* the same as mail and archon.
|
|
48
49
|
*/
|
|
49
50
|
const SERVICES_ENABLED_BY_DEFAULT = new Set([
|
|
50
|
-
'auth',
|
|
51
|
-
'mail',
|
|
52
|
-
'files',
|
|
53
51
|
'ai',
|
|
54
|
-
'jobs',
|
|
55
52
|
'integrations',
|
|
56
53
|
'archon',
|
|
57
54
|
'edge',
|
|
58
55
|
'hc',
|
|
59
56
|
]);
|
|
57
|
+
/**
|
|
58
|
+
* The services every company deploys its own copy of. Their platform entries
|
|
59
|
+
* are off but still carry the release the own steps deploy
|
|
60
|
+
* (`serviceGitRefsOf`); with an empty one `own-auth` is blocked.
|
|
61
|
+
*/
|
|
62
|
+
const OWN_SERVICE_KEYS = new Set([
|
|
63
|
+
'auth',
|
|
64
|
+
'mail',
|
|
65
|
+
'files',
|
|
66
|
+
'jobs',
|
|
67
|
+
]);
|
|
60
68
|
/**
|
|
61
69
|
* The owner as `OwnerIdentity`, trimmed. Throws a plain error naming the
|
|
62
70
|
* field when one is empty or an address is not an address. The work address
|
|
@@ -75,33 +83,36 @@ export function ownerIdentity(owner) {
|
|
|
75
83
|
return identity;
|
|
76
84
|
}
|
|
77
85
|
/**
|
|
78
|
-
* Company Included's declared org: the platform's owner,
|
|
79
|
-
*
|
|
80
|
-
*
|
|
86
|
+
* Company Included's declared org: the platform's owner, a company like any
|
|
87
|
+
* other (`services.own`: its own sign-in, mail, files and jobs). Tier 3 on the
|
|
88
|
+
* platform's apex from the start (#512) -- it runs on that domain with
|
|
89
|
+
* hosted mail, not a level-1 org whose domain gets set by hand afterward --
|
|
90
|
+
* unless `company.domain` is `null`, which runs it on oikos.host.
|
|
81
91
|
* `issuer`/`calls`/`site` go on the domain too, the same tier-3 defaults
|
|
82
92
|
* archon#388 gives every company declared there. `calls` and `site` stay
|
|
83
|
-
* disabled (calls is not in v1; no site is declared)
|
|
84
|
-
*
|
|
93
|
+
* disabled (calls is not in v1; no site is declared). Without a domain
|
|
94
|
+
* `calls.usesDomain` is off, as `validateCalls` requires. Chat stays off the domain: its
|
|
85
95
|
* re-key onto a per-org homeserver is not built yet (#463). The org's own
|
|
86
96
|
* sign-in still never collides with the shared ZITADEL instance's host --
|
|
87
97
|
* `loginHostnameFor` (#488) falls back to `<slug>-auth.oikos.host` whenever
|
|
88
98
|
* `auth.<domain>` would equal the platform's own auth hostname.
|
|
89
99
|
*/
|
|
90
100
|
export function declaredCompanyOrg(company, owner) {
|
|
101
|
+
const domain = company.domain === undefined ? company.apex : company.domain;
|
|
91
102
|
return {
|
|
92
103
|
id: COMPANY_INCLUDED_ORG_ID,
|
|
93
104
|
slug: company.slug,
|
|
94
105
|
displayName: company.name,
|
|
95
|
-
domain
|
|
106
|
+
domain,
|
|
96
107
|
mail: {
|
|
97
108
|
mode: 'hosted',
|
|
98
|
-
usesDomain:
|
|
109
|
+
usesDomain: domain !== null,
|
|
99
110
|
ownerMailboxAddress: null,
|
|
100
111
|
noReplyLocalPart: 'no-reply',
|
|
101
112
|
},
|
|
102
113
|
chat: { enabled: true, usesDomain: false },
|
|
103
|
-
calls: { enabled: false, usesDomain:
|
|
104
|
-
issuer: { usesDomain:
|
|
114
|
+
calls: { enabled: false, usesDomain: domain !== null },
|
|
115
|
+
issuer: { usesDomain: domain !== null },
|
|
105
116
|
site: { enabled: false, usesDomain: true },
|
|
106
117
|
hosting: {
|
|
107
118
|
enabled: false,
|
|
@@ -119,6 +130,7 @@ export function declaredCompanyOrg(company, owner) {
|
|
|
119
130
|
buyer: { legalName: null, orgNumber: null, address: null, vatRegistered: null },
|
|
120
131
|
bespokeAppRepo: null,
|
|
121
132
|
adoptedApp: null,
|
|
133
|
+
services: { own: true },
|
|
122
134
|
owners: [ownerIdentity(owner)],
|
|
123
135
|
test: false,
|
|
124
136
|
};
|
|
@@ -134,7 +146,7 @@ export function declaredPlatformFor(apex, serverIpv4) {
|
|
|
134
146
|
services[key] = {
|
|
135
147
|
enabled,
|
|
136
148
|
hostname: defaultPlatformHostname(key, apex),
|
|
137
|
-
gitRef: !enabled
|
|
149
|
+
gitRef: !enabled && !OWN_SERVICE_KEYS.has(key)
|
|
138
150
|
? ''
|
|
139
151
|
: key === 'mail'
|
|
140
152
|
? DEFAULT_MAIL_IMAGE_TAG
|
|
@@ -148,9 +160,12 @@ export function declaredPlatformFor(apex, serverIpv4) {
|
|
|
148
160
|
apex,
|
|
149
161
|
...(serverIpv4 ? { serverIpv4 } : {}),
|
|
150
162
|
...services,
|
|
163
|
+
// The platform runs no mail server, but every company's mail goes through the shared relay.
|
|
164
|
+
mail: { ...services.mail, relay: true },
|
|
151
165
|
// Off, like `sfu`: the pin is an Archon release tag and digest, which exist
|
|
152
166
|
// only after Archon runs. Declared on with no pin, it would be reported
|
|
153
167
|
// blocked and the bootstrap would never be complete.
|
|
154
168
|
uptime: { enabled: false, tag: '', sha256: '' },
|
|
169
|
+
heartbeatsProvider: 'self-hosted',
|
|
155
170
|
};
|
|
156
171
|
}
|
|
@@ -13,9 +13,31 @@ export interface PinnedVersions {
|
|
|
13
13
|
cloudflared: string;
|
|
14
14
|
stalwart: string;
|
|
15
15
|
hc: string;
|
|
16
|
+
/** The mail relay's base image (an exact Alpine release); Postfix comes from its package repository. */
|
|
17
|
+
alpine: string;
|
|
18
|
+
/** A static busybox, copied into the MAS image so its health check has an HTTP client. */
|
|
19
|
+
busybox: string;
|
|
16
20
|
}
|
|
17
21
|
export declare function loadVersions(): PinnedVersions;
|
|
18
22
|
export declare const VERSIONS: PinnedVersions;
|
|
23
|
+
/**
|
|
24
|
+
* The multi-arch index digest of each tag in `versions.json`, in
|
|
25
|
+
* `versions.digests.json` beside it. Kept apart so every tag comparison
|
|
26
|
+
* (applied refs, version-change lines) stays on the tag. Validated on read:
|
|
27
|
+
* a malformed digest, or a key of `versions.json` without one, throws.
|
|
28
|
+
*/
|
|
29
|
+
export declare function loadDigests(): PinnedVersions;
|
|
30
|
+
export declare const DIGESTS: PinnedVersions;
|
|
31
|
+
/**
|
|
32
|
+
* `<tag>@<digest>` for an image string handed to Coolify or a Dockerfile.
|
|
33
|
+
* Only the image string uses it; the recorded applied ref stays the tag.
|
|
34
|
+
*/
|
|
35
|
+
export declare function pinnedRef(key: keyof PinnedVersions): string;
|
|
36
|
+
/**
|
|
37
|
+
* A declared tag that equals the pinned one gets its digest; any other
|
|
38
|
+
* declared tag (an override) is passed through unchanged.
|
|
39
|
+
*/
|
|
40
|
+
export declare function pinnedTagFor(key: keyof PinnedVersions, tag: string): string;
|
|
19
41
|
/**
|
|
20
42
|
* Why an image tag cannot be used, or `null`. #248: generalised out of what
|
|
21
43
|
* was `platform-providers.ts`'s `mailImageTagBlocked`, the one guard
|
|
@@ -24,3 +46,5 @@ export declare const VERSIONS: PinnedVersions;
|
|
|
24
46
|
* `"ZITADEL"`, `"LiveKit"`, ...); `example` is a real pinned tag to suggest.
|
|
25
47
|
*/
|
|
26
48
|
export declare function floatingTagBlocked(label: string, tag: string, example: string): string | null;
|
|
49
|
+
/** {@link floatingTagBlocked} for a git ref: it also refuses `main`, `master` and `HEAD`. */
|
|
50
|
+
export declare function floatingRefBlocked(label: string, ref: string, example: string): string | null;
|
|
@@ -10,6 +10,43 @@ export function loadVersions() {
|
|
|
10
10
|
return JSON.parse(readFileSync(VERSIONS_PATH, 'utf8'));
|
|
11
11
|
}
|
|
12
12
|
export const VERSIONS = loadVersions();
|
|
13
|
+
const DIGEST_PATTERN = /^sha256:[0-9a-f]{64}$/;
|
|
14
|
+
const DIGESTS_PATH = new URL('../../versions.digests.json', import.meta.url);
|
|
15
|
+
/**
|
|
16
|
+
* The multi-arch index digest of each tag in `versions.json`, in
|
|
17
|
+
* `versions.digests.json` beside it. Kept apart so every tag comparison
|
|
18
|
+
* (applied refs, version-change lines) stays on the tag. Validated on read:
|
|
19
|
+
* a malformed digest, or a key of `versions.json` without one, throws.
|
|
20
|
+
*/
|
|
21
|
+
export function loadDigests() {
|
|
22
|
+
const raw = JSON.parse(readFileSync(DIGESTS_PATH, 'utf8'));
|
|
23
|
+
if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {
|
|
24
|
+
throw new Error('versions.digests.json must be a JSON object');
|
|
25
|
+
}
|
|
26
|
+
const record = raw;
|
|
27
|
+
for (const key of Object.keys(loadVersions())) {
|
|
28
|
+
const value = record[key];
|
|
29
|
+
if (typeof value !== 'string' || !DIGEST_PATTERN.test(value)) {
|
|
30
|
+
throw new Error(`versions.digests.json: "${key}" must be a sha256:<64 hex> digest`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return record;
|
|
34
|
+
}
|
|
35
|
+
export const DIGESTS = loadDigests();
|
|
36
|
+
/**
|
|
37
|
+
* `<tag>@<digest>` for an image string handed to Coolify or a Dockerfile.
|
|
38
|
+
* Only the image string uses it; the recorded applied ref stays the tag.
|
|
39
|
+
*/
|
|
40
|
+
export function pinnedRef(key) {
|
|
41
|
+
return `${VERSIONS[key]}@${DIGESTS[key]}`;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* A declared tag that equals the pinned one gets its digest; any other
|
|
45
|
+
* declared tag (an override) is passed through unchanged.
|
|
46
|
+
*/
|
|
47
|
+
export function pinnedTagFor(key, tag) {
|
|
48
|
+
return tag === VERSIONS[key] ? pinnedRef(key) : tag;
|
|
49
|
+
}
|
|
13
50
|
/**
|
|
14
51
|
* Tag spellings that resolve to whatever the registry currently has, not a
|
|
15
52
|
* fixed image -- a re-apply of the same declaration can silently run a
|
|
@@ -41,3 +78,12 @@ export function floatingTagBlocked(label, tag, example) {
|
|
|
41
78
|
}
|
|
42
79
|
return null;
|
|
43
80
|
}
|
|
81
|
+
/** Branch names that move, refused for a git ref on top of the floating image tags. */
|
|
82
|
+
const FLOATING_GIT_REFS = new Set(['main', 'master', 'head']);
|
|
83
|
+
/** {@link floatingTagBlocked} for a git ref: it also refuses `main`, `master` and `HEAD`. */
|
|
84
|
+
export function floatingRefBlocked(label, ref, example) {
|
|
85
|
+
if (FLOATING_GIT_REFS.has(ref.trim().toLowerCase())) {
|
|
86
|
+
return `the ${label} image tag "${ref}" floats; declare a pinned release (for example ${example})`;
|
|
87
|
+
}
|
|
88
|
+
return floatingTagBlocked(label, ref, example);
|
|
89
|
+
}
|
package/dist/calls-sfu.d.ts
CHANGED
|
@@ -129,6 +129,16 @@ export declare const LIVEKIT_SIGNALLING_PORT = 7880;
|
|
|
129
129
|
export declare const LIVEKIT_MEDIA_TCP_PORT = 7881;
|
|
130
130
|
/** RTP media. Matches #122's "Decided" section exactly; not the wider range `docs/research/2026-09-19-meetings-research.md` lists for embedded TURN, which #122 does not turn on. */
|
|
131
131
|
export declare const LIVEKIT_MEDIA_UDP_RANGE = "50000:60000";
|
|
132
|
+
/**
|
|
133
|
+
* The ports of one slice of the Coolify server's media ports (`services.sfuSlot`):
|
|
134
|
+
* slot n holds UDP `50000 + (n-1)*500` to `50000 + n*500 - 1` and TCP `7881 + n`.
|
|
135
|
+
* Throws for a slot outside 1..{@link SFU_SLOT_MAX}.
|
|
136
|
+
*/
|
|
137
|
+
export declare function sfuSlotPorts(slot: number): {
|
|
138
|
+
udpStart: number;
|
|
139
|
+
udpEnd: number;
|
|
140
|
+
tcp: number;
|
|
141
|
+
};
|
|
132
142
|
/**
|
|
133
143
|
* The host's private network on `vmbr1`, where the calls service reaches
|
|
134
144
|
* LiveKit's API directly (#127's "Human steps": VM 105 gets a second NIC on
|
package/dist/calls-sfu.js
CHANGED
|
@@ -52,7 +52,8 @@
|
|
|
52
52
|
* `calls-sfu-deploy-support.ts`'s `fileWriteMode`), never logged, scrubbed
|
|
53
53
|
* from any error `calls-sfu-deploy.mjs` prints.
|
|
54
54
|
*/
|
|
55
|
-
import { VERSIONS } from './bootstrap/versions.js';
|
|
55
|
+
import { VERSIONS, pinnedRef } from './bootstrap/versions.js';
|
|
56
|
+
import { SFU_SLOT_MAX } from './org.js';
|
|
56
57
|
// ---------------------------------------------------------------------------
|
|
57
58
|
// The guard
|
|
58
59
|
// ---------------------------------------------------------------------------
|
|
@@ -119,6 +120,27 @@ export const LIVEKIT_SIGNALLING_PORT = 7880;
|
|
|
119
120
|
export const LIVEKIT_MEDIA_TCP_PORT = 7881;
|
|
120
121
|
/** RTP media. Matches #122's "Decided" section exactly; not the wider range `docs/research/2026-09-19-meetings-research.md` lists for embedded TURN, which #122 does not turn on. */
|
|
121
122
|
export const LIVEKIT_MEDIA_UDP_RANGE = '50000:60000';
|
|
123
|
+
/** The first UDP port of slot 1 and the width of every slot's UDP range (contract section 1). */
|
|
124
|
+
const SFU_SLOT_UDP_BASE = 50000;
|
|
125
|
+
const SFU_SLOT_UDP_WIDTH = 500;
|
|
126
|
+
/** Slot n's TCP fallback port is this plus n. */
|
|
127
|
+
const SFU_SLOT_TCP_BASE = 7881;
|
|
128
|
+
/**
|
|
129
|
+
* The ports of one slice of the Coolify server's media ports (`services.sfuSlot`):
|
|
130
|
+
* slot n holds UDP `50000 + (n-1)*500` to `50000 + n*500 - 1` and TCP `7881 + n`.
|
|
131
|
+
* Throws for a slot outside 1..{@link SFU_SLOT_MAX}.
|
|
132
|
+
*/
|
|
133
|
+
export function sfuSlotPorts(slot) {
|
|
134
|
+
if (!Number.isInteger(slot) || slot < 1 || slot > SFU_SLOT_MAX) {
|
|
135
|
+
throw new Error(`sfu slot must be an integer from 1 to ${SFU_SLOT_MAX}`);
|
|
136
|
+
}
|
|
137
|
+
const udpStart = SFU_SLOT_UDP_BASE + (slot - 1) * SFU_SLOT_UDP_WIDTH;
|
|
138
|
+
return {
|
|
139
|
+
udpStart,
|
|
140
|
+
udpEnd: udpStart + SFU_SLOT_UDP_WIDTH - 1,
|
|
141
|
+
tcp: SFU_SLOT_TCP_BASE + slot,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
122
144
|
/**
|
|
123
145
|
* The host's private network on `vmbr1`, where the calls service reaches
|
|
124
146
|
* LiveKit's API directly (#127's "Human steps": VM 105 gets a second NIC on
|
|
@@ -176,6 +198,11 @@ const UNSCOPED_SIGNALLING_RULE_PATTERN = new RegExp(`^${LIVEKIT_SIGNALLING_PORT}
|
|
|
176
198
|
export function hasUnscopedSignallingRule(status) {
|
|
177
199
|
return UNSCOPED_SIGNALLING_RULE_PATTERN.test(status);
|
|
178
200
|
}
|
|
201
|
+
/**
|
|
202
|
+
* Where `cloudflared` serves its metrics, which include `/ready`. The image has no shell, so its check is
|
|
203
|
+
* the binary's own `tunnel ready` in exec form, and that command asks this address.
|
|
204
|
+
*/
|
|
205
|
+
const CLOUDFLARED_METRICS_ADDRESS = '127.0.0.1:20241';
|
|
179
206
|
/**
|
|
180
207
|
* `network_mode: host`: LiveKit's own self-hosting guide recommends this
|
|
181
208
|
* over mapping ten thousand individual UDP ports, and the SFU VM has no
|
|
@@ -187,19 +214,33 @@ export function hasUnscopedSignallingRule(status) {
|
|
|
187
214
|
export function dockerComposeYaml() {
|
|
188
215
|
return `services:
|
|
189
216
|
livekit:
|
|
190
|
-
image:
|
|
217
|
+
image: livekit/livekit-server:${pinnedRef('livekit')}
|
|
191
218
|
restart: unless-stopped
|
|
192
219
|
network_mode: host
|
|
193
220
|
volumes:
|
|
194
221
|
- ${LIVEKIT_CONFIG_PATH}:/etc/livekit.yaml:ro
|
|
195
222
|
command: --config /etc/livekit.yaml
|
|
223
|
+
healthcheck:
|
|
224
|
+
test: ["CMD-SHELL", "wget -q -O /dev/null http://127.0.0.1:${LIVEKIT_SIGNALLING_PORT}/"]
|
|
225
|
+
interval: 30s
|
|
226
|
+
timeout: 5s
|
|
227
|
+
retries: 3
|
|
228
|
+
start_period: 30s
|
|
196
229
|
cloudflared:
|
|
197
|
-
image:
|
|
230
|
+
image: cloudflare/cloudflared:${pinnedRef('cloudflared')}
|
|
198
231
|
restart: unless-stopped
|
|
199
232
|
network_mode: host
|
|
200
233
|
env_file:
|
|
201
234
|
- ${CLOUDFLARED_ENV_PATH}
|
|
235
|
+
environment:
|
|
236
|
+
TUNNEL_METRICS: ${CLOUDFLARED_METRICS_ADDRESS}
|
|
202
237
|
command: tunnel --no-autoupdate run
|
|
238
|
+
healthcheck:
|
|
239
|
+
test: ["CMD", "cloudflared", "tunnel", "--metrics", "${CLOUDFLARED_METRICS_ADDRESS}", "ready"]
|
|
240
|
+
interval: 30s
|
|
241
|
+
timeout: 5s
|
|
242
|
+
retries: 3
|
|
243
|
+
start_period: 30s
|
|
203
244
|
`;
|
|
204
245
|
}
|
|
205
246
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export type { OrgId, MailMode, MailConfig, ChatConfig, CallsConfig, IssuerConfig, SiteConfig, IssuerAppKind, IssuerApp, HostingConfig, HostingEntry, EntryGate, HostingEntryUses, EntryAuth, EntryFiles, EntryAi, EnvValue, SsoConfig, GuestsConfig, SoloConfig, StorageConfig, AiConfig, BuyerAddress, BuyerIdentity, AdoptedAppForm, AdoptedApp, OwnerIdentity, OwnerMailbox, DeclaredOrg, OrgServiceSchema, } from './org.js';
|
|
2
|
-
export { APP_ENTRY_KEY, DEFAULT_GATE_LIMIT, FILES_ENTRY_PERMISSIONS, AI_ENTRY_PERMISSIONS, DEFAULT_MAIL_APPS, ORG_SERVICE_SCHEMAS, appEntry, ownerMailboxes, validateBuyerIdentity, validateHosting, validateMail, validateOwners, validateSite, } from './org.js';
|
|
3
|
-
export type { DeclaredPlatform, PlatformService, PlatformServiceKey, PlatformSfuService, } from './platform.js';
|
|
4
|
-
export { PLATFORM_SERVICE_KEYS, defaultPlatformHostname, validatePlatform, } from './platform.js';
|
|
1
|
+
export type { OrgId, MailMode, MailConfig, ChatConfig, CallsConfig, IssuerConfig, SiteConfig, IssuerAppKind, IssuerApp, HostingConfig, HostingEntry, EntryGate, HostingEntryUses, EntryAuth, EntryFiles, EntryAi, EnvValue, SsoConfig, GuestsConfig, SoloConfig, StorageConfig, AiConfig, BuyerAddress, BuyerIdentity, AdoptedAppForm, AdoptedApp, OwnerIdentity, OwnerMailbox, DeclaredOrg, OrgServiceSchema, ServicesConfig, VersionPin, VersionPinPart, } from './org.js';
|
|
2
|
+
export { APP_ENTRY_KEY, DEFAULT_GATE_LIMIT, FILES_ENTRY_PERMISSIONS, AI_ENTRY_PERMISSIONS, DEFAULT_MAIL_APPS, ORG_SERVICE_SCHEMAS, SFU_SLOT_MAX, VERSION_PIN_PARTS, appEntry, ownerMailboxes, ownsServices, validateBuyerIdentity, validateHosting, validateMail, validateOwners, validateServices, validateVersionPin, validateSite, } from './org.js';
|
|
3
|
+
export type { DeclaredPlatform, PlatformService, PlatformServiceKey, PlatformSfuService, HeartbeatsProvider, } from './platform.js';
|
|
4
|
+
export { PLATFORM_SERVICE_KEYS, HEARTBEATS_PROVIDERS, defaultPlatformHostname, validatePlatform, } from './platform.js';
|
|
5
5
|
import { type PinnedVersions } from './bootstrap/versions.js';
|
|
6
6
|
export type { PinnedVersions };
|
|
7
7
|
export declare const PINNED_VERSIONS: Readonly<PinnedVersions>;
|
|
@@ -18,17 +18,28 @@ export { createPostgresAppliedVersionsStore, createPostgresDeclaredOrgStore, cre
|
|
|
18
18
|
export { createPostgresAuditLog } from './store/audit.js';
|
|
19
19
|
export type { FoundryStoreDb } from './store/postgres.js';
|
|
20
20
|
export { declaredOrgs, foundryAppliedState, foundryAppliedVersions, foundryAuditEvents, foundryDeclaredPlatform, foundryRequestedChanges, type AuditEventRow, type DeclaredOrgRow, type NewDeclaredOrgRow, type AppliedStateRow, type NewAppliedStateRow, type AppliedVersionRow, type NewAppliedVersionRow, type DeclaredPlatformRow, type NewDeclaredPlatformRow, type RequestedChangeRow, type NewRequestedChangeRow, } from './store/schema.js';
|
|
21
|
-
export type { LiveResource, ZitadelReader, CoolifyReader, CloudflareReader, FilesReader, AiReader, CmsReader, LiveReaders, PlatformCloudflareReader, PlatformCoolifyReader, PlatformLiveReaders, StepAction, PlannedStep, StepSystem, PlanDetail, OrgPostgresPlan, PlatformPlanDetail, AdoptCandidate, AdoptInput, AdoptPlan, AdoptSnapshot, AuditActor, AuditLog, AuthzGate, StepExecutor, StepDestroyer, DestroyTarget, StepExecutionContext, PlatformStepExecutor, PlatformStepExecutionContext, FoundryPlanApplyOptions, FoundryApplyOptions, UnconfirmedUpgrade, } from './plan/index.js';
|
|
21
|
+
export type { LiveResource, ZitadelReader, CoolifyReader, CloudflareReader, FilesReader, AiReader, CmsReader, LiveReaders, PlatformCloudflareReader, PlatformCoolifyReader, PlatformLiveReaders, StepAction, PlannedStep, StepSystem, PlanDetail, OrgPostgresPlan, PlatformPlanDetail, MailRoute, AdoptCandidate, AdoptInput, AdoptPlan, AdoptSnapshot, AuditActor, AuditLog, AuthzGate, StepExecutor, StepDestroyer, DestroyTarget, StepExecutionContext, PlatformStepExecutor, PlatformStepExecutionContext, FoundryPlanApplyOptions, FoundryApplyOptions, UnconfirmedUpgrade, } from './plan/index.js';
|
|
22
|
+
export { COMPANY_DESTINATION_NAME_PREFIX, COMPANY_NETWORKS, DEFAULT_MAX_DESTINATIONS, DestinationCapacityError, PLATFORM_NETWORK, companyNetworkName, ensureDestination, findDestination, placed, placedOnPlatform, validateNetworkName, } from './plan/index.js';
|
|
22
23
|
export { AdoptRefusedError } from './plan/index.js';
|
|
24
|
+
export { NetworkMoveBackupMissingError, UpgradeBackupMissingError } from './plan/index.js';
|
|
25
|
+
export type { BackupGatePorts } from './plan/index.js';
|
|
23
26
|
export { createEdgeAccess, createEdgeDns, edgeDnsRecordOwners, createEdgeRateLimits, createEdgeTunnels, createEdgeWorkers, createEdgeZones, } from './plan/index.js';
|
|
24
27
|
export type { AccessAppSummary, AccessDecision, AccessPolicyRule, AccessPort, DnsPort, EdgePlatformAccess, EdgePlatformDns, EdgePlatformRateLimits, EdgePlatformWorkers, EdgePlatformZones, EdgeZonesOptions, ZonePort, EdgeRateLimitsOptions, RateLimitPort, RateLimitRuleInput, EdgeWorkersOptions, EdgePlatformTunnels, TunnelPort, } from './plan/index.js';
|
|
25
28
|
export { readLiveResources, readLivePlatformResources, computePlanDetail, toPlan, planPlatform, toPlatformPlan, JOBS_INTERNAL_URL_SCOPE_KEY, JOBS_SCHEDULER_KEY, PLATFORM_AI_KEY, PLATFORM_AI_INTERNAL_URL_SCOPE_KEY, PLATFORM_AI_MANAGEMENT_KEY_SCOPE_KEY, PLATFORM_AI_ORGANISATION_SCOPE_KEY, PLATFORM_FILES_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, PLATFORM_FILES_ZITADEL_PROJECT_SCOPE_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, PLATFORM_SFU_APPLIED_SCOPE_KEY, FoundryPlanApply, DeclaredOrgNotFoundError, StalePlanError, ConfirmationRequiredError, NotAuthorizedError, NotATestOrgError, DeclaredPlatformNotFoundError, PlatformStalePlanError, PlatformConfirmationRequiredError, UpgradeConfirmationRequiredError, planNeedsConfirmation, } from './plan/index.js';
|
|
26
29
|
export type { HeldKeyResolver, OrgPlatformReadiness } from './plan/index.js';
|
|
27
|
-
export { DEFAULT_ORG_PLATFORM_READINESS, ORG_PLATFORM_KEY_REFS, agoraHostValuesFromPlatform, heldPlatformKeyRefs, orgPlatformReadinessFrom, } from './plan/index.js';
|
|
30
|
+
export { DEFAULT_ORG_PLATFORM_READINESS, ORG_PLATFORM_KEY_REFS, agoraHostValuesForOwn, agoraHostValuesFromPlatform, heldOwnKeyRefs, heldPlatformKeyRefs, orgPlatformReadinessFor, orgPlatformReadinessFrom, } from './plan/index.js';
|
|
28
31
|
export type { PlatformOrgClientFactories } from './plan/index.js';
|
|
29
32
|
export { PlatformOrgBlockedError, PlatformOrgPorts } from './plan/index.js';
|
|
30
|
-
export type {
|
|
33
|
+
export type { FilesKeysMint, FilesKeysMinted, OwnFilesDeps, OwnFilesR2Key, OwnFilesR2Keys, OwnServicePorts, OwnServicesPort, OwnServicesProvision, OwnSfuDeps, ServiceTarget, } from './plan/index.js';
|
|
34
|
+
export { OWN_FILES_R2_SCOPE_KEY, OWN_FILES_R2_UNDELIVERED_SCOPE_KEY, OWN_SERVICES_APPLIED_SCOPE_KEY, OwnServicesBlockedError, orgTarget, } from './plan/index.js';
|
|
35
|
+
export type { FilesHostInput, JobsHostDeps, JobsHostInput, OwnAuthInstance, PlanOwnAuthOptions, PlanOwnFilesOptions, PlanOwnFilesR2Options, PlanOwnJobsOptions, } from './plan/index.js';
|
|
36
|
+
export { OWN_AUTH_APPLIED_GIT_REF_SCOPE_KEY, OWN_AUTH_APPLIED_HOSTNAME_SCOPE_KEY, OWN_AUTH_BLOCKED_KEY, OWN_FILES_BLOCKED_KEY, OWN_FILES_R2_BLOCKED_KEY, OWN_JOBS_BLOCKED_KEY, OWN_MAIL_BLOCKED_KEY, planOwnAuth, planOwnFiles, planOwnFilesR2, planOwnJobs, planOwnMail, provisionFilesHost, provisionJobsHost, provisionMailServer, } from './plan/index.js';
|
|
37
|
+
export type { PlanOwnSfuOptions, SfuAppDeps, SfuAppInput } from './plan/index.js';
|
|
38
|
+
export { OWN_SFU_BLOCKED_KEY, OWN_SFU_FIREWALL_KEY, planOwnSfu, provisionSfuApp, sfuConfig, sfuPortsMappings, } from './plan/index.js';
|
|
39
|
+
export type { CoolifyPort, CloudflarePort, ZitadelPort, CallsSfuPort, JobsSchedulerPort, FilesOrganisationPort, AiOrganisationPort, FoundryStepExecutorPorts, PlatformOrgPortSource, AgoraHostPort, AgoraHostValues, LoginHostBinding, LoginHostBindingsPort, LoginHostDiscoveryPort, } from './plan/index.js';
|
|
31
40
|
export { FoundryStepExecutor, NotBuiltError, healthUrls, loginHostnameFor } from './plan/index.js';
|
|
41
|
+
export { NO_DOMAIN_ZONE, ownHostnames } from './plan/index.js';
|
|
42
|
+
export type { OwnHostnames } from './plan/index.js';
|
|
32
43
|
export type { EnsureSignInOrgOptions } from './plan/index.js';
|
|
33
44
|
export { ZITADEL_ORG_DOMAIN_SCOPE_KEY, ensureSignInOrg, registerSignInDomain, signInOrgDomain, } from './plan/index.js';
|
|
34
45
|
export { HttpJobsSchedulerClient } from './plan/index.js';
|
|
@@ -42,7 +53,7 @@ export type { MailIdentityApp, MailIdentityAudienceCheck } from './plan/index.js
|
|
|
42
53
|
export { checkMailIdentityAudience } from './plan/index.js';
|
|
43
54
|
export type { MailAllowlistApp } from './plan/index.js';
|
|
44
55
|
export { checkMailAppsAllowlist } from './plan/index.js';
|
|
45
|
-
export { FoundryPlatformStepExecutor, ProvidersPlatformCloudflareReader, ProvidersPlatformCoolifyReader, createZitadelAdminMinter, createZitadelLoginPolicy, createZitadelProjectRoles, PLATFORM_AUTH_KEY, PLATFORM_MAIL_KEY, PLATFORM_MAIL_ADMIN_KEY_DESCRIPTION, } from './plan/index.js';
|
|
56
|
+
export { FoundryPlatformStepExecutor, ProvidersPlatformCloudflareReader, ProvidersPlatformCoolifyReader, createZitadelAdminMinter, createZitadelLoginPolicy, createZitadelProjectRoles, PLATFORM_AUTH_KEY, PLATFORM_MAIL_KEY, PLATFORM_MAIL_RELAY_KEY, PLATFORM_MAIL_RELAY_ROUTES_SCOPE_KEY, PLATFORM_MAIL_ADMIN_KEY_DESCRIPTION, } from './plan/index.js';
|
|
46
57
|
export type { PlatformAuthDeps, PlatformAuthSmtp, PlatformAuthRuntime, PlatformFilesDeps, PlatformFilesRuntime, PlatformEdgeDeps, PlatformDnsFactory, PlatformAccessFactory, PlatformUptimeAccessFactory, PlatformUptimeAccessPort, PlatformUptimeDeps, PlatformWorkersFactory, PlatformWorkersPort, PlatformAccessPort, PlatformTunnelsFactory, PlatformEdgeRuntime, } from './plan/index.js';
|
|
47
58
|
export type { PlatformMailDeps, PlatformMailAdminPort, PlatformMailRuntime, } from './plan/index.js';
|
|
48
59
|
export { FILES_INTROSPECTION_SECRET_REF, FILES_MANAGEMENT_ISSUER, FILES_MANAGEMENT_KEY_ID, FILES_MANAGEMENT_KEY_REF, PLATFORM_AUTH_INSTANCE_BEARER_REF, } from './plan/index.js';
|
|
@@ -68,3 +79,5 @@ export type { RunnerDb } from './runner/db.js';
|
|
|
68
79
|
export { AlreadyRunningError, RunNotFoundError, StepFailedError, StepMismatchError, } from './runner/errors.js';
|
|
69
80
|
export { createJobRunner } from './runner/runner.js';
|
|
70
81
|
export type { CreateJobRunnerOptions, JobRunner, RunFailure, RunRecord, RunStatus, RunStepsOptions, RunnerStep, } from './runner/types.js';
|
|
82
|
+
export type { StoredApp } from './plan/health-gaps.js';
|
|
83
|
+
export { EXIT_JOBS, REPO_DOCKERFILE_CHECKED, hasRealCheck, healthGaps, } from './plan/health-gaps.js';
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { APP_ENTRY_KEY, DEFAULT_GATE_LIMIT, FILES_ENTRY_PERMISSIONS, AI_ENTRY_PERMISSIONS, DEFAULT_MAIL_APPS, ORG_SERVICE_SCHEMAS, appEntry, ownerMailboxes, validateBuyerIdentity, validateHosting, validateMail, validateOwners, validateSite, } from './org.js';
|
|
2
|
-
export { PLATFORM_SERVICE_KEYS, defaultPlatformHostname, validatePlatform, } from './platform.js';
|
|
1
|
+
export { APP_ENTRY_KEY, DEFAULT_GATE_LIMIT, FILES_ENTRY_PERMISSIONS, AI_ENTRY_PERMISSIONS, DEFAULT_MAIL_APPS, ORG_SERVICE_SCHEMAS, SFU_SLOT_MAX, VERSION_PIN_PARTS, appEntry, ownerMailboxes, ownsServices, validateBuyerIdentity, validateHosting, validateMail, validateOwners, validateServices, validateVersionPin, validateSite, } from './org.js';
|
|
2
|
+
export { PLATFORM_SERVICE_KEYS, HEARTBEATS_PROVIDERS, defaultPlatformHostname, validatePlatform, } from './platform.js';
|
|
3
3
|
// archon#273: the pinned platform service versions the planner deploys, so a
|
|
4
4
|
// host can show declared against running. Readonly: it is the planner's own
|
|
5
5
|
// object, not a copy.
|
|
@@ -11,12 +11,18 @@ export { StaleLeaseError } from './store.js';
|
|
|
11
11
|
export { createPostgresAppliedVersionsStore, createPostgresDeclaredOrgStore, createPostgresPlatformStore, createPostgresRequestedChangeStore, DeclaredOrgError, PlatformError, } from './store/postgres.js';
|
|
12
12
|
export { createPostgresAuditLog } from './store/audit.js';
|
|
13
13
|
export { declaredOrgs, foundryAppliedState, foundryAppliedVersions, foundryAuditEvents, foundryDeclaredPlatform, foundryRequestedChanges, } from './store/schema.js';
|
|
14
|
+
export { COMPANY_DESTINATION_NAME_PREFIX, COMPANY_NETWORKS, DEFAULT_MAX_DESTINATIONS, DestinationCapacityError, PLATFORM_NETWORK, companyNetworkName, ensureDestination, findDestination, placed, placedOnPlatform, validateNetworkName, } from './plan/index.js';
|
|
14
15
|
export { AdoptRefusedError } from './plan/index.js';
|
|
16
|
+
export { NetworkMoveBackupMissingError, UpgradeBackupMissingError } from './plan/index.js';
|
|
15
17
|
export { createEdgeAccess, createEdgeDns, edgeDnsRecordOwners, createEdgeRateLimits, createEdgeTunnels, createEdgeWorkers, createEdgeZones, } from './plan/index.js';
|
|
16
18
|
export { readLiveResources, readLivePlatformResources, computePlanDetail, toPlan, planPlatform, toPlatformPlan, JOBS_INTERNAL_URL_SCOPE_KEY, JOBS_SCHEDULER_KEY, PLATFORM_AI_KEY, PLATFORM_AI_INTERNAL_URL_SCOPE_KEY, PLATFORM_AI_MANAGEMENT_KEY_SCOPE_KEY, PLATFORM_AI_ORGANISATION_SCOPE_KEY, PLATFORM_FILES_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, PLATFORM_FILES_ZITADEL_PROJECT_SCOPE_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, PLATFORM_SFU_APPLIED_SCOPE_KEY, FoundryPlanApply, DeclaredOrgNotFoundError, StalePlanError, ConfirmationRequiredError, NotAuthorizedError, NotATestOrgError, DeclaredPlatformNotFoundError, PlatformStalePlanError, PlatformConfirmationRequiredError, UpgradeConfirmationRequiredError, planNeedsConfirmation, } from './plan/index.js';
|
|
17
|
-
export { DEFAULT_ORG_PLATFORM_READINESS, ORG_PLATFORM_KEY_REFS, agoraHostValuesFromPlatform, heldPlatformKeyRefs, orgPlatformReadinessFrom, } from './plan/index.js';
|
|
19
|
+
export { DEFAULT_ORG_PLATFORM_READINESS, ORG_PLATFORM_KEY_REFS, agoraHostValuesForOwn, agoraHostValuesFromPlatform, heldOwnKeyRefs, heldPlatformKeyRefs, orgPlatformReadinessFor, orgPlatformReadinessFrom, } from './plan/index.js';
|
|
18
20
|
export { PlatformOrgBlockedError, PlatformOrgPorts } from './plan/index.js';
|
|
21
|
+
export { OWN_FILES_R2_SCOPE_KEY, OWN_FILES_R2_UNDELIVERED_SCOPE_KEY, OWN_SERVICES_APPLIED_SCOPE_KEY, OwnServicesBlockedError, orgTarget, } from './plan/index.js';
|
|
22
|
+
export { OWN_AUTH_APPLIED_GIT_REF_SCOPE_KEY, OWN_AUTH_APPLIED_HOSTNAME_SCOPE_KEY, OWN_AUTH_BLOCKED_KEY, OWN_FILES_BLOCKED_KEY, OWN_FILES_R2_BLOCKED_KEY, OWN_JOBS_BLOCKED_KEY, OWN_MAIL_BLOCKED_KEY, planOwnAuth, planOwnFiles, planOwnFilesR2, planOwnJobs, planOwnMail, provisionFilesHost, provisionJobsHost, provisionMailServer, } from './plan/index.js';
|
|
23
|
+
export { OWN_SFU_BLOCKED_KEY, OWN_SFU_FIREWALL_KEY, planOwnSfu, provisionSfuApp, sfuConfig, sfuPortsMappings, } from './plan/index.js';
|
|
19
24
|
export { FoundryStepExecutor, NotBuiltError, healthUrls, loginHostnameFor } from './plan/index.js';
|
|
25
|
+
export { NO_DOMAIN_ZONE, ownHostnames } from './plan/index.js';
|
|
20
26
|
export { ZITADEL_ORG_DOMAIN_SCOPE_KEY, ensureSignInOrg, registerSignInDomain, signInOrgDomain, } from './plan/index.js';
|
|
21
27
|
export { HttpJobsSchedulerClient } from './plan/index.js';
|
|
22
28
|
export { healthTargets, platformHealthTargets } from './plan/index.js';
|
|
@@ -24,7 +30,7 @@ export { ProvidersAiReader, ProvidersCloudflareReader, ProvidersCmsReader, Provi
|
|
|
24
30
|
export { adaptZitadelListApps } from './plan/index.js';
|
|
25
31
|
export { checkMailIdentityAudience } from './plan/index.js';
|
|
26
32
|
export { checkMailAppsAllowlist } from './plan/index.js';
|
|
27
|
-
export { FoundryPlatformStepExecutor, ProvidersPlatformCloudflareReader, ProvidersPlatformCoolifyReader, createZitadelAdminMinter, createZitadelLoginPolicy, createZitadelProjectRoles, PLATFORM_AUTH_KEY, PLATFORM_MAIL_KEY, PLATFORM_MAIL_ADMIN_KEY_DESCRIPTION, } from './plan/index.js';
|
|
33
|
+
export { FoundryPlatformStepExecutor, ProvidersPlatformCloudflareReader, ProvidersPlatformCoolifyReader, createZitadelAdminMinter, createZitadelLoginPolicy, createZitadelProjectRoles, PLATFORM_AUTH_KEY, PLATFORM_MAIL_KEY, PLATFORM_MAIL_RELAY_KEY, PLATFORM_MAIL_RELAY_ROUTES_SCOPE_KEY, PLATFORM_MAIL_ADMIN_KEY_DESCRIPTION, } from './plan/index.js';
|
|
28
34
|
export { FILES_INTROSPECTION_SECRET_REF, FILES_MANAGEMENT_ISSUER, FILES_MANAGEMENT_KEY_ID, FILES_MANAGEMENT_KEY_REF, PLATFORM_AUTH_INSTANCE_BEARER_REF, } from './plan/index.js';
|
|
29
35
|
export { AI_MANAGEMENT_ISSUER, AI_MANAGEMENT_KEY_ID, AI_SIGNING_KEY_REF, AiManagementKeyExistsError, createAiManagementKeyMinter, PLATFORM_AI_MANAGEMENT_KEY_REF, PLATFORM_AI_MANAGEMENT_PERMISSIONS, } from './plan/index.js';
|
|
30
36
|
export { MissingCallsSfuConnectionError, applyCallsSfuPlatformStep, callsSfuAppliedFingerprint, } from './plan/index.js';
|
|
@@ -40,3 +46,4 @@ export { bootstrapPlatform } from './bootstrap/index.js';
|
|
|
40
46
|
export { readPlatformVersionStatus, wtfalchVersionsFromPnpmLockfile, providersStatusCoolifyPort, ghCliStatusGithubPort, } from './plan/index.js';
|
|
41
47
|
export { AlreadyRunningError, RunNotFoundError, StepFailedError, StepMismatchError, } from './runner/errors.js';
|
|
42
48
|
export { createJobRunner } from './runner/runner.js';
|
|
49
|
+
export { EXIT_JOBS, REPO_DOCKERFILE_CHECKED, hasRealCheck, healthGaps, } from './plan/health-gaps.js';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
-- @wtfalch/foundry: a company's own services. `services_own` true gives the
|
|
2
|
+
-- org its own sign-in, mail, files, jobs and calls SFU; false (the default)
|
|
3
|
+
-- keeps the platform's shared ones. `services_sfu_slot` is the slice of the
|
|
4
|
+
-- server's SFU ports an own org's media server takes; no two orgs share one.
|
|
5
|
+
-- Mirrors src/store/schema.ts's `declaredOrgs` table additions.
|
|
6
|
+
|
|
7
|
+
alter table foundry_declared_orgs
|
|
8
|
+
add column if not exists services_own boolean not null default false,
|
|
9
|
+
add column if not exists services_sfu_slot integer;
|
|
10
|
+
|
|
11
|
+
create unique index if not exists foundry_declared_orgs_sfu_slot_idx
|
|
12
|
+
on foundry_declared_orgs (services_sfu_slot)
|
|
13
|
+
where services_sfu_slot is not null;
|