@wtfalch/foundry 0.26.1 → 0.26.3
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/plan/agora.d.ts +7 -0
- package/dist/plan/agora.js +7 -0
- package/dist/plan/diff.js +6 -3
- package/dist/plan/executor.d.ts +8 -1
- package/dist/plan/executor.js +58 -22
- package/package.json +1 -1
package/dist/plan/agora.d.ts
CHANGED
|
@@ -128,6 +128,13 @@ export declare const AGORA_APP_PORTS_EXPOSES = "8080";
|
|
|
128
128
|
* so Agora is never given an address that does not resolve yet.
|
|
129
129
|
*/
|
|
130
130
|
export declare const CALLS_SERVICE_NAME_SCOPE_KEY = "coolify_calls_service_internal_name";
|
|
131
|
+
/**
|
|
132
|
+
* Recorded by the executor once the calls service has been deployed and
|
|
133
|
+
* healthy. `createGithubAppApplication` never deploys a new app, so without
|
|
134
|
+
* it `planCallsService` finds every marker and plans nothing (same shape as
|
|
135
|
+
* {@link AGORA_DEPLOYED_SCOPE_KEY}).
|
|
136
|
+
*/
|
|
137
|
+
export declare const CALLS_SERVICE_DEPLOYED_SCOPE_KEY = "coolify_calls_service_deployed";
|
|
131
138
|
/**
|
|
132
139
|
* The calls service app's fixed internal name: the executor gives the app
|
|
133
140
|
* this `custom_internal_name`, so Agora reaches it as
|
package/dist/plan/agora.js
CHANGED
|
@@ -136,6 +136,13 @@ export const AGORA_APP_PORTS_EXPOSES = '8080';
|
|
|
136
136
|
* so Agora is never given an address that does not resolve yet.
|
|
137
137
|
*/
|
|
138
138
|
export const CALLS_SERVICE_NAME_SCOPE_KEY = 'coolify_calls_service_internal_name';
|
|
139
|
+
/**
|
|
140
|
+
* Recorded by the executor once the calls service has been deployed and
|
|
141
|
+
* healthy. `createGithubAppApplication` never deploys a new app, so without
|
|
142
|
+
* it `planCallsService` finds every marker and plans nothing (same shape as
|
|
143
|
+
* {@link AGORA_DEPLOYED_SCOPE_KEY}).
|
|
144
|
+
*/
|
|
145
|
+
export const CALLS_SERVICE_DEPLOYED_SCOPE_KEY = 'coolify_calls_service_deployed';
|
|
139
146
|
/**
|
|
140
147
|
* The calls service app's fixed internal name: the executor gives the app
|
|
141
148
|
* this `custom_internal_name`, so Agora reaches it as
|
package/dist/plan/diff.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { VERSIONS, floatingTagBlocked } from '../bootstrap/versions.js';
|
|
2
2
|
import { APP_ENTRY_KEY, ownerMailboxRecorded, ownerMailboxes } from '../org.js';
|
|
3
|
-
import { AGORA_ACTIVATED_ENV_SCOPE_KEY, AGORA_APP_PORTS_EXPOSES, CALLS_INTROSPECTION_SCOPE_KEY, CALLS_INTROSPECTION_UNDELIVERED_SCOPE_KEY, CHAT_INTROSPECTION_SCOPE_KEY, NO_AGORA_HOST_VALUES, ZITADEL_ORG_SCOPE_KEY, agoraEnvDrift, agoraEnvRevision, agoraHostMissing, agoraHostnameFor, callsServiceHasName, callsServiceHasTokenEnv, callsTokenEnvMovedMessage, callsTokenHostFrom, desiredAgoraEnv, withRecordedMessagingProject, } from './agora.js';
|
|
3
|
+
import { AGORA_ACTIVATED_ENV_SCOPE_KEY, AGORA_APP_PORTS_EXPOSES, CALLS_INTROSPECTION_SCOPE_KEY, CALLS_INTROSPECTION_UNDELIVERED_SCOPE_KEY, CALLS_SERVICE_DEPLOYED_SCOPE_KEY, CHAT_INTROSPECTION_SCOPE_KEY, NO_AGORA_HOST_VALUES, ZITADEL_ORG_SCOPE_KEY, agoraEnvDrift, agoraEnvRevision, agoraHostMissing, agoraHostnameFor, callsServiceHasName, callsServiceHasTokenEnv, callsTokenEnvMovedMessage, callsTokenHostFrom, desiredAgoraEnv, withRecordedMessagingProject, } from './agora.js';
|
|
4
4
|
import { BOULE_BROKER_ENTRY_KEY, bouleEnvDrift, desiredBouleEnv, wantedBouleBrokerEntry, } from './boule.js';
|
|
5
5
|
import { brokerEntryAppliedScopeKey, brokerEntryWarningsScopeKey, entryAuthEnv, entryAuthEnvAppliedScopeKey, wantedBrokerEntry, } from './broker-entries.js';
|
|
6
6
|
import { chatInternalNames } from './chat-matrix.js';
|
|
@@ -1350,11 +1350,12 @@ function planCallsService(org, byKey, claimed, callsReady, recordedIds, callsBlo
|
|
|
1350
1350
|
const missingName = !callsServiceHasName(org, recordedIds);
|
|
1351
1351
|
const missingTokenEnv = !callsServiceHasTokenEnv(tokenHost, recordedIds);
|
|
1352
1352
|
const missingDbSplit = !recordedIds[serviceDbEnvAppliedScopeKey('calls-service')];
|
|
1353
|
+
const missingDeploy = !recordedIds[CALLS_SERVICE_DEPLOYED_SCOPE_KEY];
|
|
1353
1354
|
if (org.calls.enabled &&
|
|
1354
1355
|
callsReady &&
|
|
1355
1356
|
!callsBlocked &&
|
|
1356
1357
|
byKey.has(key) &&
|
|
1357
|
-
(missingName || missingTokenEnv || missingDbSplit)) {
|
|
1358
|
+
(missingName || missingTokenEnv || missingDbSplit || missingDeploy)) {
|
|
1358
1359
|
return {
|
|
1359
1360
|
step: withHost({
|
|
1360
1361
|
action: {
|
|
@@ -1368,7 +1369,9 @@ function planCallsService(org, byKey, claimed, callsReady, recordedIds, callsBlo
|
|
|
1368
1369
|
? `set the calls service's internal name for ${org.slug}`
|
|
1369
1370
|
: missingTokenEnv
|
|
1370
1371
|
? `set the calls service's ZITADEL token env for ${org.slug}`
|
|
1371
|
-
:
|
|
1372
|
+
: missingDbSplit
|
|
1373
|
+
? `give the calls service's owner and runtime database URLs for ${org.slug} and redeploy it (#239, #456)`
|
|
1374
|
+
: `deploy the calls service for ${org.slug}`,
|
|
1372
1375
|
destroys: false,
|
|
1373
1376
|
migrates: false,
|
|
1374
1377
|
},
|
package/dist/plan/executor.d.ts
CHANGED
|
@@ -135,7 +135,6 @@ export interface CoolifyPort {
|
|
|
135
135
|
name: string;
|
|
136
136
|
postgres_db: string;
|
|
137
137
|
postgres_user: string;
|
|
138
|
-
postgres_initdb_args?: string;
|
|
139
138
|
}): Promise<{
|
|
140
139
|
uuid: string;
|
|
141
140
|
internal_db_url: string;
|
|
@@ -2220,6 +2219,14 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
|
|
|
2220
2219
|
* `system:kind:key` slot of its own for the same reason.
|
|
2221
2220
|
*/
|
|
2222
2221
|
private ensureCallsDatabase;
|
|
2222
|
+
/**
|
|
2223
|
+
* Sets `POSTGRES_INITDB_ARGS` on a Postgres database, once: the official
|
|
2224
|
+
* image's entrypoint passes it to `initdb`, which Coolify's own
|
|
2225
|
+
* `postgres_initdb_args` field never does ({@link CALLS_SYNAPSE_DB_INITDB_ARGS}).
|
|
2226
|
+
* `/databases/{uuid}/envs` is not a typed port method yet, so this goes
|
|
2227
|
+
* through `raw`. Nothing here is a secret.
|
|
2228
|
+
*/
|
|
2229
|
+
private ensureDatabaseInitdbArgs;
|
|
2223
2230
|
/**
|
|
2224
2231
|
* Reads back {@link CALLS_SECRET_ENV_KEYS} from the MAS app's own envs
|
|
2225
2232
|
* before minting fresh ones -- this stack's only memory across a re-run
|
package/dist/plan/executor.js
CHANGED
|
@@ -2,7 +2,7 @@ import { randomBytes } from 'node:crypto';
|
|
|
2
2
|
import { URL } from 'node:url';
|
|
3
3
|
import { CALLS_SFU_PRIVATE_URL, CALLS_SFU_PUBLIC_URL } from '../calls-sfu.js';
|
|
4
4
|
import { APP_ENTRY_KEY, appEntry, filesCollectionEnvName, ownerMailboxRecorded, ownerMailboxes, sharedSecretRef, } from '../org.js';
|
|
5
|
-
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_NAME_SCOPE_KEY, CALLS_SERVICE_PORT, CALLS_SERVICE_TOKEN_ENV_SCOPE_KEY, CHAT_INTROSPECTION_SCOPE_KEY, CHAT_INTROSPECTION_UNDELIVERED_SCOPE_KEY, CHAT_SERVICE_URL_SCOPE_KEY, CMS_CLIENT_ID_SCOPE_KEY, ENTRY_CLIENT_ID_SCOPE_PREFIX, ZITADEL_MESSAGING_PROJECT_SCOPE_KEY, ZITADEL_ORG_SCOPE_KEY, agoraEnvDrift, agoraEnvRevision, agoraHostnameFor, callsIntrospectionAppName, callsServiceHasName, callsServiceInternalName, callsTokenEnvMarker, callsTokenEnvMovedMessage, chatIntrospectionAppName, desiredAgoraEnv, entryClientIdScopeKey, } from './agora.js';
|
|
5
|
+
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_INTROSPECTION_SCOPE_KEY, CHAT_INTROSPECTION_UNDELIVERED_SCOPE_KEY, CHAT_SERVICE_URL_SCOPE_KEY, CMS_CLIENT_ID_SCOPE_KEY, ENTRY_CLIENT_ID_SCOPE_PREFIX, ZITADEL_MESSAGING_PROJECT_SCOPE_KEY, ZITADEL_ORG_SCOPE_KEY, agoraEnvDrift, agoraEnvRevision, agoraHostnameFor, callsIntrospectionAppName, callsServiceHasName, callsServiceInternalName, callsTokenEnvMarker, callsTokenEnvMovedMessage, chatIntrospectionAppName, desiredAgoraEnv, entryClientIdScopeKey, } from './agora.js';
|
|
6
6
|
import { BOULE_BROKER_ENTRY_KEY, BOULE_CLIENT_ID_SCOPE_KEY, BOULE_DATABASE_SCOPE_KEY, BOULE_DEPLOYED_SCOPE_KEY, bouleEnvDrift, bouleHostnameFor, desiredBouleEnv, wantedBouleBrokerEntry, } from './boule.js';
|
|
7
7
|
import { brokerEntryAppliedScopeKey, brokerEntryWarningsScopeKey, entryAuthEnv, entryAuthEnvAppliedScopeKey, wantedBrokerEntry, writeBrokerEntry, } from './broker-entries.js';
|
|
8
8
|
import { MAS_IMAGE, MAS_TAG, SYNAPSE_IMAGE, SYNAPSE_TAG, callsMasConfigYaml, callsServerName, callsSynapseHomeserverYaml, mintCallsMatrixSecrets, parseCallsDbUrl, } from './calls-matrix.js';
|
|
@@ -567,21 +567,17 @@ const CALLS_MAS_DB_SCOPE_KEY = 'coolify_calls_mas_database';
|
|
|
567
567
|
* Synapse refuses to start against a database whose `LC_COLLATE`/`LC_CTYPE`
|
|
568
568
|
* is not `C` (https://element-hq.github.io/synapse/latest/postgres.html);
|
|
569
569
|
* Coolify's official postgres image otherwise creates one with `en_US.utf8`.
|
|
570
|
-
* `
|
|
571
|
-
* `
|
|
572
|
-
*
|
|
573
|
-
*
|
|
574
|
-
*
|
|
575
|
-
*
|
|
576
|
-
*
|
|
577
|
-
*
|
|
578
|
-
*
|
|
579
|
-
*
|
|
580
|
-
*
|
|
581
|
-
* such requirement, so this is passed for the Synapse database only. Not
|
|
582
|
-
* using Synapse's own `allow_unsafe_locale` escape hatch regardless of
|
|
583
|
-
* which fix lands: that trades a loud startup refusal for silent index
|
|
584
|
-
* corruption after a future glibc upgrade.
|
|
570
|
+
* Coolify's `postgres_initdb_args` field is stored and never handed to
|
|
571
|
+
* `initdb` (its Postgres start action builds the container's env from
|
|
572
|
+
* `postgres_user`/`postgres_password`/`postgres_db` and runtime envs only,
|
|
573
|
+
* https://github.com/coollabsio/coolify/blob/main/app/Actions/Database/StartPostgresql.php),
|
|
574
|
+
* so foundry no longer sends it (#494). The official `postgres` image's
|
|
575
|
+
* entrypoint does read the `POSTGRES_INITDB_ARGS` env: `ensureCallsDatabase`
|
|
576
|
+
* sets it on the Synapse database before that database first starts. It
|
|
577
|
+
* cannot change a database whose data directory `initdb` already made; MAS
|
|
578
|
+
* has no such requirement, so the env goes on the Synapse database only. Not
|
|
579
|
+
* using Synapse's own `allow_unsafe_locale` escape hatch: that trades a loud
|
|
580
|
+
* startup refusal for silent index corruption after a future glibc upgrade.
|
|
585
581
|
*/
|
|
586
582
|
const CALLS_SYNAPSE_DB_INITDB_ARGS = '--locale=C --encoding=UTF8';
|
|
587
583
|
/**
|
|
@@ -3298,13 +3294,23 @@ export class FoundryStepExecutor {
|
|
|
3298
3294
|
* database, whose {@link CALLS_SERVICE_DB_SCOPE_KEY} has no
|
|
3299
3295
|
* `system:kind:key` slot of its own for the same reason.
|
|
3300
3296
|
*/
|
|
3301
|
-
async ensureCallsDatabase(org, context, projectUuid, environment, name, postgresDb, scopeKey,
|
|
3297
|
+
async ensureCallsDatabase(org, context, projectUuid, environment, name, postgresDb, scopeKey, initdbArgs) {
|
|
3298
|
+
// A database found by id or name may be one a failed run created and never
|
|
3299
|
+
// started (its env call threw), so the env is checked on every path. It
|
|
3300
|
+
// only takes effect before the first start; the callers start it later
|
|
3301
|
+
// (`getDatabaseStarted`).
|
|
3302
3302
|
const existing = context.recordedIds()[scopeKey];
|
|
3303
|
-
if (existing)
|
|
3303
|
+
if (existing) {
|
|
3304
|
+
if (initdbArgs)
|
|
3305
|
+
await this.ensureDatabaseInitdbArgs(existing, initdbArgs);
|
|
3304
3306
|
return existing;
|
|
3307
|
+
}
|
|
3305
3308
|
const found = (await this.ports.coolify.listDatabases()).find((d) => d.name === name && d.environment_id === environment.id);
|
|
3306
|
-
if (found)
|
|
3309
|
+
if (found) {
|
|
3310
|
+
if (initdbArgs)
|
|
3311
|
+
await this.ensureDatabaseInitdbArgs(found.uuid, initdbArgs);
|
|
3307
3312
|
return found.uuid;
|
|
3313
|
+
}
|
|
3308
3314
|
const server = await this.resolveServer(org, context);
|
|
3309
3315
|
const created = await this.ports.coolify.createPostgresDatabase({
|
|
3310
3316
|
project_uuid: projectUuid,
|
|
@@ -3314,10 +3320,27 @@ export class FoundryStepExecutor {
|
|
|
3314
3320
|
name,
|
|
3315
3321
|
postgres_db: postgresDb,
|
|
3316
3322
|
postgres_user: postgresDb,
|
|
3317
|
-
...(postgresInitdbArgs ? { postgres_initdb_args: postgresInitdbArgs } : {}),
|
|
3318
3323
|
});
|
|
3324
|
+
if (initdbArgs)
|
|
3325
|
+
await this.ensureDatabaseInitdbArgs(created.uuid, initdbArgs);
|
|
3319
3326
|
return created.uuid;
|
|
3320
3327
|
}
|
|
3328
|
+
/**
|
|
3329
|
+
* Sets `POSTGRES_INITDB_ARGS` on a Postgres database, once: the official
|
|
3330
|
+
* image's entrypoint passes it to `initdb`, which Coolify's own
|
|
3331
|
+
* `postgres_initdb_args` field never does ({@link CALLS_SYNAPSE_DB_INITDB_ARGS}).
|
|
3332
|
+
* `/databases/{uuid}/envs` is not a typed port method yet, so this goes
|
|
3333
|
+
* through `raw`. Nothing here is a secret.
|
|
3334
|
+
*/
|
|
3335
|
+
async ensureDatabaseInitdbArgs(dbId, args) {
|
|
3336
|
+
const envs = (await this.ports.coolify.raw('GET', `/databases/${dbId}/envs`));
|
|
3337
|
+
if (envs.some((e) => e.key === 'POSTGRES_INITDB_ARGS'))
|
|
3338
|
+
return;
|
|
3339
|
+
await this.ports.coolify.raw('POST', `/databases/${dbId}/envs`, {
|
|
3340
|
+
key: 'POSTGRES_INITDB_ARGS',
|
|
3341
|
+
value: args,
|
|
3342
|
+
});
|
|
3343
|
+
}
|
|
3321
3344
|
/**
|
|
3322
3345
|
* Reads back {@link CALLS_SECRET_ENV_KEYS} from the MAS app's own envs
|
|
3323
3346
|
* before minting fresh ones -- this stack's only memory across a re-run
|
|
@@ -3788,6 +3811,7 @@ export class FoundryStepExecutor {
|
|
|
3788
3811
|
name,
|
|
3789
3812
|
domains: coolifyTunnelDomains([callsHostnameFor(org)]),
|
|
3790
3813
|
dockerfile_location: CALLS_SERVICE_DOCKERFILE,
|
|
3814
|
+
ports_exposes: CALLS_SERVICE_PORT,
|
|
3791
3815
|
}));
|
|
3792
3816
|
// A fixed internal name, like Synapse and MAS above, so the org's Agora
|
|
3793
3817
|
// reaches this app as `http://<name>:8080` (`agora.ts`'s `AGORA_CALLS_UPSTREAM`).
|
|
@@ -3797,7 +3821,13 @@ export class FoundryStepExecutor {
|
|
|
3797
3821
|
// tells `desiredAgoraEnv` that the name and the env are both in place.
|
|
3798
3822
|
// No Coolify health check on the calls service (#543 review): its image
|
|
3799
3823
|
// is node:24-alpine with no curl, which Coolify's container check runs.
|
|
3800
|
-
|
|
3824
|
+
// A found app may predate `ports_exposes` on create; patch a differing one.
|
|
3825
|
+
const portDiffers = app.existed &&
|
|
3826
|
+
(await this.ports.coolify.getApplication(app.appId)).ports_exposes !== CALLS_SERVICE_PORT;
|
|
3827
|
+
await this.ports.coolify.updateApplication(app.appId, {
|
|
3828
|
+
custom_internal_name: name,
|
|
3829
|
+
...(portDiffers ? { ports_exposes: CALLS_SERVICE_PORT } : {}),
|
|
3830
|
+
});
|
|
3801
3831
|
// Every app foundry deploys keeps its own logs (#75), reused as-is here.
|
|
3802
3832
|
await this.ensureLogVolume(app.appId, org, app.existed);
|
|
3803
3833
|
const bindingSecret = await this.ensureCallsBindingSecret(app.appId);
|
|
@@ -3863,12 +3893,18 @@ export class FoundryStepExecutor {
|
|
|
3863
3893
|
// here would bring back a container built before that migrate step
|
|
3864
3894
|
// existed, still pointed at a role nothing has created.
|
|
3865
3895
|
const givesTokenEnv = token !== null && context.recordedIds()[CALLS_SERVICE_TOKEN_ENV_SCOPE_KEY] !== tokenMarker;
|
|
3866
|
-
|
|
3896
|
+
// A new app is never deployed by Coolify (`instant_deploy` defaults to
|
|
3897
|
+
// false), so a missing deployed marker or a port fix deploys too. One
|
|
3898
|
+
// deploy covers all three cases and the restart below never follows it.
|
|
3899
|
+
if ((app.existed && dbSplitNeedsRefresh) ||
|
|
3900
|
+
!context.recordedIds()[CALLS_SERVICE_DEPLOYED_SCOPE_KEY] ||
|
|
3901
|
+
portDiffers) {
|
|
3867
3902
|
// #543: every deploy waits for health -- the calls service's own
|
|
3868
3903
|
// public hostname and `/healthz`, the same path the quartet above
|
|
3869
3904
|
// just set on the app.
|
|
3870
3905
|
const runtime = this.ports.runtime ?? {};
|
|
3871
3906
|
await waitForNewDeployment(this.ports.coolify, app.appId, publicHealthCheck(org, context.recordedIds(), callsHostnameFor(org), CALLS_HEALTH_CHECK_PATH), runtime, runtime.sleep ?? defaultSleep, 'coolify:calls-service', `${org.slug}'s calls service stays at its previous deploy`);
|
|
3907
|
+
context.recordScopeId(CALLS_SERVICE_DEPLOYED_SCOPE_KEY, '1');
|
|
3872
3908
|
}
|
|
3873
3909
|
else if (app.existed && (!callsServiceHasName(org, context.recordedIds()) || givesTokenEnv)) {
|
|
3874
3910
|
await this.ports.coolify.restart(app.appId);
|