@wtfalch/foundry 0.61.0 → 0.62.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/index.d.ts +5 -1
- package/dist/index.js +2 -0
- package/dist/migrations/0065_native_build_runs.sql +32 -0
- package/dist/migrations/0066_org_kosmos.sql +10 -0
- package/dist/modules/sign-in.js +1 -1
- package/dist/native-build.d.ts +70 -0
- package/dist/native-build.js +92 -0
- package/dist/org.d.ts +22 -3
- package/dist/org.js +25 -2
- package/dist/plan/agora.d.ts +1 -1
- package/dist/plan/agora.js +4 -0
- package/dist/plan/blue-green.d.ts +19 -7
- package/dist/plan/blue-green.js +35 -8
- package/dist/plan/deploy-health.js +9 -2
- package/dist/plan/desktop-callback.js +4 -1
- package/dist/plan/diff.js +180 -63
- package/dist/plan/edge-access.js +1 -0
- package/dist/plan/executor.d.ts +40 -18
- package/dist/plan/executor.js +153 -49
- package/dist/plan/kosmos.d.ts +56 -0
- package/dist/plan/kosmos.js +84 -0
- package/dist/plan/locked-hostnames.js +5 -2
- package/dist/plan/org-names.d.ts +3 -3
- package/dist/plan/org-names.js +3 -1
- package/dist/plan/org-postgres-migrate.js +1 -0
- package/dist/plan/own-limits.d.ts +6 -0
- package/dist/plan/own-limits.js +1 -0
- package/dist/plan/platform-providers.js +6 -0
- package/dist/plan/post-logout-uris.d.ts +1 -1
- package/dist/plan/post-logout-uris.js +5 -2
- package/dist/plan/providers-readers.js +20 -1
- package/dist/release/migration-compatibility.d.ts +66 -0
- package/dist/release/migration-compatibility.js +340 -0
- package/dist/requested-changes.js +2 -1
- package/dist/slug.d.ts +1 -1
- package/dist/slug.js +11 -2
- package/dist/store/postgres.js +13 -0
- package/dist/store/schema.d.ts +36 -0
- package/dist/store/schema.js +6 -0
- package/package.json +2 -2
package/dist/plan/executor.js
CHANGED
|
@@ -5,7 +5,7 @@ import { EdgeApiError } from '@wtfalch/edge';
|
|
|
5
5
|
import { coolifyImageFor } from '../bootstrap/versions.js';
|
|
6
6
|
import { redactSecrets } from '../calls-sfu-deploy-support.js';
|
|
7
7
|
import { resolveCallsSfuUrls } from '../calls-sfu.js';
|
|
8
|
-
import { APPS_DOMAIN, APP_ENTRY_KEY, FULL_COMMIT_SHA, ORG_POSTGRES_SERVER_SCOPE_KEY, ORG_SERVICE_SCHEMAS, appsOn, companyGithubFor, filesCollectionEnvName, heldEnvRef, isLiveEntry, isMainEntry, mainEntry, ownerMailboxRecorded, ownerMailboxes, ownsServices, sharedSecretRef, } from '../org.js';
|
|
8
|
+
import { APPS_DOMAIN, APP_ENTRY_KEY, FULL_COMMIT_SHA, ORG_POSTGRES_SERVER_SCOPE_KEY, ORG_SERVICE_SCHEMAS, appsOn, companyGithubFor, filesCollectionEnvName, heldEnvRef, isLiveEntry, isMainEntry, kosmosOn, mainEntry, ownerMailboxRecorded, ownerMailboxes, ownsServices, sharedSecretRef, } from '../org.js';
|
|
9
9
|
import { adoptedKeysUnreadable, parseAdoptSnapshot } from './adopt.js';
|
|
10
10
|
import { AGORA_ACTIVATED_ENV_SCOPE_KEY, AGORA_APP_PORTS_EXPOSES, AGORA_CLIENT_ID_SCOPE_KEY, AGORA_DEPLOYED_SCOPE_KEY, APP_CLIENT_ID_SCOPE_KEY, CALLS_INTROSPECTION_SCOPE_KEY, CALLS_INTROSPECTION_UNDELIVERED_SCOPE_KEY, CALLS_SERVICE_DEPLOYED_SCOPE_KEY, CALLS_SERVICE_NAME_SCOPE_KEY, CALLS_SERVICE_PORT, CALLS_SERVICE_TOKEN_ENV_SCOPE_KEY, CHAT_CALLS_APPLIED_SCOPE_KEY, CHAT_CALLS_TOKEN_SCOPE_KEY, CHAT_CALLS_TOKEN_UNDELIVERED_SCOPE_KEY, CHAT_INTROSPECTION_SCOPE_KEY, CHAT_INTROSPECTION_UNDELIVERED_SCOPE_KEY, CHAT_SERVICE_URL_SCOPE_KEY, CMS_CLIENT_ID_SCOPE_KEY, DESKTOP_AUTH_PORT, ENTRY_CLIENT_ID_SCOPE_PREFIX, ZITADEL_MESSAGING_PROJECT_SCOPE_KEY, ZITADEL_ORG_SCOPE_KEY, agoraEnvDrift, agoraEnvRevision, agoraHostnameFor, callsIntrospectionAppName, callsServiceHasName, callsServiceInternalName, callsTokenEnvMarker, callsTokenEnvMovedMessage, chatCallsApplied, chatHasUpstreams, chatIntrospectionAppName, desiredAgoraEnv, desktopAuthInternalName, entryClientIdScopeKey, } from './agora.js';
|
|
11
11
|
import { APPS_DATABASE_ADMIN_URL_ENV, applyAppsDbRoute, retireAppsDbRoute, } from './apps-db-route.js';
|
|
@@ -19,7 +19,7 @@ import { AUTH_REGISTRAR_KEY_ENV, AUTH_REGISTRAR_URL_ENV, REGISTRAR_APPLIED_SCOPE
|
|
|
19
19
|
import { BROKER_WORKSPACES_DATABASE_SCOPE_KEY, canHoldWorkspacesDatabase, ensureBrokerWorkspacesDatabase, } from './broker-workspaces-database.js';
|
|
20
20
|
import { recordBuiltCommit } from './build-record.js';
|
|
21
21
|
import { CallError, withTimeout as withLabelledTimeout } from './call-timeout.js';
|
|
22
|
-
import { MAS_TAG, SYNAPSE_IMAGE, SYNAPSE_TAG, callsMasConfigYaml,
|
|
22
|
+
import { MAS_TAG, SYNAPSE_IMAGE, SYNAPSE_TAG, callsMasConfigYaml, callsSynapseHomeserverYaml, masDockerfile, mintCallsMatrixSecrets, parseCallsDbUrl, } from './calls-matrix.js';
|
|
23
23
|
import { CHAT_MAS_PORT, CHAT_REPO, CHAT_SERVICE_DOCKERFILE, CHAT_SERVICE_PORT, CHAT_SYNAPSE_DOCKERFILE, CHAT_SYNAPSE_PORT, assertChatServerName, chatInternalNames, chatMasConfigYaml, chatRekeyedInternalNames, chatServerName, chatServiceEnvs, chatSynapseHomeserverYaml, chatUpstreams, } from './chat-matrix.js';
|
|
24
24
|
import { ensureCloudflaredConnector } from './cloudflared-connector.js';
|
|
25
25
|
import { BACKUP_POSTGRES_DATABASES_SCOPE_KEY, BACKUP_SCOPE_PREFIX, COMPANY_BACKUP_DATABASE_NAMES, backupScopeKey, } from './company-backups.js';
|
|
@@ -50,6 +50,7 @@ import { IMAGE_HEALTHCHECK_SETTINGS, commandHealthCheckSettings, healthCheckSett
|
|
|
50
50
|
import { PROXY_LABEL_TOKEN_REF } from './held-key-refs.js';
|
|
51
51
|
import { appHostname, cmsStudioHostnameFor, coolifyTunnelDomains, entryHostnames, siteHostnameFor, tunnelDnsHostnames, tunnelIngressHostnames, workspaceHost, } from './hostnames.js';
|
|
52
52
|
import { KEYS_REGISTRAR_DELIVERY_ATTEMPTS, KEYS_REGISTRAR_KEY_DAYS, KEYS_REGISTRAR_KEY_ENV, KEYS_REGISTRAR_MINTED_AT_SCOPE_KEY, KEYS_REGISTRAR_MINT_STARTED_SCOPE_KEY, KEYS_REGISTRAR_SCOPE_KEY, KEYS_REGISTRAR_UNDELIVERED_SCOPE_KEY, KEYS_REGISTRAR_URL_APPLIED_SCOPE_KEY, KEYS_REGISTRAR_URL_ENV, } from './keys-registrar.js';
|
|
53
|
+
import { KOSMOS_CLIENT_ID_SCOPE_KEY, KOSMOS_DATABASE_SCOPE_KEY, KOSMOS_DEPLOYED_SCOPE_KEY, desiredKosmosEnv, kosmosEnvDrift, kosmosHostnameFor, } from './kosmos.js';
|
|
53
54
|
import { LOGIN_BINDING_SCOPE_KEY, LOGIN_CLIENT_ORGS_SCOPE_KEY, LOGIN_HOST_PENDING_REMOVAL_SCOPE_KEY, LOGIN_HOST_SHARED_SCOPE_KEY, OWN_CUSTOMERS_BASE_URI_SCOPE_KEY, clientOrganizationIds, clientOrganizationsState, customersClientOrganizations, customersEntryKeys, loginBindingState, loginHostClientIds, ownAuthApplied, pendingRemovalHosts, pendingRemovalState, } from './login-host.js';
|
|
54
55
|
import { machineBypassScopeKey } from './machine-paths.js';
|
|
55
56
|
import { MAIL_COPY_COMPLETED_SCOPE_KEY, MAIL_COPY_DONE_SCOPE_KEY, parseMailCopyCompleted, runMailCopyStep, } from './mail-copy.js';
|
|
@@ -392,6 +393,15 @@ export const BOULE_REPO = 'wtfalch/boule';
|
|
|
392
393
|
export const BOULE_APP_DOCKERFILE = '/Dockerfile';
|
|
393
394
|
/** Boule's own `EXPOSE 3000` (its doc comment just above) -- the port its health check (#543) probes. */
|
|
394
395
|
export const BOULE_PORT = 3000;
|
|
396
|
+
/**
|
|
397
|
+
* foundry#949: kosmos (https://github.com/wtfalch/kosmos), bootstrapped from the same `wtfalch/package-template`
|
|
398
|
+
* service shape Boule is, with its own Dockerfile at the repository root, serving on 3000 (`EXPOSE 3000`) and
|
|
399
|
+
* running its migrations through `apps/host/scripts/migrate.mjs` before it serves, the same shape Boule's
|
|
400
|
+
* `migrate-and-start.sh` follows.
|
|
401
|
+
*/
|
|
402
|
+
export const KOSMOS_REPO = 'wtfalch/kosmos';
|
|
403
|
+
export const KOSMOS_APP_DOCKERFILE = '/Dockerfile';
|
|
404
|
+
export const KOSMOS_PORT = 3000;
|
|
395
405
|
/**
|
|
396
406
|
* Every app foundry deploys keeps its own logs across a redeploy, so a
|
|
397
407
|
* database outage does not also erase the only other copy of the evidence
|
|
@@ -657,6 +667,14 @@ const CALLS_SYNAPSE_SCOPE_KEY = 'coolify_calls_synapse';
|
|
|
657
667
|
const CALLS_MAS_SCOPE_KEY = 'coolify_calls_mas';
|
|
658
668
|
const CALLS_SYNAPSE_DB_SCOPE_KEY = 'coolify_calls_synapse_database';
|
|
659
669
|
const CALLS_MAS_DB_SCOPE_KEY = 'coolify_calls_mas_database';
|
|
670
|
+
/** #430: the calls service's own Matrix/MAS env vars, from before calls#93 (transit-only). Removed from every org's calls service, never written again. */
|
|
671
|
+
const CALLS_MATRIX_ENV_KEYS = [
|
|
672
|
+
'MAS_URL',
|
|
673
|
+
'MAS_HOMESERVER',
|
|
674
|
+
'MAS_CLIENT_ID',
|
|
675
|
+
'MAS_CLIENT_SECRET',
|
|
676
|
+
'SYNAPSE_URL',
|
|
677
|
+
];
|
|
660
678
|
/**
|
|
661
679
|
* Synapse refuses to start against a database whose `LC_COLLATE`/`LC_CTYPE`
|
|
662
680
|
* is not `C` (https://element-hq.github.io/synapse/latest/postgres.html);
|
|
@@ -947,6 +965,8 @@ function limitProfileOf(action) {
|
|
|
947
965
|
case 'coolify:boule':
|
|
948
966
|
case 'coolify:boule-broker-entry':
|
|
949
967
|
return LIMITS.boule;
|
|
968
|
+
case 'coolify:kosmos':
|
|
969
|
+
return LIMITS.kosmos;
|
|
950
970
|
case 'coolify:broker-registrar':
|
|
951
971
|
return LIMITS.brokerEntry;
|
|
952
972
|
case 'coolify:keys-registrar':
|
|
@@ -1380,6 +1400,8 @@ export class FoundryStepExecutor {
|
|
|
1380
1400
|
return this.bouleOwners(org, context);
|
|
1381
1401
|
case 'coolify:boule-broker-entry':
|
|
1382
1402
|
return this.bouleBrokerEntry(org, context);
|
|
1403
|
+
case 'coolify:kosmos':
|
|
1404
|
+
return this.coolifyKosmos(action, org, context);
|
|
1383
1405
|
case 'coolify:broker-registrar':
|
|
1384
1406
|
return this.brokerRegistrar(org, context);
|
|
1385
1407
|
case 'coolify:keys-registrar':
|
|
@@ -2778,6 +2800,7 @@ export class FoundryStepExecutor {
|
|
|
2778
2800
|
const isCmsClient = action.key === 'cms' && org.site.enabled;
|
|
2779
2801
|
const isAgoraClient = action.key === 'agora';
|
|
2780
2802
|
const isBouleClient = action.key === 'boule';
|
|
2803
|
+
const isKosmosClient = action.key === 'kosmos' && kosmosOn(org);
|
|
2781
2804
|
const authEntry = org.hosting.entries.find((e) => e.key === action.key && e.uses?.auth);
|
|
2782
2805
|
// A workspace's own client `<key>-work`, signing in at the workspace host.
|
|
2783
2806
|
const workspaceEntry = authEntry ? undefined : workspaceAuthEntryOf(org, action.key);
|
|
@@ -2787,9 +2810,11 @@ export class FoundryStepExecutor {
|
|
|
2787
2810
|
? AGORA_CLIENT_ID_SCOPE_KEY
|
|
2788
2811
|
: isBouleClient
|
|
2789
2812
|
? BOULE_CLIENT_ID_SCOPE_KEY
|
|
2790
|
-
:
|
|
2791
|
-
?
|
|
2792
|
-
:
|
|
2813
|
+
: isKosmosClient
|
|
2814
|
+
? KOSMOS_CLIENT_ID_SCOPE_KEY
|
|
2815
|
+
: authEntry || workspaceEntry
|
|
2816
|
+
? entryClientIdScopeKey(action.key)
|
|
2817
|
+
: APP_CLIENT_ID_SCOPE_KEY;
|
|
2793
2818
|
const priorAppId = recorded[`zitadel:app:${action.key}`];
|
|
2794
2819
|
// Only a step the planner flagged as destroying (`recreate`, set with
|
|
2795
2820
|
// `destroys: true` so plan-apply asks for the typed confirmation) may
|
|
@@ -4247,6 +4272,8 @@ export class FoundryStepExecutor {
|
|
|
4247
4272
|
}
|
|
4248
4273
|
case 'boule':
|
|
4249
4274
|
return { appIds: await byName(`${org.slug}-boule`), template: true };
|
|
4275
|
+
case 'kosmos':
|
|
4276
|
+
return { appIds: await byName(`${org.slug}-kosmos`), template: true };
|
|
4250
4277
|
case 'chat_service':
|
|
4251
4278
|
return { appIds: await byName(chat.service), template: true };
|
|
4252
4279
|
case 'calls_service':
|
|
@@ -6530,16 +6557,18 @@ export class FoundryStepExecutor {
|
|
|
6530
6557
|
// ---- calls service (#145, part 2 of #128) -------------------------------
|
|
6531
6558
|
/**
|
|
6532
6559
|
* `plan/diff.ts`'s `planCallsService`: the org's own calls service --
|
|
6533
|
-
* `wtfalch/calls`'s `packages/service`, wired to the
|
|
6534
|
-
*
|
|
6535
|
-
*
|
|
6536
|
-
*
|
|
6537
|
-
*
|
|
6538
|
-
*
|
|
6539
|
-
*
|
|
6540
|
-
*
|
|
6541
|
-
*
|
|
6542
|
-
*
|
|
6560
|
+
* `wtfalch/calls`'s `packages/service`, wired to the shared LiveKit SFU
|
|
6561
|
+
* (#122/#127). #430: calls run transit-only (calls#93's
|
|
6562
|
+
* `CALLS_ENCRYPTION=transit`), so this no longer waits on or wires a
|
|
6563
|
+
* per-org Matrix stack at all -- an org whose stack is already live from
|
|
6564
|
+
* before this keeps running it, unused, until a human removes it
|
|
6565
|
+
* separately. Static config checks run first, before any Coolify call
|
|
6566
|
+
* (#128's own carried-over PR 344 review fix, restated in #145's own
|
|
6567
|
+
* issue body): the shared SFU's LiveKit credentials must already be
|
|
6568
|
+
* resolvable -- not minted or guessed here. Park stops the app only, the
|
|
6569
|
+
* same park-not-delete rule {@link coolifyCalls}/{@link coolifyApp}
|
|
6570
|
+
* already follow (#7 corrections 1); its own database is never touched
|
|
6571
|
+
* from that branch.
|
|
6543
6572
|
*/
|
|
6544
6573
|
async coolifyCallsService(action, org, context) {
|
|
6545
6574
|
if (action.desired.enabled === false) {
|
|
@@ -6550,18 +6579,8 @@ export class FoundryStepExecutor {
|
|
|
6550
6579
|
await this.ports.coolify.raw('POST', `/applications/${appId}/stop`);
|
|
6551
6580
|
return { resourceId: appId };
|
|
6552
6581
|
}
|
|
6553
|
-
// Refused before anything is created, like the missing-
|
|
6582
|
+
// Refused before anything is created, like the missing-SFU refusal below.
|
|
6554
6583
|
const sfuUrls = resolveCallsSfuUrls(this.ports.callsSfuUrls);
|
|
6555
|
-
const recorded = context.recordedIds();
|
|
6556
|
-
const synapseId = recorded[CALLS_SYNAPSE_SCOPE_KEY];
|
|
6557
|
-
const masId = recorded[CALLS_MAS_SCOPE_KEY];
|
|
6558
|
-
if (!synapseId || !masId) {
|
|
6559
|
-
throw new Error(`coolify:calls-service for ${org.slug}: the calls Matrix stack has not been provisioned yet -- coolify:calls must run first`);
|
|
6560
|
-
}
|
|
6561
|
-
const masCredentials = await this.readCallsMasClientCredentials(masId);
|
|
6562
|
-
if (!masCredentials) {
|
|
6563
|
-
throw new Error(`coolify:calls-service for ${org.slug}: the calls Matrix stack's MAS client credentials are not on the MAS app yet -- retry this apply once coolify:calls has minted them`);
|
|
6564
|
-
}
|
|
6565
6584
|
const sfu = await this.ports.callsSfu.resolveCredentials(org.slug);
|
|
6566
6585
|
if (!sfu) {
|
|
6567
6586
|
throw new Error(`coolify:calls-service for ${org.slug}: the shared calls SFU has no LiveKit credentials for this org yet -- see https://github.com/wtfalch/foundry/issues/152`);
|
|
@@ -6569,8 +6588,6 @@ export class FoundryStepExecutor {
|
|
|
6569
6588
|
const projectUuid = await this.ensureCoolifyProject(org, context);
|
|
6570
6589
|
const { environment } = await this.ports.coolify.findOrCreateEnvironment(projectUuid, 'production');
|
|
6571
6590
|
const name = callsServiceInternalName(org);
|
|
6572
|
-
const synapseHost = `${org.slug}-calls-synapse`;
|
|
6573
|
-
const masHost = `${org.slug}-calls-mas`;
|
|
6574
6591
|
const dbName = `${name}-db`;
|
|
6575
6592
|
const serviceHome = await this.ensureHomeDatabase(org, context, 'calls_service', CALLS_SERVICE_DB_SCOPE_KEY, () => this.ensureCallsDatabase(org, context, projectUuid, environment, dbName, dbName.replace(/-/g, '_'), CALLS_SERVICE_DB_SCOPE_KEY));
|
|
6576
6593
|
if (!(await this.homeOwnerUrl(serviceHome))) {
|
|
@@ -6612,17 +6629,25 @@ export class FoundryStepExecutor {
|
|
|
6612
6629
|
});
|
|
6613
6630
|
// Every app foundry deploys keeps its own logs (#75), reused as-is here.
|
|
6614
6631
|
await this.ensureLogVolume(app.appId, org, app.existed);
|
|
6632
|
+
// #430: an org whose Matrix stack is still live from before this keeps
|
|
6633
|
+
// the Synapse/MAS apps running, unused, until a human removes them
|
|
6634
|
+
// separately -- but its calls service's own env drops their vars on its
|
|
6635
|
+
// next apply, the same as a brand new org that never got them.
|
|
6636
|
+
for (const key of CALLS_MATRIX_ENV_KEYS) {
|
|
6637
|
+
const env = (await this.ports.coolify.listEnvs(app.appId)).find((e) => e.key === key && !e.is_preview);
|
|
6638
|
+
if (env?.uuid)
|
|
6639
|
+
await this.ports.coolify.deleteEnv(app.appId, env.uuid);
|
|
6640
|
+
}
|
|
6615
6641
|
const bindingSecret = await this.ensureCallsBindingSecret(app.appId);
|
|
6616
6642
|
const envs = [
|
|
6617
6643
|
{ key: 'LIVEKIT_URL', value: sfuUrls.privateUrl },
|
|
6618
6644
|
{ key: 'LIVEKIT_PUBLIC_URL', value: sfuUrls.publicUrl },
|
|
6619
6645
|
{ key: 'LIVEKIT_API_KEY', value: sfu.apiKey },
|
|
6620
6646
|
{ key: 'LIVEKIT_API_SECRET', value: sfu.apiSecret },
|
|
6621
|
-
|
|
6622
|
-
|
|
6623
|
-
|
|
6624
|
-
{ key: '
|
|
6625
|
-
{ key: 'SYNAPSE_URL', value: `http://${synapseHost}:8008` },
|
|
6647
|
+
// #430 (calls#93): calls are transit-only, so the service never needs
|
|
6648
|
+
// a Matrix/MAS provider -- MAS_URL, MAS_HOMESERVER, MAS_CLIENT_ID,
|
|
6649
|
+
// MAS_CLIENT_SECRET and SYNAPSE_URL are never written.
|
|
6650
|
+
{ key: 'CALLS_ENCRYPTION', value: 'transit' },
|
|
6626
6651
|
{ key: 'CALLS_ORIGIN', value: `https://${agoraHostnameFor(org)}` },
|
|
6627
6652
|
{ key: 'CALLS_HOST_ADAPTER', value: 'production' },
|
|
6628
6653
|
// Deliberately kept as it was, not widened: the calls service refuses to
|
|
@@ -6748,6 +6773,9 @@ export class FoundryStepExecutor {
|
|
|
6748
6773
|
throw new Error(`${at}: token ${tokenId} is recorded but chat-service no longer holds a usable one; remove the recorded keys ${CHAT_CALLS_TOKEN_SCOPE_KEY} and ${CHAT_CALLS_TOKEN_UNDELIVERED_SCOPE_KEY} to mint a new one`);
|
|
6749
6774
|
}
|
|
6750
6775
|
const token = usable ?? randomBytes(32).toString('hex');
|
|
6776
|
+
// Registered before either write, so a Coolify error body echoing the upsert is scrubbed
|
|
6777
|
+
// from the run record the same way the write loop's own dropped-error fallback intends.
|
|
6778
|
+
this.handledSecrets.add(token);
|
|
6751
6779
|
if (!tokenId) {
|
|
6752
6780
|
tokenId = randomBytes(8).toString('hex');
|
|
6753
6781
|
context.recordScopeId(CHAT_CALLS_TOKEN_UNDELIVERED_SCOPE_KEY, tokenId);
|
|
@@ -6852,22 +6880,6 @@ export class FoundryStepExecutor {
|
|
|
6852
6880
|
appId: client.appId,
|
|
6853
6881
|
};
|
|
6854
6882
|
}
|
|
6855
|
-
/**
|
|
6856
|
-
* Reads back the calls Matrix stack's MAS client id/secret from the MAS
|
|
6857
|
-
* app's own envs ({@link CALLS_SECRET_ENV_KEYS}) -- never mints: minting
|
|
6858
|
-
* either is {@link ensureCallsMatrixSecrets}'s own job, and a missing value
|
|
6859
|
-
* here means that stack has not finished provisioning yet, not something
|
|
6860
|
-
* this step can fix on its own.
|
|
6861
|
-
*/
|
|
6862
|
-
async readCallsMasClientCredentials(masAppId) {
|
|
6863
|
-
const envs = await this.ports.coolify.listEnvs(masAppId);
|
|
6864
|
-
const read = (key) => envs.find((e) => e.key === key && !e.is_preview)?.value;
|
|
6865
|
-
const clientId = read(CALLS_SECRET_ENV_KEYS.masClientId);
|
|
6866
|
-
const clientSecret = read(CALLS_SECRET_ENV_KEYS.masClientSecret);
|
|
6867
|
-
if (!clientId || !clientSecret)
|
|
6868
|
-
return null;
|
|
6869
|
-
return { clientId, clientSecret };
|
|
6870
|
-
}
|
|
6871
6883
|
/**
|
|
6872
6884
|
* `null` once the calls service app already has a `CALLS_BINDING_SECRET` --
|
|
6873
6885
|
* never rotated, the same rule {@link ensureAuthCookieSecret} follows for
|
|
@@ -7452,6 +7464,98 @@ export class FoundryStepExecutor {
|
|
|
7452
7464
|
this.recordSignInEnvLive(context, 'boule', bouleHostnameFor(org), env);
|
|
7453
7465
|
return { resourceId: app.appId };
|
|
7454
7466
|
}
|
|
7467
|
+
// ---- kosmos (foundry#949) --------------------------------------------------
|
|
7468
|
+
/**
|
|
7469
|
+
* `plan/diff.ts`'s `planKosmos`: provisions the org's own kosmos
|
|
7470
|
+
* (https://github.com/wtfalch/kosmos) as one more Coolify application, from
|
|
7471
|
+
* that repo's root Dockerfile ({@link KOSMOS_REPO}), with a database of its
|
|
7472
|
+
* own, and pushes its sign-in env. The same shape as {@link coolifyBoule},
|
|
7473
|
+
* trimmed: kosmos is opt-in (`kosmosOn(org)` guards every caller, so this is
|
|
7474
|
+
* never reached while it is off), has no mail, no owner seeding and no
|
|
7475
|
+
* Archon credential.
|
|
7476
|
+
*
|
|
7477
|
+
* The sign-in env comes from `plan/kosmos.ts`'s `desiredKosmosEnv`, the same
|
|
7478
|
+
* function the plan compares the live app against; it never writes a
|
|
7479
|
+
* partial set. The database is `<slug>-kosmos-db`, split the same way
|
|
7480
|
+
* Boule's is: the superuser URL as `DATABASE_URL_OWNER` and a `<dbName>_rt`
|
|
7481
|
+
* role's URL as `DATABASE_URL`, both through `pushDatabaseUrl`.
|
|
7482
|
+
*
|
|
7483
|
+
* #504 (same reason {@link coolifyBoule}'s own comment gives): a brand new
|
|
7484
|
+
* app is never restarted, but it is deployed once ({@link KOSMOS_DEPLOYED_SCOPE_KEY}).
|
|
7485
|
+
*/
|
|
7486
|
+
async coolifyKosmos(action, org, context) {
|
|
7487
|
+
// Park stops the app only; its database is never touched (#7 corrections 1).
|
|
7488
|
+
if (action.desired.enabled === false) {
|
|
7489
|
+
const appId = context.recordedIds()[`coolify:kosmos:${action.key}`];
|
|
7490
|
+
if (!appId) {
|
|
7491
|
+
throw new Error(`coolify:kosmos for ${org.slug}: no live app recorded, nothing to park`);
|
|
7492
|
+
}
|
|
7493
|
+
await this.ports.coolify.raw('POST', `/applications/${appId}/stop`);
|
|
7494
|
+
return { resourceId: appId };
|
|
7495
|
+
}
|
|
7496
|
+
const issuer = action.desired.issuer ?? null;
|
|
7497
|
+
const { env, missing } = desiredKosmosEnv(org, context.recordedIds(), issuer);
|
|
7498
|
+
if (missing.length > 0) {
|
|
7499
|
+
throw new Error(`kosmos for ${org.slug} cannot be configured: missing ${missing.join(', ')}. Nothing was written.`);
|
|
7500
|
+
}
|
|
7501
|
+
const projectUuid = await this.ensureCoolifyProject(org, context);
|
|
7502
|
+
const { environment } = await this.ports.coolify.findOrCreateEnvironment(projectUuid, 'production');
|
|
7503
|
+
const name = `${org.slug}-kosmos`;
|
|
7504
|
+
const dbName = `${org.slug}-kosmos-db`.replace(/-/g, '_');
|
|
7505
|
+
// #444: on the company's server there is no Coolify database to make, like Boule's.
|
|
7506
|
+
const kosmosServer = this.homeOnServer(org, context, 'kosmos')
|
|
7507
|
+
? await this.serverHome(org, context, 'kosmos')
|
|
7508
|
+
: null;
|
|
7509
|
+
const dbId = kosmosServer
|
|
7510
|
+
? ''
|
|
7511
|
+
: await this.ensureCallsDatabase(org, context, projectUuid, environment, `${org.slug}-kosmos-db`, dbName, KOSMOS_DATABASE_SCOPE_KEY);
|
|
7512
|
+
if (!kosmosServer)
|
|
7513
|
+
context.recordScopeId(KOSMOS_DATABASE_SCOPE_KEY, dbId);
|
|
7514
|
+
const server = await this.resolveServer(org, context);
|
|
7515
|
+
const githubApp = await this.ports.coolify.requireGithubApp();
|
|
7516
|
+
const app = await this.findOrCreateCallsApp(name, environment.id, () => this.ports.coolify.createGithubAppApplication({
|
|
7517
|
+
is_preview_deployments_enabled: false,
|
|
7518
|
+
project_uuid: projectUuid,
|
|
7519
|
+
server_uuid: server.uuid,
|
|
7520
|
+
environment_name: environment.name,
|
|
7521
|
+
environment_uuid: environment.uuid,
|
|
7522
|
+
github_app_uuid: githubApp.uuid,
|
|
7523
|
+
git_repository: KOSMOS_REPO,
|
|
7524
|
+
git_branch: 'main',
|
|
7525
|
+
build_pack: 'dockerfile',
|
|
7526
|
+
name,
|
|
7527
|
+
domains: coolifyTunnelDomains([kosmosHostnameFor(org)]),
|
|
7528
|
+
dockerfile_location: KOSMOS_APP_DOCKERFILE,
|
|
7529
|
+
}));
|
|
7530
|
+
// Every app foundry deploys keeps its own logs (#75), reused as-is here.
|
|
7531
|
+
await this.ensureLogVolume(app.appId, org, app.existed);
|
|
7532
|
+
await this.ports.coolify.updateApplication(app.appId, {
|
|
7533
|
+
...healthCheckSettings('/api/health', KOSMOS_PORT),
|
|
7534
|
+
...(await this.routedLabels(name, kosmosHostnameFor(org), KOSMOS_PORT)),
|
|
7535
|
+
});
|
|
7536
|
+
const liveRows = (await this.ports.coolify.listEnvs(app.appId)).filter((e) => !e.is_preview);
|
|
7537
|
+
const liveEnv = Object.fromEntries(liveRows.map((e) => [e.key, e.value]));
|
|
7538
|
+
const envChanged = kosmosEnvDrift(env, liveEnv).length > 0;
|
|
7539
|
+
const envs = Object.entries(env).map(([key, value]) => ({ key, value }));
|
|
7540
|
+
const cookieSecret = await this.ensureAuthCookieSecret(app.appId);
|
|
7541
|
+
if (cookieSecret)
|
|
7542
|
+
envs.push({ key: 'AUTH_COOKIE_SECRET', value: cookieSecret });
|
|
7543
|
+
await this.ports.coolify.bulkUpsertEnvs(app.appId, envs);
|
|
7544
|
+
const dbPush = await this.pushDatabaseUrl(app.appId, dbId, dbName, kosmosServer ?? undefined);
|
|
7545
|
+
if (!dbPush.pushed) {
|
|
7546
|
+
throw new Error(`kosmos for ${org.slug}: its database has no internal_db_url yet -- retry this apply once Coolify reports one`);
|
|
7547
|
+
}
|
|
7548
|
+
if (!context.recordedIds()[KOSMOS_DEPLOYED_SCOPE_KEY]) {
|
|
7549
|
+
const runtime = this.ports.runtime ?? {};
|
|
7550
|
+
const built = await waitForNewDeployment(this.ports.coolify, app.appId, publicHealthCheck(org, context.recordedIds(), kosmosHostnameFor(org), '/api/health'), runtime, runtime.sleep ?? defaultSleep, 'coolify:kosmos', `${org.slug}'s kosmos stays at its previous deploy`);
|
|
7551
|
+
await recordBuiltCommit(this.ports.coolify, { appId: app.appId, deploymentUuid: built.deploymentUuid, name: 'kosmos' }, (key, value) => context.recordScopeId(key, value));
|
|
7552
|
+
context.recordScopeId(KOSMOS_DEPLOYED_SCOPE_KEY, '1');
|
|
7553
|
+
}
|
|
7554
|
+
else if (app.existed && (envChanged || cookieSecret !== null || dbPush.changed)) {
|
|
7555
|
+
await this.ports.coolify.restart(app.appId);
|
|
7556
|
+
}
|
|
7557
|
+
return { resourceId: app.appId };
|
|
7558
|
+
}
|
|
7455
7559
|
/**
|
|
7456
7560
|
* C3: the Archon credential of an org's Boule. Archon's mint rotates on every call, so it is called only
|
|
7457
7561
|
* when none was pushed or the recorded push time (no secret) is 335 days old. Otherwise the held value is
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { DeclaredOrg } from '../org.js';
|
|
2
|
+
/**
|
|
3
|
+
* foundry#949: what both the planner (`diff.ts`) and the executor
|
|
4
|
+
* (`executor.ts`) need to agree on for an org's kosmos -- the hostname, the
|
|
5
|
+
* recorded ids and the env vars those produce -- so the plan can compare the
|
|
6
|
+
* live app with exactly what the executor would write. The same split
|
|
7
|
+
* `boule.ts` and `agora.ts` already follow, trimmed to what kosmos needs for
|
|
8
|
+
* its first release: a ZITADEL client, a database and sign-in env. Kosmos has
|
|
9
|
+
* no mail, no owner seeding and no Archon credential -- it is not Boule.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* The fixed `kosmos` ZITADEL web client's `clientId`, recorded by
|
|
13
|
+
* `executor.ts`'s `zitadelApp` and read by `coolifyKosmos` for
|
|
14
|
+
* `AUTH_CLIENT_ID`. Public, not a secret, so a flat scope id like
|
|
15
|
+
* `boule_client_id`.
|
|
16
|
+
*/
|
|
17
|
+
export declare const KOSMOS_CLIENT_ID_SCOPE_KEY = "kosmos_client_id";
|
|
18
|
+
/**
|
|
19
|
+
* The kosmos database's Coolify uuid. It has no `system:kind:key` slot of its
|
|
20
|
+
* own (the step's one auto-recorded `resourceId` is the app), so it is a flat
|
|
21
|
+
* scope id, like the Boule and cms databases.
|
|
22
|
+
*/
|
|
23
|
+
export declare const KOSMOS_DATABASE_SCOPE_KEY = "coolify_kosmos_database";
|
|
24
|
+
/**
|
|
25
|
+
* Recorded once `coolifyKosmos` has actually called `deploy` on this org's
|
|
26
|
+
* kosmos app -- the same marker {@link BOULE_DEPLOYED_SCOPE_KEY} (`boule.ts`)
|
|
27
|
+
* and {@link AGORA_DEPLOYED_SCOPE_KEY} (`agora.ts`) are, and for the same
|
|
28
|
+
* reason: Coolify defaults `instant_deploy` to false, so a freshly created
|
|
29
|
+
* app sat built but never started.
|
|
30
|
+
*/
|
|
31
|
+
export declare const KOSMOS_DEPLOYED_SCOPE_KEY = "coolify_kosmos_deployed";
|
|
32
|
+
/**
|
|
33
|
+
* `kosmos.<domain>` when the org has a domain, `<slug>-kosmos.oikos.host`
|
|
34
|
+
* otherwise -- the same tier rule `bouleHostnameFor`/`agoraHostnameFor`
|
|
35
|
+
* follow; `../slug.ts`'s `KOSMOS_SUFFIX` reserves the collision.
|
|
36
|
+
*/
|
|
37
|
+
export declare function kosmosHostnameFor(org: DeclaredOrg): string;
|
|
38
|
+
/** The env names `desiredKosmosEnv` can write, so the live reader knows which to read back. */
|
|
39
|
+
export declare const KOSMOS_MANAGED_ENV_KEYS: readonly ["APP_URL", "AUTH_ISSUER", "AUTH_ORGANIZATION_ID", "AUTH_CLIENT_ID"];
|
|
40
|
+
/**
|
|
41
|
+
* The env vars an org's kosmos app should carry, and every input still missing
|
|
42
|
+
* to build them. Callers that write must refuse on a non-empty `missing`: an
|
|
43
|
+
* kosmos with a blank client id fails every sign-in. `issuer` is the host's
|
|
44
|
+
* issuer address (`AgoraHostValues.issuer`), the same one Agora and Boule sign
|
|
45
|
+
* in at.
|
|
46
|
+
*
|
|
47
|
+
* Not here, and written by the executor instead: the database URLs
|
|
48
|
+
* (`DATABASE_URL_OWNER`, `DATABASE_URL`, minted once) and `AUTH_COOKIE_SECRET`
|
|
49
|
+
* (generated once, never rotated) -- the same split `desiredBouleEnv` keeps.
|
|
50
|
+
*/
|
|
51
|
+
export declare function desiredKosmosEnv(org: DeclaredOrg, recorded: Readonly<Record<string, string>>, issuer: string | null): {
|
|
52
|
+
env: Record<string, string>;
|
|
53
|
+
missing: string[];
|
|
54
|
+
};
|
|
55
|
+
/** The desired env keys the live app lacks or holds a different value for. */
|
|
56
|
+
export declare function kosmosEnvDrift(desired: Readonly<Record<string, string>>, live: Readonly<Record<string, string>>): string[];
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { ZITADEL_ORG_SCOPE_KEY } from './agora.js';
|
|
2
|
+
/**
|
|
3
|
+
* foundry#949: what both the planner (`diff.ts`) and the executor
|
|
4
|
+
* (`executor.ts`) need to agree on for an org's kosmos -- the hostname, the
|
|
5
|
+
* recorded ids and the env vars those produce -- so the plan can compare the
|
|
6
|
+
* live app with exactly what the executor would write. The same split
|
|
7
|
+
* `boule.ts` and `agora.ts` already follow, trimmed to what kosmos needs for
|
|
8
|
+
* its first release: a ZITADEL client, a database and sign-in env. Kosmos has
|
|
9
|
+
* no mail, no owner seeding and no Archon credential -- it is not Boule.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* The fixed `kosmos` ZITADEL web client's `clientId`, recorded by
|
|
13
|
+
* `executor.ts`'s `zitadelApp` and read by `coolifyKosmos` for
|
|
14
|
+
* `AUTH_CLIENT_ID`. Public, not a secret, so a flat scope id like
|
|
15
|
+
* `boule_client_id`.
|
|
16
|
+
*/
|
|
17
|
+
export const KOSMOS_CLIENT_ID_SCOPE_KEY = 'kosmos_client_id';
|
|
18
|
+
/**
|
|
19
|
+
* The kosmos database's Coolify uuid. It has no `system:kind:key` slot of its
|
|
20
|
+
* own (the step's one auto-recorded `resourceId` is the app), so it is a flat
|
|
21
|
+
* scope id, like the Boule and cms databases.
|
|
22
|
+
*/
|
|
23
|
+
export const KOSMOS_DATABASE_SCOPE_KEY = 'coolify_kosmos_database';
|
|
24
|
+
/**
|
|
25
|
+
* Recorded once `coolifyKosmos` has actually called `deploy` on this org's
|
|
26
|
+
* kosmos app -- the same marker {@link BOULE_DEPLOYED_SCOPE_KEY} (`boule.ts`)
|
|
27
|
+
* and {@link AGORA_DEPLOYED_SCOPE_KEY} (`agora.ts`) are, and for the same
|
|
28
|
+
* reason: Coolify defaults `instant_deploy` to false, so a freshly created
|
|
29
|
+
* app sat built but never started.
|
|
30
|
+
*/
|
|
31
|
+
export const KOSMOS_DEPLOYED_SCOPE_KEY = 'coolify_kosmos_deployed';
|
|
32
|
+
/**
|
|
33
|
+
* `kosmos.<domain>` when the org has a domain, `<slug>-kosmos.oikos.host`
|
|
34
|
+
* otherwise -- the same tier rule `bouleHostnameFor`/`agoraHostnameFor`
|
|
35
|
+
* follow; `../slug.ts`'s `KOSMOS_SUFFIX` reserves the collision.
|
|
36
|
+
*/
|
|
37
|
+
export function kosmosHostnameFor(org) {
|
|
38
|
+
return org.domain ? `kosmos.${org.domain}` : `${org.slug}-kosmos.oikos.host`;
|
|
39
|
+
}
|
|
40
|
+
/** The env names `desiredKosmosEnv` can write, so the live reader knows which to read back. */
|
|
41
|
+
export const KOSMOS_MANAGED_ENV_KEYS = [
|
|
42
|
+
'APP_URL',
|
|
43
|
+
'AUTH_ISSUER',
|
|
44
|
+
'AUTH_ORGANIZATION_ID',
|
|
45
|
+
'AUTH_CLIENT_ID',
|
|
46
|
+
];
|
|
47
|
+
/**
|
|
48
|
+
* The env vars an org's kosmos app should carry, and every input still missing
|
|
49
|
+
* to build them. Callers that write must refuse on a non-empty `missing`: an
|
|
50
|
+
* kosmos with a blank client id fails every sign-in. `issuer` is the host's
|
|
51
|
+
* issuer address (`AgoraHostValues.issuer`), the same one Agora and Boule sign
|
|
52
|
+
* in at.
|
|
53
|
+
*
|
|
54
|
+
* Not here, and written by the executor instead: the database URLs
|
|
55
|
+
* (`DATABASE_URL_OWNER`, `DATABASE_URL`, minted once) and `AUTH_COOKIE_SECRET`
|
|
56
|
+
* (generated once, never rotated) -- the same split `desiredBouleEnv` keeps.
|
|
57
|
+
*/
|
|
58
|
+
export function desiredKosmosEnv(org, recorded, issuer) {
|
|
59
|
+
const zitadelOrgId = recorded[ZITADEL_ORG_SCOPE_KEY];
|
|
60
|
+
const clientId = recorded[KOSMOS_CLIENT_ID_SCOPE_KEY];
|
|
61
|
+
const missing = [];
|
|
62
|
+
if (!issuer)
|
|
63
|
+
missing.push('issuer');
|
|
64
|
+
if (!zitadelOrgId)
|
|
65
|
+
missing.push('zitadelOrgId');
|
|
66
|
+
if (!clientId)
|
|
67
|
+
missing.push('clientId');
|
|
68
|
+
const env = {
|
|
69
|
+
APP_URL: `https://${kosmosHostnameFor(org)}`,
|
|
70
|
+
};
|
|
71
|
+
if (issuer)
|
|
72
|
+
env.AUTH_ISSUER = issuer;
|
|
73
|
+
if (zitadelOrgId)
|
|
74
|
+
env.AUTH_ORGANIZATION_ID = zitadelOrgId;
|
|
75
|
+
if (clientId)
|
|
76
|
+
env.AUTH_CLIENT_ID = clientId;
|
|
77
|
+
return { env, missing };
|
|
78
|
+
}
|
|
79
|
+
/** The desired env keys the live app lacks or holds a different value for. */
|
|
80
|
+
export function kosmosEnvDrift(desired, live) {
|
|
81
|
+
return Object.entries(desired)
|
|
82
|
+
.filter(([key, value]) => live[key] !== value)
|
|
83
|
+
.map(([key]) => key);
|
|
84
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { ownsServices } from '../org.js';
|
|
1
|
+
import { kosmosOn, ownsServices } from '../org.js';
|
|
2
2
|
import { OWN_SERVICE_LABELS } from '../service-labels.js';
|
|
3
3
|
import { agoraHostnameFor } from './agora.js';
|
|
4
4
|
import { bouleHostnameFor } from './boule.js';
|
|
5
5
|
import { cmsStudioHostnameFor, siteHostnameFor } from './hostnames.js';
|
|
6
|
+
import { kosmosHostnameFor } from './kosmos.js';
|
|
6
7
|
import { loginHostnameFor } from './login-host.js';
|
|
7
8
|
import { ownHostnames } from './own-hostnames.js';
|
|
8
9
|
export { OWN_SERVICE_LABELS };
|
|
@@ -31,6 +32,8 @@ export function lockedHostnames(org, platformHostnames) {
|
|
|
31
32
|
}
|
|
32
33
|
locked.add(agoraHostnameFor(org));
|
|
33
34
|
locked.add(bouleHostnameFor(org));
|
|
35
|
+
if (kosmosOn(org))
|
|
36
|
+
locked.add(kosmosHostnameFor(org));
|
|
34
37
|
if (org.site.enabled)
|
|
35
38
|
locked.add(cmsStudioHostnameFor(org));
|
|
36
39
|
if (org.calls.enabled && org.calls.usesDomain)
|
|
@@ -58,6 +61,6 @@ export function hostingNames(org, platformHostnames) {
|
|
|
58
61
|
locked: lockedHostnames(org, platformHostnames),
|
|
59
62
|
slug: org.slug,
|
|
60
63
|
siteOwnsApex: org.site.enabled && org.site.usesDomain && org.domain !== null,
|
|
61
|
-
builtinBlueGreen: org.boule?.blueGreen !== undefined || org.agora?.blueGreen !== undefined,
|
|
64
|
+
builtinBlueGreen: org.boule?.blueGreen !== undefined || org.agora?.blueGreen !== undefined || kosmosOn(org),
|
|
62
65
|
};
|
|
63
66
|
}
|
package/dist/plan/org-names.d.ts
CHANGED
|
@@ -6,13 +6,13 @@ export type OrgNameSource = 'own' | 'hosting';
|
|
|
6
6
|
* hosting entries: Agora, Boule, the tunnel and its connector, and chat,
|
|
7
7
|
* calls and the cms site while they are on.
|
|
8
8
|
*/
|
|
9
|
-
export declare function companyServiceNames(org: Pick<DeclaredOrg, 'slug' | 'chat' | 'calls' | 'site'>): string[];
|
|
9
|
+
export declare function companyServiceNames(org: Pick<DeclaredOrg, 'slug' | 'chat' | 'calls' | 'site' | 'kosmos'>): string[];
|
|
10
10
|
/**
|
|
11
11
|
* Every container name, network alias, database, tunnel and bucket name that
|
|
12
12
|
* foundry derives for an org: its own-service names when it owns services,
|
|
13
13
|
* and its hosting-entry and database names.
|
|
14
14
|
*/
|
|
15
|
-
export declare function orgDerivedNames(org: Pick<DeclaredOrg, 'slug' | 'domain' | 'services' | 'hosting' | 'chat' | 'calls' | 'site'>): {
|
|
15
|
+
export declare function orgDerivedNames(org: Pick<DeclaredOrg, 'slug' | 'domain' | 'services' | 'hosting' | 'chat' | 'calls' | 'site' | 'kosmos'>): {
|
|
16
16
|
name: string;
|
|
17
17
|
source: OrgNameSource;
|
|
18
18
|
}[];
|
|
@@ -26,5 +26,5 @@ export interface NameClash {
|
|
|
26
26
|
source: OrgNameSource;
|
|
27
27
|
}
|
|
28
28
|
/** Every name `org` derives that another org or the platform also derives. */
|
|
29
|
-
export declare function findNameClashes(org: Pick<DeclaredOrg, 'slug' | 'domain' | 'services' | 'hosting' | 'chat' | 'calls' | 'site'>, others: readonly Pick<DeclaredOrg, 'slug' | 'domain' | 'services' | 'hosting' | 'chat' | 'calls' | 'site'>[]): NameClash[];
|
|
29
|
+
export declare function findNameClashes(org: Pick<DeclaredOrg, 'slug' | 'domain' | 'services' | 'hosting' | 'chat' | 'calls' | 'site' | 'kosmos'>, others: readonly Pick<DeclaredOrg, 'slug' | 'domain' | 'services' | 'hosting' | 'chat' | 'calls' | 'site' | 'kosmos'>[]): NameClash[];
|
|
30
30
|
export declare function nameClashMessage(slug: string, clash: NameClash): string;
|
package/dist/plan/org-names.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ownsServices } from '../org.js';
|
|
1
|
+
import { kosmosOn, ownsServices } from '../org.js';
|
|
2
2
|
import { entryHasDatabase } from '../worker-app-config.js';
|
|
3
3
|
import { chatInternalNames, chatRekeyedInternalNames } from './chat-matrix.js';
|
|
4
4
|
import { workerAppHostname, workspaceHost, workspaceInboxHost } from './hostnames.js';
|
|
@@ -20,6 +20,8 @@ export function companyServiceNames(org) {
|
|
|
20
20
|
`${s}-tunnel`,
|
|
21
21
|
`${s}-cloudflared`,
|
|
22
22
|
];
|
|
23
|
+
if (kosmosOn(org))
|
|
24
|
+
names.push(`${s}-kosmos`, `${s}-kosmos-db`);
|
|
23
25
|
if (org.chat?.enabled) {
|
|
24
26
|
names.push(...Object.values(chatInternalNames(s)), ...Object.values(chatRekeyedInternalNames(s)));
|
|
25
27
|
}
|
|
@@ -107,6 +107,12 @@ export declare const LIMITS: {
|
|
|
107
107
|
autoDeploy: false | "declared";
|
|
108
108
|
capabilities: CapabilityTier;
|
|
109
109
|
};
|
|
110
|
+
readonly kosmos: {
|
|
111
|
+
memory: string;
|
|
112
|
+
dbMemory: string;
|
|
113
|
+
autoDeploy: false | "declared";
|
|
114
|
+
capabilities: CapabilityTier;
|
|
115
|
+
};
|
|
110
116
|
readonly companyOther: {
|
|
111
117
|
memory: string;
|
|
112
118
|
dbMemory: string;
|
package/dist/plan/own-limits.js
CHANGED
|
@@ -3905,6 +3905,9 @@ async function waitForMailInstance(url, runtime, sleep) {
|
|
|
3905
3905
|
for (;;) {
|
|
3906
3906
|
try {
|
|
3907
3907
|
const response = await fetchImpl(url);
|
|
3908
|
+
// #760: an unread body left undici's parser paused when the socket
|
|
3909
|
+
// ended, throwing an uncaught assertion in the host.
|
|
3910
|
+
await response.body?.cancel();
|
|
3908
3911
|
if (response.ok)
|
|
3909
3912
|
return;
|
|
3910
3913
|
}
|
|
@@ -7035,6 +7038,9 @@ async function waitForInstance(url, runtime, sleep, label = 'platform:auth') {
|
|
|
7035
7038
|
const response = await fetchImpl(url, {
|
|
7036
7039
|
signal: AbortSignal.timeout(runtime.readyRequestTimeoutMs ?? 10_000),
|
|
7037
7040
|
});
|
|
7041
|
+
// #760: an unread body left undici's parser paused when the socket
|
|
7042
|
+
// ended, throwing an uncaught assertion in the host.
|
|
7043
|
+
await response.body?.cancel();
|
|
7038
7044
|
if (response.ok)
|
|
7039
7045
|
return;
|
|
7040
7046
|
}
|
|
@@ -16,7 +16,7 @@ export declare function zitadelAppPostLogoutAppliedScopeKey(appKey: string): str
|
|
|
16
16
|
/**
|
|
17
17
|
* The post-logout addresses company app `appKey` is created with and kept at.
|
|
18
18
|
* Agora signs out locally and has no signed-out page, so it holds only its
|
|
19
|
-
* root; every `@wtfalch/auth` app (cms, boule, the app, entries with
|
|
19
|
+
* root; every `@wtfalch/auth` app (cms, boule, kosmos, the app, entries with
|
|
20
20
|
* `uses.auth`) holds both on each of its hosts.
|
|
21
21
|
*/
|
|
22
22
|
export declare function companyAppPostLogoutUris(org: DeclaredOrg, appKey: string): string[];
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { agoraHostnameFor } from './agora.js';
|
|
2
2
|
import { bouleHostnameFor } from './boule.js';
|
|
3
3
|
import { appHostname, cmsStudioHostnameFor, entryHostnames } from './hostnames.js';
|
|
4
|
+
import { kosmosHostnameFor } from './kosmos.js';
|
|
4
5
|
import { workspaceAuthEntryOf, workspaceClientHosts } from './workspace-client.js';
|
|
5
6
|
/** The page `@wtfalch/auth` apps land on after ZITADEL ends the session. */
|
|
6
7
|
export const SIGNED_OUT_PATH = '/auth/signed-out';
|
|
@@ -27,7 +28,7 @@ export function zitadelAppPostLogoutAppliedScopeKey(appKey) {
|
|
|
27
28
|
/**
|
|
28
29
|
* The post-logout addresses company app `appKey` is created with and kept at.
|
|
29
30
|
* Agora signs out locally and has no signed-out page, so it holds only its
|
|
30
|
-
* root; every `@wtfalch/auth` app (cms, boule, the app, entries with
|
|
31
|
+
* root; every `@wtfalch/auth` app (cms, boule, kosmos, the app, entries with
|
|
31
32
|
* `uses.auth`) holds both on each of its hosts.
|
|
32
33
|
*/
|
|
33
34
|
export function companyAppPostLogoutUris(org, appKey) {
|
|
@@ -44,6 +45,8 @@ export function companyAppPostLogoutUris(org, appKey) {
|
|
|
44
45
|
? cmsStudioHostnameFor(org)
|
|
45
46
|
: appKey === 'boule'
|
|
46
47
|
? bouleHostnameFor(org)
|
|
47
|
-
:
|
|
48
|
+
: appKey === 'kosmos'
|
|
49
|
+
? kosmosHostnameFor(org)
|
|
50
|
+
: appHostname(org);
|
|
48
51
|
return postLogoutUrisFor(hosts ?? [hostname]);
|
|
49
52
|
}
|