@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/diff.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { VERSIONS
|
|
2
|
-
import { APP_ENTRY_KEY, ORG_POSTGRES_SERVER_SCOPE_KEY, ORG_SERVICE_SCHEMAS, appsOn, companyGithubFor, heldEnvRef, mainEntry, ownerMailboxRecorded, ownerMailboxes, ownerNameProblem, ownsServices, } from '../org.js';
|
|
1
|
+
import { VERSIONS } from '../bootstrap/versions.js';
|
|
2
|
+
import { APP_ENTRY_KEY, ORG_POSTGRES_SERVER_SCOPE_KEY, ORG_SERVICE_SCHEMAS, appsOn, companyGithubFor, heldEnvRef, kosmosOn, mainEntry, ownerMailboxRecorded, ownerMailboxes, ownerNameProblem, ownsServices, } from '../org.js';
|
|
3
3
|
import { entryHasDatabase } from '../worker-app-config.js';
|
|
4
4
|
import { AGORA_ACTIVATED_ENV_SCOPE_KEY, AGORA_APP_PORTS_EXPOSES, CALLS_INTROSPECTION_SCOPE_KEY, CALLS_INTROSPECTION_UNDELIVERED_SCOPE_KEY, CALLS_SERVICE_DEPLOYED_SCOPE_KEY, CHAT_CALLS_APPLIED_SCOPE_KEY, CHAT_CALLS_TOKEN_SCOPE_KEY, CHAT_CALLS_TOKEN_UNDELIVERED_SCOPE_KEY, CHAT_INTROSPECTION_SCOPE_KEY, NO_AGORA_HOST_VALUES, ZITADEL_ORG_SCOPE_KEY, agoraEnvDrift, agoraEnvRevision, agoraHostMissing, agoraHostnameFor, callsServiceHasName, callsServiceHasTokenEnv, callsTokenEnvMovedMessage, callsTokenHostFrom, chatCallsApplied, chatHasUpstreams, desiredAgoraEnv, entryClientIdScopeKey, withRecordedMessagingProject, } from './agora.js';
|
|
5
5
|
import { planAppsDbRoute } from './apps-db-route.js';
|
|
@@ -26,6 +26,7 @@ import { gateIdRecordKey, hasWildcardGate, planGates } from './gates.js';
|
|
|
26
26
|
import { githubSourceResourceKey, planGithubSource } from './github-source.js';
|
|
27
27
|
import { appHostname, tunnelDnsHostnames } from './hostnames.js';
|
|
28
28
|
import { planKeysRegistrar } from './keys-registrar.js';
|
|
29
|
+
import { desiredKosmosEnv, kosmosEnvDrift } from './kosmos.js';
|
|
29
30
|
import { APP_GONE_KIND } from './live.js';
|
|
30
31
|
import { lockedHostnames } from './locked-hostnames.js';
|
|
31
32
|
import { LOGIN_BINDING_SCOPE_KEY, LOGIN_CLIENT_ORGS_SCOPE_KEY, OWN_CUSTOMERS_BASE_URI_SCOPE_KEY, agoraIssuerHostFor, clientOrganizationsState, companyLoginTunnelHost, customersClientOrganizations, inAppApps, loginBindingState, loginHostClientIds, loginHostServesAgora, loginHostnameFor, ownAuthApplied, ownCustomersAppState, pendingRemovalHosts, } from './login-host.js';
|
|
@@ -351,7 +352,7 @@ export function computePlanDetail(declaredOrg, live, recordedIds = {}, agoraHost
|
|
|
351
352
|
const chat = planChat(org, byKey, claimed, recordedIds, agoraHost, issuer.step !== null);
|
|
352
353
|
if (chat.step)
|
|
353
354
|
plannedSteps.push(chat.step);
|
|
354
|
-
const calls = planCalls(org, byKey, claimed
|
|
355
|
+
const calls = planCalls(org, byKey, claimed);
|
|
355
356
|
if (calls.step)
|
|
356
357
|
plannedSteps.push(calls.step);
|
|
357
358
|
// An own org's media server, before the calls service that reads its addresses.
|
|
@@ -711,7 +712,16 @@ export function computePlanDetail(declaredOrg, live, recordedIds = {}, agoraHost
|
|
|
711
712
|
}, customerGrantsShape);
|
|
712
713
|
if (boule.step)
|
|
713
714
|
plannedSteps.push(boule.step);
|
|
714
|
-
//
|
|
715
|
+
// foundry#949: the company's kosmos, only once it turns it on. Its fixed ZITADEL client first, the same
|
|
716
|
+
// reason Boule's and Agora's do: `coolifyKosmos` reads this step's recorded client id.
|
|
717
|
+
const kosmosClient = planKosmosClient(org, byKey, claimed);
|
|
718
|
+
const kosmosGuarded = guardApp(kosmosClient);
|
|
719
|
+
if (kosmosGuarded)
|
|
720
|
+
plannedSteps.push(kosmosGuarded);
|
|
721
|
+
const kosmos = planKosmos(org, byKey, claimed, recordedIds, authIssuer, options.otherSlugs ?? []);
|
|
722
|
+
if (kosmos.step)
|
|
723
|
+
plannedSteps.push(kosmos.step);
|
|
724
|
+
// Boule's, Agora's and kosmos's blue/green: after their own steps, which make the app it copies.
|
|
715
725
|
plannedSteps.push(...blueGreen.steps.filter((x) => x.phase === 'last').map(blueGreenDraft()));
|
|
716
726
|
// Boule owners, after the Boule step: needs the app deployed and the seeding secret in its env.
|
|
717
727
|
const bouleOwners = planBouleOwners(org, recordedIds, (byKey.get(resourceKey('coolify', 'boule', org.id))?.config.env ?? null));
|
|
@@ -977,6 +987,8 @@ export function computePlanDetail(declaredOrg, live, recordedIds = {}, agoraHost
|
|
|
977
987
|
if (boule.mailBlocked) {
|
|
978
988
|
unmanaged.push({ description: boule.mailBlocked, key: 'coolify:boule:mail' });
|
|
979
989
|
}
|
|
990
|
+
if (kosmos.blocked)
|
|
991
|
+
unmanaged.push({ description: kosmos.blocked, key: 'coolify:kosmos' });
|
|
980
992
|
// Same for an ai key minted but never delivered: planning a new mint would
|
|
981
993
|
// orphan a second live key.
|
|
982
994
|
if (aiKey.blocked)
|
|
@@ -1769,8 +1781,8 @@ function loginHostWaiting(org, host, recordedIds, bindingsWired, issuerPlanned)
|
|
|
1769
1781
|
* actually need it -- CMS's, chat's and calls' entries follow their own
|
|
1770
1782
|
* on/off switch, the same ones this file already gates their own
|
|
1771
1783
|
* `coolify:*` steps on (`org.ts`'s `SiteConfig.enabled`, `ChatConfig.enabled`,
|
|
1772
|
-
* `CallsConfig.enabled`);
|
|
1773
|
-
* "every org gets one" rule; the app schema follows whether any
|
|
1784
|
+
* `CallsConfig.enabled`); kosmos follows its own switch too (`KosmosApp.enabled`, foundry#949); Boule's is
|
|
1785
|
+
* unconditional, matching `org.ts`'s own "every org gets one" rule; the app schema follows whether any
|
|
1774
1786
|
* `hosting.entries` entry declares `database: true` (`coolifyDatabase`'s own
|
|
1775
1787
|
* gate) rather than `hosting.enabled` alone, since a hosting org with no
|
|
1776
1788
|
* database-bearing entry has nothing for this schema to hold.
|
|
@@ -1778,6 +1790,8 @@ function loginHostWaiting(org, host, recordedIds, bindingsWired, issuerPlanned)
|
|
|
1778
1790
|
function orgServiceSchemaEnabled(org, schema) {
|
|
1779
1791
|
if (schema.schema === 'cms')
|
|
1780
1792
|
return org.site.enabled;
|
|
1793
|
+
if (schema.schema === 'kosmos')
|
|
1794
|
+
return kosmosOn(org);
|
|
1781
1795
|
if (schema.schema.startsWith('chat_'))
|
|
1782
1796
|
return org.chat.enabled;
|
|
1783
1797
|
if (schema.schema.startsWith('calls_'))
|
|
@@ -2197,75 +2211,47 @@ function planChat(org, byKey, claimed, recordedIds, reading, issuerPlanned) {
|
|
|
2197
2211
|
* matrix-authentication-service, entirely separate from chat's
|
|
2198
2212
|
* (`plan/calls-matrix.ts`'s own top doc comment: a call guest must never gain
|
|
2199
2213
|
* chat membership). Unlike chat, off by default: `org.ts`'s
|
|
2200
|
-
* `CallsConfig.enabled` doc comment.
|
|
2201
|
-
* {@link planApp}/{@link planDatabase}/{@link planTunnel} below
|
|
2202
|
-
* (`planHostingToggle`, #7 corrections 1) -- turning it off parks Synapse and
|
|
2203
|
-
* MAS, never either database (`executor.ts`'s `coolifyCalls`).
|
|
2214
|
+
* `CallsConfig.enabled` doc comment.
|
|
2204
2215
|
*
|
|
2205
|
-
* #
|
|
2206
|
-
*
|
|
2207
|
-
*
|
|
2208
|
-
*
|
|
2209
|
-
*
|
|
2210
|
-
*
|
|
2216
|
+
* #430: calls run transit-only now (calls#93's `CALLS_ENCRYPTION=transit`),
|
|
2217
|
+
* so the calls service no longer talks to a per-org Matrix stack and this
|
|
2218
|
+
* never provisions a new one, nor re-provisions a parked one -- the live
|
|
2219
|
+
* resource is still claimed, so it is never reported unmanaged. An org
|
|
2220
|
+
* whose stack is already live and enabled is left exactly as it is;
|
|
2221
|
+
* removing it is a separate, human-confirmed step. Turning `calls.enabled`
|
|
2222
|
+
* off still parks an already-live stack unchanged (`executor.ts`'s
|
|
2223
|
+
* `coolifyCalls`, #7 corrections 1): data is retained, not deleted.
|
|
2211
2224
|
*/
|
|
2212
|
-
function planCalls(org, byKey, claimed
|
|
2225
|
+
function planCalls(org, byKey, claimed) {
|
|
2213
2226
|
const key = resourceKey('coolify', 'calls', org.id);
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
};
|
|
2221
|
-
}
|
|
2222
|
-
// #248: the two images this stack's own apply would run, checked the same
|
|
2223
|
-
// way the platform's SFU checks LiveKit/cloudflared and ZITADEL checks its
|
|
2224
|
-
// own tag -- a re-apply of the same declaration must not silently run a
|
|
2225
|
-
// different version with no diff shown anywhere.
|
|
2226
|
-
if (org.calls.enabled && !byKey.get(key)) {
|
|
2227
|
-
const synapseBlocked = floatingTagBlocked('Synapse', synapseTag ?? VERSIONS.synapse, 'v1.161.0');
|
|
2228
|
-
if (synapseBlocked) {
|
|
2229
|
-
return {
|
|
2230
|
-
step: null,
|
|
2231
|
-
blocked: `${org.slug}'s calls Matrix stack is blocked, not planned: ${synapseBlocked}`,
|
|
2232
|
-
};
|
|
2233
|
-
}
|
|
2234
|
-
const masBlocked = floatingTagBlocked('matrix-authentication-service', VERSIONS.mas, '1.24.0');
|
|
2235
|
-
if (masBlocked) {
|
|
2236
|
-
return {
|
|
2237
|
-
step: null,
|
|
2238
|
-
blocked: `${org.slug}'s calls Matrix stack is blocked, not planned: ${masBlocked}`,
|
|
2239
|
-
};
|
|
2240
|
-
}
|
|
2241
|
-
}
|
|
2227
|
+
const live = byKey.get(key);
|
|
2228
|
+
if (live)
|
|
2229
|
+
claimed.add(key);
|
|
2230
|
+
const liveEnabled = live !== undefined && live.config.enabled !== false;
|
|
2231
|
+
if (org.calls.enabled && !liveEnabled)
|
|
2232
|
+
return { step: null, blocked: null };
|
|
2242
2233
|
const toggle = planHostingToggle('coolify', 'calls', org.calls.enabled, org, byKey, claimed, () => `provision ${org.slug}'s calls Matrix stack (Synapse + matrix-authentication-service)`, () => `park ${org.slug}'s calls Matrix stack (stops serving; data is retained, not deleted -- #7 corrections 1)`);
|
|
2243
|
-
// A held Synapse tag rides in the provisioning step; a parking step carries none.
|
|
2244
|
-
if (toggle && synapseTag && toggle.action.desired.enabled === true) {
|
|
2245
|
-
toggle.action.desired = { ...toggle.action.desired, synapseTag };
|
|
2246
|
-
}
|
|
2247
2234
|
return { step: toggle, blocked: null };
|
|
2248
2235
|
}
|
|
2249
2236
|
/**
|
|
2250
2237
|
* The org's own calls service (#145, part 2 of #128) -- `wtfalch/calls`'s
|
|
2251
|
-
* `packages/service`,
|
|
2252
|
-
*
|
|
2253
|
-
*
|
|
2254
|
-
*
|
|
2255
|
-
* authenticate a guest against, and a Matrix stack alone answers no browser,
|
|
2256
|
-
* so the two are declared as one on/off switch even though they are two
|
|
2257
|
-
* planner steps. Same on/off, park-not-delete shape as {@link planCalls}
|
|
2258
|
-
* (`planHostingToggle`, #7 corrections 1) -- turning it off parks the app,
|
|
2259
|
-
* never its database (`executor.ts`'s `coolifyCallsService`).
|
|
2238
|
+
* `packages/service`, wired to the shared LiveKit SFU (foundry#122/#127).
|
|
2239
|
+
* Same on/off shape as {@link planCalls} (`planHostingToggle`, #7
|
|
2240
|
+
* corrections 1) -- turning it off parks the app, never its database
|
|
2241
|
+
* (`executor.ts`'s `coolifyCallsService`).
|
|
2260
2242
|
*
|
|
2261
|
-
* #
|
|
2262
|
-
* stack
|
|
2263
|
-
*
|
|
2243
|
+
* #430: calls run transit-only (calls#93), so this no longer rides behind
|
|
2244
|
+
* the Matrix stack {@link planCalls} used to provision -- there is none to
|
|
2245
|
+
* ride behind any more -- and reports the shared SFU as missing itself
|
|
2246
|
+
* (#177) rather than relying on `planCalls`'s now-removed report.
|
|
2264
2247
|
*/
|
|
2265
2248
|
function planCallsService(org, byKey, claimed, callsReady, recordedIds, callsBlocked, tokenHost) {
|
|
2266
2249
|
const key = resourceKey('coolify', 'calls-service', org.id);
|
|
2267
|
-
if (org.calls.enabled && !byKey.get(key) && !callsReady) {
|
|
2268
|
-
return {
|
|
2250
|
+
if (org.calls.enabled && !byKey.get(key) && !callsReady && !ownsServices(org)) {
|
|
2251
|
+
return {
|
|
2252
|
+
step: null,
|
|
2253
|
+
blocked: `${org.slug}'s calls service is blocked, not planned: the platform owner's calls SFU (the platform's) is off`,
|
|
2254
|
+
};
|
|
2269
2255
|
}
|
|
2270
2256
|
// An own org's service names its own media server, so it waits for it even when already provisioned.
|
|
2271
2257
|
if (org.calls.enabled && ownsServices(org) && !callsReady) {
|
|
@@ -3081,6 +3067,137 @@ function planBouleClient(org, byKey, claimed) {
|
|
|
3081
3067
|
},
|
|
3082
3068
|
};
|
|
3083
3069
|
}
|
|
3070
|
+
/**
|
|
3071
|
+
* foundry#949: the fixed ZITADEL web client `kosmos`, once the company turns kosmos on. The same shape as
|
|
3072
|
+
* {@link planBouleClient} and {@link planAgoraClient}; `kosmosOn(org)` is false before any client is planned.
|
|
3073
|
+
*/
|
|
3074
|
+
function planKosmosClient(org, byKey, claimed) {
|
|
3075
|
+
if (!kosmosOn(org))
|
|
3076
|
+
return null;
|
|
3077
|
+
const key = resourceKey('zitadel', 'app', 'kosmos');
|
|
3078
|
+
const live = byKey.get(key);
|
|
3079
|
+
if (live)
|
|
3080
|
+
claimed.add(key);
|
|
3081
|
+
const desired = { appType: 'web' };
|
|
3082
|
+
if (!live) {
|
|
3083
|
+
return {
|
|
3084
|
+
action: { system: 'zitadel', kind: 'app', key: 'kosmos', desired },
|
|
3085
|
+
step: {
|
|
3086
|
+
description: `provision the web ZITADEL app 'kosmos' for ${org.slug}'s kosmos sign-in`,
|
|
3087
|
+
destroys: false,
|
|
3088
|
+
migrates: false,
|
|
3089
|
+
},
|
|
3090
|
+
};
|
|
3091
|
+
}
|
|
3092
|
+
if (live.config.appType === 'web')
|
|
3093
|
+
return null;
|
|
3094
|
+
return {
|
|
3095
|
+
action: {
|
|
3096
|
+
system: 'zitadel',
|
|
3097
|
+
kind: 'app',
|
|
3098
|
+
key: 'kosmos',
|
|
3099
|
+
desired: { ...desired, recreate: true },
|
|
3100
|
+
},
|
|
3101
|
+
step: {
|
|
3102
|
+
description: `recreate ZITADEL app 'kosmos' for ${org.slug} as web (was ${String(live.config.appType)})`,
|
|
3103
|
+
destroys: true,
|
|
3104
|
+
migrates: false,
|
|
3105
|
+
},
|
|
3106
|
+
};
|
|
3107
|
+
}
|
|
3108
|
+
/**
|
|
3109
|
+
* foundry#949: the org's own kosmos, a per-org singleton keyed by the org id under its own `kosmos` kind, built
|
|
3110
|
+
* from https://github.com/wtfalch/kosmos's root Dockerfile (`executor.ts`'s `coolifyKosmos`), with a database of
|
|
3111
|
+
* its own. Unlike Boule and Agora, kosmos is set up only once the company turns it on (`kosmosOn(org)`); while
|
|
3112
|
+
* it is off, an org that never had it plans nothing, and a live app is parked (stopped, database kept), the
|
|
3113
|
+
* same rule a switched-off service always follows here.
|
|
3114
|
+
*
|
|
3115
|
+
* Once live, a step is planned again whenever the live app's sign-in env lacks a desired key or holds a
|
|
3116
|
+
* different value (`kosmos.ts`'s `desiredKosmosEnv`, the function the executor writes from). The only host
|
|
3117
|
+
* value is the issuer address (the one Agora and Boule sign in at); without it kosmos is blocked, not planned,
|
|
3118
|
+
* so every other step still applies.
|
|
3119
|
+
*/
|
|
3120
|
+
function planKosmos(org, byKey, claimed, recordedIds, issuer, otherSlugs) {
|
|
3121
|
+
if (!kosmosOn(org)) {
|
|
3122
|
+
// Switched off: park a live app (stop it, keep the database), like CMS and calls. A stopped app is
|
|
3123
|
+
// already parked, so the plan reaches empty.
|
|
3124
|
+
const parked = planHostingToggle('coolify', 'kosmos', false, org, byKey, claimed, () => `provision ${org.slug}'s kosmos`, () => `park ${org.slug}'s kosmos (stops serving; its database is retained, not deleted)`, org.id, (live) => !/^(exited|stopped)/.test(String(live.config.status ?? '')));
|
|
3125
|
+
return { step: parked, blocked: null };
|
|
3126
|
+
}
|
|
3127
|
+
const key = resourceKey('coolify', 'kosmos', org.id);
|
|
3128
|
+
const live = byKey.get(key);
|
|
3129
|
+
if (live)
|
|
3130
|
+
claimed.add(key);
|
|
3131
|
+
// Never truncated: Postgres cuts an identifier at 63 bytes, and a DNS label
|
|
3132
|
+
// over 63 is refused by Coolify and Cloudflare -- the same check `planBoule` makes.
|
|
3133
|
+
const tooLong = [
|
|
3134
|
+
[`${org.slug}-kosmos`, 'the kosmos host label and app name'],
|
|
3135
|
+
[`${org.slug}_kosmos_db_rt`, 'the kosmos database role'],
|
|
3136
|
+
].filter(([name]) => name.length > 63);
|
|
3137
|
+
if (tooLong.length > 0) {
|
|
3138
|
+
return {
|
|
3139
|
+
step: null,
|
|
3140
|
+
blocked: `${org.slug}'s kosmos is blocked, not planned: ${tooLong.map(([n, what]) => `${what} '${n}' is over 63 characters`).join(', ')}; shorten the slug`,
|
|
3141
|
+
};
|
|
3142
|
+
}
|
|
3143
|
+
if (otherSlugs.includes(`${org.slug}-kosmos`)) {
|
|
3144
|
+
return {
|
|
3145
|
+
step: null,
|
|
3146
|
+
blocked: `${org.slug}'s kosmos is blocked, not planned: another declared org has the slug '${org.slug}-kosmos', which is this kosmos's hostname`,
|
|
3147
|
+
};
|
|
3148
|
+
}
|
|
3149
|
+
if (!issuer) {
|
|
3150
|
+
return {
|
|
3151
|
+
step: null,
|
|
3152
|
+
blocked: `${org.slug}'s kosmos is blocked, not planned: the host supplies no issuer`,
|
|
3153
|
+
};
|
|
3154
|
+
}
|
|
3155
|
+
const action = {
|
|
3156
|
+
system: 'coolify',
|
|
3157
|
+
kind: 'kosmos',
|
|
3158
|
+
key: org.id,
|
|
3159
|
+
desired: { issuer },
|
|
3160
|
+
};
|
|
3161
|
+
if (!live) {
|
|
3162
|
+
return {
|
|
3163
|
+
step: {
|
|
3164
|
+
action,
|
|
3165
|
+
step: { description: `provision ${org.slug}'s kosmos`, destroys: false, migrates: false },
|
|
3166
|
+
},
|
|
3167
|
+
blocked: null,
|
|
3168
|
+
};
|
|
3169
|
+
}
|
|
3170
|
+
const liveEnv = (live.config.env ?? {});
|
|
3171
|
+
const { env } = desiredKosmosEnv(org, recordedIds, issuer);
|
|
3172
|
+
const drift = kosmosEnvDrift(env, liveEnv);
|
|
3173
|
+
// Switched back on while the app is still stopped: the step starts it (`coolifyKosmos` starts a stopped app).
|
|
3174
|
+
if (/^(exited|stopped)/.test(String(live.config.status ?? ''))) {
|
|
3175
|
+
return {
|
|
3176
|
+
step: {
|
|
3177
|
+
action,
|
|
3178
|
+
step: {
|
|
3179
|
+
description: `start ${org.slug}'s kosmos again (it was parked)`,
|
|
3180
|
+
destroys: false,
|
|
3181
|
+
migrates: false,
|
|
3182
|
+
},
|
|
3183
|
+
},
|
|
3184
|
+
blocked: null,
|
|
3185
|
+
};
|
|
3186
|
+
}
|
|
3187
|
+
if (drift.length === 0)
|
|
3188
|
+
return { step: null, blocked: null };
|
|
3189
|
+
return {
|
|
3190
|
+
step: {
|
|
3191
|
+
action,
|
|
3192
|
+
step: {
|
|
3193
|
+
description: `update ${org.slug}'s kosmos config (${drift.join(', ')} missing or different)`,
|
|
3194
|
+
destroys: false,
|
|
3195
|
+
migrates: false,
|
|
3196
|
+
},
|
|
3197
|
+
},
|
|
3198
|
+
blocked: null,
|
|
3199
|
+
};
|
|
3200
|
+
}
|
|
3084
3201
|
/**
|
|
3085
3202
|
* C3: which credentials the org's Boule is given, and why one is not. Archon's credential goes to every
|
|
3086
3203
|
* Boule the host can mint it for; the customers directory credential only while `customers.enabled`. Each
|
package/dist/plan/edge-access.js
CHANGED
|
@@ -35,6 +35,7 @@ export function createEdgeAccess(client, options = {}) {
|
|
|
35
35
|
createGate: (input) => retrying(() => client.createGate(input)),
|
|
36
36
|
replaceGateHostnames: (id, input) => retrying(() => client.replaceGateHostnames(id, input)),
|
|
37
37
|
deleteGate: (id) => retrying(() => client.deleteGate(id)),
|
|
38
|
+
setGatePeople: (id, input) => retrying(() => client.setGatePeople(id, input)),
|
|
38
39
|
};
|
|
39
40
|
const writeSlot = options.writeSlot ?? limiter(EDGE_DNS_WRITE_CONCURRENCY);
|
|
40
41
|
/** app id -> a seen live app with no edge row yet. */
|
package/dist/plan/executor.d.ts
CHANGED
|
@@ -1966,6 +1966,15 @@ export declare const BOULE_REPO = "wtfalch/boule";
|
|
|
1966
1966
|
export declare const BOULE_APP_DOCKERFILE = "/Dockerfile";
|
|
1967
1967
|
/** Boule's own `EXPOSE 3000` (its doc comment just above) -- the port its health check (#543) probes. */
|
|
1968
1968
|
export declare const BOULE_PORT = 3000;
|
|
1969
|
+
/**
|
|
1970
|
+
* foundry#949: kosmos (https://github.com/wtfalch/kosmos), bootstrapped from the same `wtfalch/package-template`
|
|
1971
|
+
* service shape Boule is, with its own Dockerfile at the repository root, serving on 3000 (`EXPOSE 3000`) and
|
|
1972
|
+
* running its migrations through `apps/host/scripts/migrate.mjs` before it serves, the same shape Boule's
|
|
1973
|
+
* `migrate-and-start.sh` follows.
|
|
1974
|
+
*/
|
|
1975
|
+
export declare const KOSMOS_REPO = "wtfalch/kosmos";
|
|
1976
|
+
export declare const KOSMOS_APP_DOCKERFILE = "/Dockerfile";
|
|
1977
|
+
export declare const KOSMOS_PORT = 3000;
|
|
1969
1978
|
export declare const LOG_DIR = "/app/logs";
|
|
1970
1979
|
/** The company's Coolify destination (its Docker network `co-<slug>`), recorded when its first resource is created. */
|
|
1971
1980
|
export declare const COOLIFY_DESTINATION_SCOPE_KEY = "coolify_destination";
|
|
@@ -3145,16 +3154,18 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
|
|
|
3145
3154
|
private coolifyChatRekey;
|
|
3146
3155
|
/**
|
|
3147
3156
|
* `plan/diff.ts`'s `planCallsService`: the org's own calls service --
|
|
3148
|
-
* `wtfalch/calls`'s `packages/service`, wired to the
|
|
3149
|
-
*
|
|
3150
|
-
*
|
|
3151
|
-
*
|
|
3152
|
-
*
|
|
3153
|
-
*
|
|
3154
|
-
*
|
|
3155
|
-
*
|
|
3156
|
-
*
|
|
3157
|
-
*
|
|
3157
|
+
* `wtfalch/calls`'s `packages/service`, wired to the shared LiveKit SFU
|
|
3158
|
+
* (#122/#127). #430: calls run transit-only (calls#93's
|
|
3159
|
+
* `CALLS_ENCRYPTION=transit`), so this no longer waits on or wires a
|
|
3160
|
+
* per-org Matrix stack at all -- an org whose stack is already live from
|
|
3161
|
+
* before this keeps running it, unused, until a human removes it
|
|
3162
|
+
* separately. Static config checks run first, before any Coolify call
|
|
3163
|
+
* (#128's own carried-over PR 344 review fix, restated in #145's own
|
|
3164
|
+
* issue body): the shared SFU's LiveKit credentials must already be
|
|
3165
|
+
* resolvable -- not minted or guessed here. Park stops the app only, the
|
|
3166
|
+
* same park-not-delete rule {@link coolifyCalls}/{@link coolifyApp}
|
|
3167
|
+
* already follow (#7 corrections 1); its own database is never touched
|
|
3168
|
+
* from that branch.
|
|
3158
3169
|
*/
|
|
3159
3170
|
private coolifyCallsService;
|
|
3160
3171
|
/**
|
|
@@ -3187,14 +3198,6 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
|
|
|
3187
3198
|
* env and mints nothing.
|
|
3188
3199
|
*/
|
|
3189
3200
|
private callsTokenEnvs;
|
|
3190
|
-
/**
|
|
3191
|
-
* Reads back the calls Matrix stack's MAS client id/secret from the MAS
|
|
3192
|
-
* app's own envs ({@link CALLS_SECRET_ENV_KEYS}) -- never mints: minting
|
|
3193
|
-
* either is {@link ensureCallsMatrixSecrets}'s own job, and a missing value
|
|
3194
|
-
* here means that stack has not finished provisioning yet, not something
|
|
3195
|
-
* this step can fix on its own.
|
|
3196
|
-
*/
|
|
3197
|
-
private readCallsMasClientCredentials;
|
|
3198
3201
|
/**
|
|
3199
3202
|
* `null` once the calls service app already has a `CALLS_BINDING_SECRET` --
|
|
3200
3203
|
* never rotated, the same rule {@link ensureAuthCookieSecret} follows for
|
|
@@ -3327,6 +3330,25 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
|
|
|
3327
3330
|
* shape {@link coolifyAgora}'s own doc comment explains.
|
|
3328
3331
|
*/
|
|
3329
3332
|
private coolifyBoule;
|
|
3333
|
+
/**
|
|
3334
|
+
* `plan/diff.ts`'s `planKosmos`: provisions the org's own kosmos
|
|
3335
|
+
* (https://github.com/wtfalch/kosmos) as one more Coolify application, from
|
|
3336
|
+
* that repo's root Dockerfile ({@link KOSMOS_REPO}), with a database of its
|
|
3337
|
+
* own, and pushes its sign-in env. The same shape as {@link coolifyBoule},
|
|
3338
|
+
* trimmed: kosmos is opt-in (`kosmosOn(org)` guards every caller, so this is
|
|
3339
|
+
* never reached while it is off), has no mail, no owner seeding and no
|
|
3340
|
+
* Archon credential.
|
|
3341
|
+
*
|
|
3342
|
+
* The sign-in env comes from `plan/kosmos.ts`'s `desiredKosmosEnv`, the same
|
|
3343
|
+
* function the plan compares the live app against; it never writes a
|
|
3344
|
+
* partial set. The database is `<slug>-kosmos-db`, split the same way
|
|
3345
|
+
* Boule's is: the superuser URL as `DATABASE_URL_OWNER` and a `<dbName>_rt`
|
|
3346
|
+
* role's URL as `DATABASE_URL`, both through `pushDatabaseUrl`.
|
|
3347
|
+
*
|
|
3348
|
+
* #504 (same reason {@link coolifyBoule}'s own comment gives): a brand new
|
|
3349
|
+
* app is never restarted, but it is deployed once ({@link KOSMOS_DEPLOYED_SCOPE_KEY}).
|
|
3350
|
+
*/
|
|
3351
|
+
private coolifyKosmos;
|
|
3330
3352
|
/**
|
|
3331
3353
|
* C3: the Archon credential of an org's Boule. Archon's mint rotates on every call, so it is called only
|
|
3332
3354
|
* when none was pushed or the recorded push time (no secret) is 335 days old. Otherwise the held value is
|