@wtfalch/foundry 0.8.0 → 0.10.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/bootstrap/declare.js +1 -5
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/dist/migrations/0029_hosting_entries.sql +109 -0
- package/dist/migrations/0030_hosting_entry_hostnames.sql +24 -0
- package/dist/org.d.ts +87 -29
- package/dist/org.js +180 -27
- package/dist/plan/agora.d.ts +7 -0
- package/dist/plan/agora.js +9 -0
- package/dist/plan/broker-entries.d.ts +104 -0
- package/dist/plan/broker-entries.js +214 -0
- package/dist/plan/diff.d.ts +2 -0
- package/dist/plan/diff.js +274 -50
- package/dist/plan/executor.d.ts +89 -29
- package/dist/plan/executor.js +495 -233
- package/dist/plan/hostnames.d.ts +24 -0
- package/dist/plan/hostnames.js +56 -0
- package/dist/plan/index.d.ts +3 -1
- package/dist/plan/index.js +2 -1
- package/dist/plan/locked-hostnames.d.ts +8 -0
- package/dist/plan/locked-hostnames.js +26 -0
- package/dist/plan/login-host.d.ts +1 -1
- package/dist/plan/login-host.js +5 -2
- package/dist/plan/plan-apply.d.ts +16 -0
- package/dist/plan/plan-apply.js +48 -11
- package/dist/plan/platform-diff.d.ts +69 -10
- package/dist/plan/platform-diff.js +133 -14
- package/dist/plan/platform-providers.d.ts +102 -2
- package/dist/plan/platform-providers.js +486 -40
- package/dist/plan/zitadel-system-api.d.ts +64 -0
- package/dist/plan/zitadel-system-api.js +125 -0
- package/dist/platform.d.ts +1 -1
- package/dist/store/postgres.js +12 -12
- package/dist/store/schema.d.ts +20 -1
- package/dist/store/schema.js +7 -1
- package/package.json +1 -1
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { VERSIONS } from '../bootstrap/versions.js';
|
|
2
|
+
import { APP_ENTRY_KEY } from '../org.js';
|
|
2
3
|
import { VERSION_CHANGE_KIND, } from '../plan.js';
|
|
4
|
+
import { legacyAppHostname } from './hostnames.js';
|
|
3
5
|
/**
|
|
4
6
|
* The `Plan` (#19's public contract) a {@link PlatformPlanDetail} carries,
|
|
5
7
|
* mirroring `diff.ts`'s `toPlan`. There is no separate platform-shaped `Plan`
|
|
@@ -136,6 +138,10 @@ export const PLATFORM_ARCHON_APPLIED_BROKER_SCOPE_KEY = 'archon_applied_broker_a
|
|
|
136
138
|
export const PLATFORM_ARCHON_GATE_CLOSED_SCOPE_KEY = 'archon_gate_closed';
|
|
137
139
|
export const PLATFORM_AUTH_ADMIN_USER_SCOPE_KEY = 'zitadel_platform_admin_user_id';
|
|
138
140
|
export const PLATFORM_AUTH_ADMIN_UNDELIVERED_SCOPE_KEY = 'zitadel_platform_admin_user_undelivered';
|
|
141
|
+
/** #283: the created instance's id, recorded once its owner token is delivered. */
|
|
142
|
+
export const PLATFORM_AUTH_INSTANCE_SCOPE_KEY = 'zitadel_platform_instance_id';
|
|
143
|
+
/** #283: an instance that may exist without a delivered owner token; blocks creating another. */
|
|
144
|
+
export const PLATFORM_AUTH_INSTANCE_UNDELIVERED_SCOPE_KEY = 'zitadel_platform_instance_undelivered';
|
|
139
145
|
/**
|
|
140
146
|
* #216 (building on #209/#215): the `IAM_LOGIN_CLIENT` machine user minted
|
|
141
147
|
* for the login app and the broker, mirroring
|
|
@@ -343,24 +349,78 @@ export function platformMailMxHost(apex) {
|
|
|
343
349
|
export const ORG_MAIL_APPLIED_APEX_SCOPE_KEY = 'mail_applied_apex';
|
|
344
350
|
export const ORG_MAIL_APPLIED_HOST_SCOPE_KEY = 'mail_applied_host';
|
|
345
351
|
/**
|
|
346
|
-
*
|
|
352
|
+
* The key of a hosting entry in a `coolify:app` or `cloudflare:access` step
|
|
353
|
+
* and in the ids recorded for it: `<orgId>:<entry key>`, so a second entry
|
|
354
|
+
* cannot collide with the first.
|
|
355
|
+
*/
|
|
356
|
+
export function appStepKey(org, entryKey = APP_ENTRY_KEY) {
|
|
357
|
+
return `${org.id}:${entryKey}`;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* The Coolify name of a hosting entry's app: `<slug>` for the `app` entry, so
|
|
361
|
+
* the move to a list finds the app that runs today, `<slug>-<key>` for any
|
|
362
|
+
* other.
|
|
363
|
+
*/
|
|
364
|
+
export function appNameFor(org, entry) {
|
|
365
|
+
return entry.key === APP_ENTRY_KEY ? org.slug : `${org.slug}-${entry.key}`;
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* #243: the entry's `env` values the last `coolify:app` apply pushed,
|
|
347
369
|
* recorded as one JSON object (env name to value; ids and a domain, never a
|
|
348
370
|
* secret). `planApp` plans the step again when {@link resolveAppEnv} differs
|
|
349
|
-
* from it, so a changed map reaches an app that is already live.
|
|
371
|
+
* from it, so a changed map reaches an app that is already live. The `.<key>`
|
|
372
|
+
* suffix is the entry key; a scope key holds no `:`, so `storeRecordedIds`
|
|
373
|
+
* still filters it out.
|
|
350
374
|
*/
|
|
351
|
-
export
|
|
375
|
+
export function appEnvAppliedScopeKey(entryKey) {
|
|
376
|
+
return `app_env_applied.${entryKey}`;
|
|
377
|
+
}
|
|
352
378
|
/**
|
|
353
379
|
* #239: set to '1' once an app has been restarted after its database URLs
|
|
354
380
|
* were pushed. Not a URL. Without it a crash between the push and the restart
|
|
355
381
|
* would leave a stale container that no plan notices.
|
|
356
382
|
*/
|
|
357
|
-
export
|
|
383
|
+
export function dbEnvAppliedScopeKey(entryKey) {
|
|
384
|
+
return `db_env_applied.${entryKey}`;
|
|
385
|
+
}
|
|
386
|
+
export function appSettingsOf(entry) {
|
|
387
|
+
return { folder: entry.folder, port: entry.port, autoDeploy: entry.autoDeploy };
|
|
388
|
+
}
|
|
389
|
+
/** What an app that predates the marker has: Coolify's own defaults, so it plans no change. */
|
|
390
|
+
export const DEFAULT_APP_SETTINGS = { folder: null, port: null, autoDeploy: true };
|
|
391
|
+
export function appSettingsAppliedScopeKey(entryKey) {
|
|
392
|
+
return `app_settings_applied.${entryKey}`;
|
|
393
|
+
}
|
|
394
|
+
/** The settings the app has: the marker, or the defaults when there is none. */
|
|
395
|
+
export function appliedAppSettings(recorded, entryKey) {
|
|
396
|
+
const marker = recorded[appSettingsAppliedScopeKey(entryKey)];
|
|
397
|
+
return marker ? JSON.parse(marker) : DEFAULT_APP_SETTINGS;
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* The names `coolifyApp` last put in the entry's Coolify `domains`, as a JSON
|
|
401
|
+
* array (`[]` for an internal entry). `planApp` plans the step again, with a
|
|
402
|
+
* redeploy, when {@link entryHostnames} differs from it.
|
|
403
|
+
*/
|
|
404
|
+
export function appDomainsAppliedScopeKey(entryKey) {
|
|
405
|
+
return `app_domains_applied.${entryKey}`;
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* The names the app has: the marker, or for an app that predates it the one
|
|
409
|
+
* name it was created with (the `app` entry's address, nothing for any other
|
|
410
|
+
* entry), so an app that runs today plans no change.
|
|
411
|
+
*/
|
|
412
|
+
export function appliedAppDomains(org, recorded, entryKey) {
|
|
413
|
+
const marker = recorded[appDomainsAppliedScopeKey(entryKey)];
|
|
414
|
+
if (marker)
|
|
415
|
+
return JSON.parse(marker);
|
|
416
|
+
return entryKey === APP_ENTRY_KEY ? [legacyAppHostname(org)] : [];
|
|
417
|
+
}
|
|
358
418
|
/**
|
|
359
|
-
* The `
|
|
360
|
-
*
|
|
361
|
-
*
|
|
419
|
+
* The entry's `env` values that have a value, in env-name order, and the env
|
|
420
|
+
* names whose source has none yet. Shared by `planApp` and the executor's
|
|
421
|
+
* `pushAppEnv` so both read the same recorded ids.
|
|
362
422
|
*/
|
|
363
|
-
export function resolveAppEnv(org, recorded) {
|
|
423
|
+
export function resolveAppEnv(org, entry, recorded) {
|
|
364
424
|
const sources = {
|
|
365
425
|
filesOrganisationId: recorded[`files:organisation:${org.id}`],
|
|
366
426
|
aiOrganisationId: recorded[`ai:organisation:${org.id}`],
|
|
@@ -368,12 +428,12 @@ export function resolveAppEnv(org, recorded) {
|
|
|
368
428
|
};
|
|
369
429
|
const values = {};
|
|
370
430
|
const unresolved = [];
|
|
371
|
-
for (const [name,
|
|
372
|
-
const value = sources[
|
|
431
|
+
for (const [name, declared] of Object.entries(entry.env).sort(([a], [b]) => (a < b ? -1 : 1))) {
|
|
432
|
+
const value = typeof declared === 'string' ? declared : sources[declared.from];
|
|
373
433
|
if (value)
|
|
374
434
|
values[name] = value;
|
|
375
|
-
else
|
|
376
|
-
unresolved.push([name,
|
|
435
|
+
else if (typeof declared !== 'string')
|
|
436
|
+
unresolved.push([name, declared.from]);
|
|
377
437
|
}
|
|
378
438
|
return { values, unresolved };
|
|
379
439
|
}
|
|
@@ -561,7 +621,7 @@ export function planPlatform(platform, live, options = {}) {
|
|
|
561
621
|
plannedSteps.push(archon.step);
|
|
562
622
|
// #257: after every service above, so a database any of them just
|
|
563
623
|
// recorded is already visible to `recordedIds` on this same plan.
|
|
564
|
-
const backups = planBackups(byKey, claimed, recordedIds, blocked.backupExecutorMissing ?? false);
|
|
624
|
+
const backups = planBackups(byKey, claimed, recordedIds, blocked.backupExecutorMissing ?? false, drifted.backupStorage ?? false, drifted.backupStorageBlocked);
|
|
565
625
|
plannedSteps.push(...backups.steps);
|
|
566
626
|
// #251: one line per service whose recorded applied ref no longer matches
|
|
567
627
|
// what the next apply would use (or, for zitadel, what is actually live).
|
|
@@ -763,15 +823,36 @@ export const PLATFORM_BACKUP_SERVICE_KEYS = [
|
|
|
763
823
|
* Once a step's own `coolify:backup:<service>` resource is live, it is
|
|
764
824
|
* claimed and never re-planned: like every other provision-only step here,
|
|
765
825
|
* there is no park branch for a backup already scheduled.
|
|
826
|
+
*
|
|
827
|
+
* #257 fix round 2: that "claimed, never re-planned" rule is exactly why a
|
|
828
|
+
* rotated R2 credential would otherwise never reach Coolify again once every
|
|
829
|
+
* service's own backup is live -- nothing left in this loop ever calls
|
|
830
|
+
* `ensurePlatformBackupStorage` (`platform-providers.ts`) again. `storageDrifted`
|
|
831
|
+
* (the host's own `driftedSteps`, read fresh each plan) is the signal that
|
|
832
|
+
* self-heal is needed; when it is true, this plans exactly one extra
|
|
833
|
+
* `coolify:backup-storage:platform` step, independent of every per-service
|
|
834
|
+
* step's own claimed status, so the self-heal runs again whether or not any
|
|
835
|
+
* service step is also being (re)planned this round. It is never itself
|
|
836
|
+
* "claimed" against a live resource -- there is nothing to read live for a
|
|
837
|
+
* storage push, only to react to -- so it simply stops being planned the
|
|
838
|
+
* moment `storageDrifted` goes false again.
|
|
839
|
+
*
|
|
840
|
+
* #257 fix round 4, point 4: `storageBlocked`, when set, means the host's own
|
|
841
|
+
* `driftedSteps` could not even tell whether the storage is drifted (a
|
|
842
|
+
* Coolify read failed) -- reported in `blocked` instead of planning the
|
|
843
|
+
* self-heal step, so a persistent failure is visible in the plan rather than
|
|
844
|
+
* only logged and retried silently forever.
|
|
766
845
|
*/
|
|
767
|
-
function planBackups(byKey, claimed, recordedIds, executorMissing) {
|
|
846
|
+
function planBackups(byKey, claimed, recordedIds, executorMissing, storageDrifted, storageBlocked) {
|
|
768
847
|
const steps = [];
|
|
769
848
|
const blocked = [];
|
|
849
|
+
let anyDatabase = false;
|
|
770
850
|
for (const service of PLATFORM_BACKUP_SERVICE_KEYS) {
|
|
771
851
|
const key = resourceKey('coolify', 'backup', service);
|
|
772
852
|
const live = byKey.get(key);
|
|
773
853
|
if (live) {
|
|
774
854
|
claimed.add(key);
|
|
855
|
+
anyDatabase = true;
|
|
775
856
|
continue;
|
|
776
857
|
}
|
|
777
858
|
// No database yet is not reported: unlike a Coolify reader or a backup
|
|
@@ -783,6 +864,7 @@ function planBackups(byKey, claimed, recordedIds, executorMissing) {
|
|
|
783
864
|
const databaseId = recordedIds[PLATFORM_BACKUP_DATABASE_SCOPE_KEYS[service]];
|
|
784
865
|
if (!databaseId)
|
|
785
866
|
continue;
|
|
867
|
+
anyDatabase = true;
|
|
786
868
|
if (executorMissing) {
|
|
787
869
|
blocked.push({
|
|
788
870
|
description: `platform:backup:${service} is blocked, not planned: the host built the platform executor without its backup dependencies`,
|
|
@@ -804,6 +886,33 @@ function planBackups(byKey, claimed, recordedIds, executorMissing) {
|
|
|
804
886
|
},
|
|
805
887
|
});
|
|
806
888
|
}
|
|
889
|
+
// #257 fix round 4, point 4: a blocked liveness check is reported instead
|
|
890
|
+
// of planning the self-heal step -- there is nothing to push until the
|
|
891
|
+
// read itself works again.
|
|
892
|
+
if (!executorMissing && storageBlocked && anyDatabase) {
|
|
893
|
+
blocked.push({ description: storageBlocked, key: 'backup-storage' });
|
|
894
|
+
}
|
|
895
|
+
else if (
|
|
896
|
+
// #257 fix round 2: same "nothing to report yet" gate every other branch
|
|
897
|
+
// above follows -- a platform with no database yet has no storage to
|
|
898
|
+
// self-heal either, whatever a stale `storageDrifted` reading says.
|
|
899
|
+
!executorMissing &&
|
|
900
|
+
storageDrifted &&
|
|
901
|
+
anyDatabase) {
|
|
902
|
+
steps.push({
|
|
903
|
+
action: {
|
|
904
|
+
system: 'coolify',
|
|
905
|
+
kind: 'backup-storage',
|
|
906
|
+
key: 'platform',
|
|
907
|
+
desired: {},
|
|
908
|
+
},
|
|
909
|
+
step: {
|
|
910
|
+
description: "self-heal the platform's R2 backup storage registered with Coolify (a rotated credential or a changed bucket/endpoint)",
|
|
911
|
+
destroys: false,
|
|
912
|
+
migrates: false,
|
|
913
|
+
},
|
|
914
|
+
});
|
|
915
|
+
}
|
|
807
916
|
return { steps, blocked };
|
|
808
917
|
}
|
|
809
918
|
/**
|
|
@@ -999,6 +1108,16 @@ function planAuth(platform, recordedIds, executorBlocked, executorMissing, authD
|
|
|
999
1108
|
blocked: `the platform's sign-in is blocked, not planned: the admin key of ZITADEL machine user ${undelivered} was minted but never delivered and cannot be shown again; delete that user in ZITADEL and remove the recorded key ${PLATFORM_AUTH_ADMIN_UNDELIVERED_SCOPE_KEY} to mint a new one`,
|
|
1000
1109
|
};
|
|
1001
1110
|
}
|
|
1111
|
+
// #283: an instance may exist whose owner token never reached the host.
|
|
1112
|
+
const instanceLost = recordedIds[PLATFORM_AUTH_INSTANCE_UNDELIVERED_SCOPE_KEY];
|
|
1113
|
+
if (instanceLost &&
|
|
1114
|
+
!recordedIds[PLATFORM_AUTH_INSTANCE_SCOPE_KEY] &&
|
|
1115
|
+
!recordedIds[PLATFORM_AUTH_ADMIN_USER_SCOPE_KEY]) {
|
|
1116
|
+
return {
|
|
1117
|
+
step: null,
|
|
1118
|
+
blocked: `the platform's sign-in is blocked, not planned: a ZITADEL instance (${instanceLost}) may exist without a delivered owner token; delete it with the system API (DELETE /system/v1/instances/{id}) or redeploy the ZITADEL database, then remove the recorded key ${PLATFORM_AUTH_INSTANCE_UNDELIVERED_SCOPE_KEY}`,
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1002
1121
|
// #211 round 6: the executor itself is unusable on this host -- nothing to
|
|
1003
1122
|
// plan, whatever the broker's recorded state says.
|
|
1004
1123
|
if (executorMissing)
|
|
@@ -5,6 +5,7 @@ import type { StepAction } from './diff.js';
|
|
|
5
5
|
import type { CloudflarePort, CoolifyPort, FoundryStepExecutorPorts, ZitadelPort } from './executor.js';
|
|
6
6
|
import type { LiveResource, PlatformCloudflareReader, PlatformCoolifyReader } from './live.js';
|
|
7
7
|
import type { PlatformStepExecutionContext, PlatformStepExecutor } from './plan-apply.js';
|
|
8
|
+
import { type BrokerDeployRecord } from './platform-diff.js';
|
|
8
9
|
import { type CallsSfuPlatformDeps } from './platform-sfu.js';
|
|
9
10
|
/**
|
|
10
11
|
* #168: everything {@link provisionPlatformAuth} needs beyond
|
|
@@ -206,6 +207,8 @@ export declare class FoundryPlatformStepExecutor implements PlatformStepExecutor
|
|
|
206
207
|
driftedSteps(platform: DeclaredPlatform, recordedIds?: Readonly<Record<string, string>>): Promise<{
|
|
207
208
|
archon?: boolean;
|
|
208
209
|
auth?: boolean;
|
|
210
|
+
backupStorage?: boolean;
|
|
211
|
+
backupStorageBlocked?: string;
|
|
209
212
|
}>;
|
|
210
213
|
execute(action: StepAction, context: PlatformStepExecutionContext): Promise<{
|
|
211
214
|
resourceId?: string;
|
|
@@ -223,6 +226,13 @@ export declare class FoundryPlatformStepExecutor implements PlatformStepExecutor
|
|
|
223
226
|
private executeSfu;
|
|
224
227
|
private coolifyJobs;
|
|
225
228
|
}
|
|
229
|
+
/**
|
|
230
|
+
* #283: the ref the new instance's IAM_OWNER machine token (a PAT) is
|
|
231
|
+
* delivered under, once. A host reads it back to build `zitadelForInstance`
|
|
232
|
+
* (see {@link provisionPlatformAuth}'s doc comment). Distinct from the admin
|
|
233
|
+
* key delivered under `zitadel:platform:admin`.
|
|
234
|
+
*/
|
|
235
|
+
export declare const PLATFORM_AUTH_INSTANCE_BEARER_REF = "zitadel:platform:instanceBearer";
|
|
226
236
|
/**
|
|
227
237
|
* The fixed description Stalwart's admin API key is minted under
|
|
228
238
|
* (`StalwartClient.createApiKey`'s own signature returns only the secret,
|
|
@@ -361,6 +371,40 @@ export declare const PLATFORM_ARCHON_ACCESS_APP_NAME = "foundry:platform:archon"
|
|
|
361
371
|
export declare const PLATFORM_ZITADEL_ORG_SCOPE_KEY = "zitadel_platform_org";
|
|
362
372
|
/** Archon's OIDC client id: public, not a secret, so a plain scope id. */
|
|
363
373
|
export declare const PLATFORM_ARCHON_CLIENT_ID_SCOPE_KEY = "zitadel_platform_archon_client_id";
|
|
374
|
+
/**
|
|
375
|
+
* One `AUTH_APPS` entry, read back. Only the fields this step itself reads or
|
|
376
|
+
* writes are named; every other field `wtfalch/auth/broker/src/apps.ts`'s
|
|
377
|
+
* `App` knows about (`from`, `productName`, `locale`, `registration`,
|
|
378
|
+
* `emailLink`) rides along unnamed through the index signature, so merging an
|
|
379
|
+
* entry (see `provisionPlatformArchon`) never has to know the broker's full
|
|
380
|
+
* shape to avoid dropping a field it does not itself set.
|
|
381
|
+
*/
|
|
382
|
+
type BrokerAppEntry = Record<string, unknown> & {
|
|
383
|
+
organizationId: string;
|
|
384
|
+
key: string;
|
|
385
|
+
name: string;
|
|
386
|
+
clientIds: string[];
|
|
387
|
+
origins: string[];
|
|
388
|
+
};
|
|
389
|
+
/**
|
|
390
|
+
* One `AUTH_APPS[index]` entry, checked the way
|
|
391
|
+
* `wtfalch/auth/broker/src/apps.ts`'s own `parseApps`/`parseAppFields`
|
|
392
|
+
* (https://github.com/wtfalch/auth/blob/main/broker/src/apps.ts) check it at
|
|
393
|
+
* boot, in the same order: a `key` of at least 32 characters and a non-empty
|
|
394
|
+
* `organizationId`; a `name` that passes the broker's own `isSafeText` rule
|
|
395
|
+
* (#218); a `productName`, when present, that is a string and also passes it
|
|
396
|
+
* (#218); a `from` that passes {@link isValidSenderFrom}; a `locale` of
|
|
397
|
+
* `'en'` or `'nb'` when present; non-blank `clientIds`; and `origins` each in
|
|
398
|
+
* {@link isCanonicalOrigin} form. #211 round 2: on a bad
|
|
399
|
+
* entry this throws naming the broker app id and the entry's index, never
|
|
400
|
+
* the value -- `key` may hold another organisation's secret, and even
|
|
401
|
+
* `organizationId`/`name` are not this step's to print in an error only an
|
|
402
|
+
* operator reads. `entry` may be anything at all (`null`, a string, an
|
|
403
|
+
* array): every check below is a `typeof`/`Array.isArray` guard first, so a
|
|
404
|
+
* malformed entry never reaches a property read on a non-object and throws a
|
|
405
|
+
* `TypeError` instead of this named error.
|
|
406
|
+
*/
|
|
407
|
+
export declare function validateBrokerAppEntry(entry: unknown, brokerAppId: string, index: number): BrokerAppEntry;
|
|
364
408
|
/**
|
|
365
409
|
* #172/#201: mints Archon's own AI management key -- the plain bearer key
|
|
366
410
|
* (`wtfk_...`) `AiOrganisationPort` (`executor.ts`) authenticates with,
|
|
@@ -493,6 +537,51 @@ export declare function createAiManagementKeyMinter(options: {
|
|
|
493
537
|
fetchImpl?: typeof fetch;
|
|
494
538
|
now?: () => number;
|
|
495
539
|
}): AiManagementKeyMinter;
|
|
540
|
+
/**
|
|
541
|
+
* #211: names of the envs the platform's sign-in broker (`wtfalch/auth`'s
|
|
542
|
+
* `broker/server.ts`) refuses to start without, that `provisionPlatformAuth`
|
|
543
|
+
* itself has no way to supply yet -- `ZITADEL_SERVICE_USER_TOKEN`/`_FILE`
|
|
544
|
+
* (#209 mints and pushes this once it lands) and `RESEND_API_TOKEN` (#210,
|
|
545
|
+
* blocked on `wtfalch/auth#138`). Read from the broker's own live env, not a
|
|
546
|
+
* recorded scope id: whichever of #209/#210 lands first (or a human, setting
|
|
547
|
+
* either directly on the app) is enough to clear that one input, in either
|
|
548
|
+
* order, with no coupling to this step's own execution.
|
|
549
|
+
*
|
|
550
|
+
* #211 round 3: `AUTH_APPS` is a third required input -- the broker's own
|
|
551
|
+
* `parseApps` refuses to start with no `AUTH_APPS` set or an empty array
|
|
552
|
+
* (`wtfalch/auth/broker/src/apps.ts`), and this check used to name only the
|
|
553
|
+
* other two, so a deploy could go ahead with a broker that still would not
|
|
554
|
+
* come up. Also treat `null`/`undefined` as missing, not only `''`: the
|
|
555
|
+
* `CoolifyPort.listEnvs` doc comment already warns a live value does not
|
|
556
|
+
* always match its declared `string` type.
|
|
557
|
+
*/
|
|
558
|
+
export declare function missingBrokerInputs(coolify: Pick<CoolifyPort, 'listEnvs'>, brokerAppId: string): Promise<string[]>;
|
|
559
|
+
/**
|
|
560
|
+
* #211 round 5: the one rule `provisionPlatformAuth` and
|
|
561
|
+
* `provisionPlatformArchon` both call to decide whether the broker needs a
|
|
562
|
+
* (re)deploy -- replacing the two ad hoc "was it ever ostensibly deployed"
|
|
563
|
+
* booleans each function used to keep, which is what let a recreated broker,
|
|
564
|
+
* and a crash between writing `AUTH_APPS` and deploying, both go unnoticed
|
|
565
|
+
* (the round 5 issue text's own diagnosis: "they share one cause"). `false`
|
|
566
|
+
* while any required input is still missing -- deploying a broker that
|
|
567
|
+
* cannot start is pointless regardless of what changed. Otherwise `true`
|
|
568
|
+
* when nothing has ever been recorded, when the broker that was deployed is
|
|
569
|
+
* a different app (recreated under a new id), or when the `AUTH_APPS` that
|
|
570
|
+
* was deployed is not the `AUTH_APPS` that is live/about-to-be-live now.
|
|
571
|
+
* `authAppsRaw` is that exact string -- read live by `provisionPlatformAuth`,
|
|
572
|
+
* which never writes `AUTH_APPS` itself, or supplied by
|
|
573
|
+
* `provisionPlatformArchon` as the string it already knows it just wrote (or
|
|
574
|
+
* left unchanged), so neither caller needs a second read to agree with
|
|
575
|
+
* itself about what is now true of the app.
|
|
576
|
+
*/
|
|
577
|
+
export declare function brokerDeployNeeded(missingInputs: readonly string[], brokerAppId: string, authAppsRaw: string | undefined, recordedDeploy: BrokerDeployRecord | null): boolean;
|
|
578
|
+
/**
|
|
579
|
+
* #211 round 5: the {@link BrokerDeployRecord} JSON a successful deploy
|
|
580
|
+
* writes under {@link PLATFORM_AUTH_BROKER_DEPLOYED_SCOPE_KEY} (via each
|
|
581
|
+
* caller's own `record()`), replacing the plain broker-id string round 2-4
|
|
582
|
+
* wrote there. Paired with {@link brokerDeployNeeded}, which reads it back.
|
|
583
|
+
*/
|
|
584
|
+
export declare function brokerDeployRecordValue(brokerAppId: string, authAppsRaw: string | undefined): string;
|
|
496
585
|
/**
|
|
497
586
|
* #171: everything {@link provisionPlatformFiles} needs beyond
|
|
498
587
|
* {@link PlatformStepExecutionContext}, bundled for the same reason as
|
|
@@ -600,10 +689,21 @@ export declare const INTEGRATIONS_MANAGEMENT_KEY_ID = "foundry-1";
|
|
|
600
689
|
* platform database's dump lands in. A person creates or confirms the
|
|
601
690
|
* bucket and these credentials by hand (the issue's own "Human steps"); this
|
|
602
691
|
* package only ever registers the bucket with Coolify and schedules the
|
|
603
|
-
* dump, never creates or reads the bucket itself.
|
|
692
|
+
* dump, never creates or reads the bucket itself. `raw` carries the S3
|
|
693
|
+
* storage update call, `PATCH /s3-storages/{uuid}` -- checked against the
|
|
694
|
+
* same live Coolify 4.3.8 instance `@wtfalch/providers`' own CLAUDE.md
|
|
695
|
+
* records as the last version this estate verified its `{uuid}` routes
|
|
696
|
+
* against (that package's own "Known traps": the bundled `openapi.yaml`
|
|
697
|
+
* omits every one of them, so `routes/api.php`-level comments are what to
|
|
698
|
+
* trust). `raw` also carries the schedule-repoint call (#257 fix round 3),
|
|
699
|
+
* `PATCH /databases/{uuid}/backups/{backup_uuid}`, documented in Coolify's
|
|
700
|
+
* public API reference ("Update database backup") as taking an
|
|
701
|
+
* `s3_storage_uuid` field. `CoolifyPort` has not wrapped either route with a
|
|
702
|
+
* typed method, so both go through the same `raw` escape hatch `coolifyApp`'s
|
|
703
|
+
* own stop/start calls use for a REST route the port has not wrapped yet.
|
|
604
704
|
*/
|
|
605
705
|
export interface PlatformBackupDeps {
|
|
606
|
-
coolify: Pick<CoolifyPort, 'listS3Storages' | 'createS3Storage' | 'listDatabaseBackups' | 'createDatabaseBackup'>;
|
|
706
|
+
coolify: Pick<CoolifyPort, 'listS3Storages' | 'createS3Storage' | 'listDatabaseBackups' | 'createDatabaseBackup' | 'raw'>;
|
|
607
707
|
r2: {
|
|
608
708
|
endpoint: string;
|
|
609
709
|
bucket: string;
|