@wtfalch/foundry 0.16.15 → 0.16.16
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/org.d.ts +28 -5
- package/dist/org.js +19 -0
- package/dist/plan/deploy-health.d.ts +136 -0
- package/dist/plan/deploy-health.js +153 -0
- package/dist/plan/diff.js +50 -9
- package/dist/plan/entry-files-key.d.ts +3 -1
- package/dist/plan/entry-files-key.js +7 -10
- package/dist/plan/executor.d.ts +52 -5
- package/dist/plan/executor.js +79 -6
- package/dist/plan/hostnames.d.ts +6 -0
- package/dist/plan/hostnames.js +13 -0
- package/dist/plan/index.d.ts +1 -1
- package/dist/plan/index.js +1 -1
- package/dist/plan/plan-apply.d.ts +17 -0
- package/dist/plan/plan-apply.js +48 -3
- package/dist/plan/platform-diff.d.ts +89 -9
- package/dist/plan/platform-diff.js +176 -13
- package/dist/plan/platform-org.d.ts +9 -0
- package/dist/plan/platform-providers.d.ts +38 -5
- package/dist/plan/platform-providers.js +520 -99
- package/package.json +2 -2
|
@@ -127,6 +127,17 @@ export const PLATFORM_ARCHON_KEY = 'platform:archon';
|
|
|
127
127
|
*/
|
|
128
128
|
export const PLATFORM_ARCHON_APPLIED_HOSTNAME_SCOPE_KEY = 'archon_applied_hostname';
|
|
129
129
|
export const PLATFORM_ARCHON_APPLIED_GIT_REF_SCOPE_KEY = 'archon_applied_git_ref';
|
|
130
|
+
/**
|
|
131
|
+
* #398: the integrations management key id Archon runs with, recorded by
|
|
132
|
+
* `provisionPlatformArchon` only after its deployment finished and its health
|
|
133
|
+
* check passed with that id in its env. `provisionPlatformIntegrations`'s
|
|
134
|
+
* rotation step reads this before dropping a previous key id from the
|
|
135
|
+
* runner's `MANAGEMENT_PUBLIC_KEYS`: the old key stays pinned until this
|
|
136
|
+
* matches the new id, so a request signed by an Archon container still
|
|
137
|
+
* running the old key is never refused. If Archon's deploy fails this stays
|
|
138
|
+
* unrecorded and the old key stays pinned.
|
|
139
|
+
*/
|
|
140
|
+
export const PLATFORM_ARCHON_INTEGRATIONS_KEY_SYNCED_SCOPE_KEY = 'archon_integrations_key_synced';
|
|
130
141
|
/**
|
|
131
142
|
* #211 round 4: the broker app id Archon last registered its entry with.
|
|
132
143
|
* `provisionPlatformArchon` reads the broker to register with from
|
|
@@ -138,6 +149,22 @@ export const PLATFORM_ARCHON_APPLIED_GIT_REF_SCOPE_KEY = 'archon_applied_git_ref
|
|
|
138
149
|
* changed hostname or `gitRef` does.
|
|
139
150
|
*/
|
|
140
151
|
export const PLATFORM_ARCHON_APPLIED_BROKER_SCOPE_KEY = 'archon_applied_broker_app';
|
|
152
|
+
/**
|
|
153
|
+
* The `AUTHZ_BOOTSTRAP_OPERATOR` the last Archon apply set (`none` when it set
|
|
154
|
+
* none). `planArchon` plans the step again when the owner's id recorded by
|
|
155
|
+
* `platform:auth` differs from it, so Archon picks the owner up once auth
|
|
156
|
+
* has recorded it.
|
|
157
|
+
*/
|
|
158
|
+
export const PLATFORM_ARCHON_APPLIED_OPERATOR_SCOPE_KEY = 'archon_applied_bootstrap_operator';
|
|
159
|
+
/**
|
|
160
|
+
* The owner's ZITADEL user id Archon takes as its first operator, from
|
|
161
|
+
* {@link PLATFORM_AUTH_OWNER_INVITE_SCOPE_KEY}. Undefined when none is
|
|
162
|
+
* recorded or the recorded value is `none` (no owner declared).
|
|
163
|
+
*/
|
|
164
|
+
export function archonBootstrapOperator(recorded) {
|
|
165
|
+
const id = recorded[PLATFORM_AUTH_OWNER_INVITE_SCOPE_KEY];
|
|
166
|
+
return id && id !== 'none' ? id : undefined;
|
|
167
|
+
}
|
|
141
168
|
/**
|
|
142
169
|
* #175: set to '1' before the step points the tunnel at `http_status:404`
|
|
143
170
|
* (Access app rebuild), cleared to '' once the real ingress is written back.
|
|
@@ -448,8 +475,14 @@ export function appliedAppDomains(org, recorded, entryKey) {
|
|
|
448
475
|
* The entry's `env` values that have a value, in env-name order, and the env
|
|
449
476
|
* names whose source has none yet. Shared by `planApp` and the executor's
|
|
450
477
|
* `pushAppEnv` so both read the same recorded ids.
|
|
478
|
+
*
|
|
479
|
+
* A secret source is never a value here. `secrets` names each one that can be
|
|
480
|
+
* filled (`available`), `unresolvedSecrets` each that cannot, and `marker`
|
|
481
|
+
* is `values` plus a `secret:<source>` label per available secret: the JSON
|
|
482
|
+
* `coolifyApp` records as applied, so a declared change replans and no secret
|
|
483
|
+
* is written into it.
|
|
451
484
|
*/
|
|
452
|
-
export function resolveAppEnv(org, entry, recorded) {
|
|
485
|
+
export function resolveAppEnv(org, entry, recorded, available = { platformMailKey: false, sharedSecret: false }) {
|
|
453
486
|
const sources = {
|
|
454
487
|
filesOrganisationId: recorded[`files:organisation:${org.id}`],
|
|
455
488
|
aiOrganisationId: recorded[`ai:organisation:${org.id}`],
|
|
@@ -457,14 +490,38 @@ export function resolveAppEnv(org, entry, recorded) {
|
|
|
457
490
|
};
|
|
458
491
|
const values = {};
|
|
459
492
|
const unresolved = [];
|
|
493
|
+
const secrets = [];
|
|
494
|
+
const unresolvedSecrets = [];
|
|
495
|
+
const marker = {};
|
|
460
496
|
for (const [name, declared] of Object.entries(entry.env).sort(([a], [b]) => (a < b ? -1 : 1))) {
|
|
497
|
+
if (typeof declared !== 'string' && declared.from === 'platformMailKey') {
|
|
498
|
+
if (available.platformMailKey) {
|
|
499
|
+
secrets.push({ name, source: 'platformMailKey' });
|
|
500
|
+
marker[name] = 'secret:platformMailKey';
|
|
501
|
+
}
|
|
502
|
+
else
|
|
503
|
+
unresolvedSecrets.push([name, 'platformMailKey']);
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
506
|
+
if (typeof declared !== 'string' && declared.from === 'sharedSecret') {
|
|
507
|
+
const label = `sharedSecret ${declared.name}`;
|
|
508
|
+
if (available.sharedSecret) {
|
|
509
|
+
secrets.push({ name, source: declared });
|
|
510
|
+
marker[name] = `secret:sharedSecret:${declared.name}`;
|
|
511
|
+
}
|
|
512
|
+
else
|
|
513
|
+
unresolvedSecrets.push([name, label]);
|
|
514
|
+
continue;
|
|
515
|
+
}
|
|
461
516
|
const value = typeof declared === 'string' ? declared : sources[declared.from];
|
|
462
|
-
if (value)
|
|
517
|
+
if (value) {
|
|
463
518
|
values[name] = value;
|
|
519
|
+
marker[name] = value;
|
|
520
|
+
}
|
|
464
521
|
else if (typeof declared !== 'string')
|
|
465
522
|
unresolved.push([name, declared.from]);
|
|
466
523
|
}
|
|
467
|
-
return { values, unresolved };
|
|
524
|
+
return { values, unresolved, secrets, unresolvedSecrets, marker };
|
|
468
525
|
}
|
|
469
526
|
/**
|
|
470
527
|
* #172: the resource key `planAi` plans under and `FoundryPlatformStepExecutor`
|
|
@@ -535,13 +592,6 @@ export const PLATFORM_AI_MIGRATE_BLOCKED_SCOPE_KEY = 'ai_platform_migrate_blocke
|
|
|
535
592
|
* that throws) is repaired by the next one.
|
|
536
593
|
*/
|
|
537
594
|
export const PLATFORM_AI_DEPLOY_PENDING_SCOPE_KEY = 'ai_platform_deploy_pending';
|
|
538
|
-
/**
|
|
539
|
-
* The Coolify deployment queued for the host (its `deployment_uuid`, or
|
|
540
|
-
* `untracked` when the deploy answered none), set when that deploy is queued
|
|
541
|
-
* and cleared once it finished. The mint waits for it, so a retry after a
|
|
542
|
-
* failed wait still waits instead of minting against the old container.
|
|
543
|
-
*/
|
|
544
|
-
export const PLATFORM_AI_RUNNER_DEPLOYMENT_SCOPE_KEY = 'ai_platform_runner_deployment';
|
|
545
595
|
/**
|
|
546
596
|
* #171: the resource key `planFiles` plans under and `FoundryPlatformStepExecutor`
|
|
547
597
|
* dispatches on, the {@link PLATFORM_AUTH_KEY} shape for the files host.
|
|
@@ -715,6 +765,17 @@ export function planPlatform(platform, live, options = {}) {
|
|
|
715
765
|
unmanaged.push({ description: ai.blocked, key: 'ai' });
|
|
716
766
|
if (archon.blocked)
|
|
717
767
|
unmanaged.push({ description: archon.blocked, key: 'archon' });
|
|
768
|
+
// #398: the old integrations key stays pinned until Archon holds the new
|
|
769
|
+
// id; name that when Archon is what is stopping it.
|
|
770
|
+
if (archon.blocked &&
|
|
771
|
+
recordedIds[PLATFORM_INTEGRATIONS_KEY_ROTATION_PREVIOUS_SCOPE_KEY] &&
|
|
772
|
+
recordedIds[PLATFORM_INTEGRATIONS_KEY_ID_SCOPE_KEY] !==
|
|
773
|
+
recordedIds[PLATFORM_ARCHON_INTEGRATIONS_KEY_SYNCED_SCOPE_KEY]) {
|
|
774
|
+
unmanaged.push({
|
|
775
|
+
description: `the previous integrations management key ${recordedIds[PLATFORM_INTEGRATIONS_KEY_ROTATION_PREVIOUS_SCOPE_KEY]} stays pinned on the runner until Archon is redeployed with the new key, and Archon is blocked (see above)`,
|
|
776
|
+
key: 'integrations',
|
|
777
|
+
});
|
|
778
|
+
}
|
|
718
779
|
unmanaged.push(...backups.blocked);
|
|
719
780
|
return { plannedSteps, unmanaged, versionChanges };
|
|
720
781
|
}
|
|
@@ -1344,6 +1405,52 @@ export const PLATFORM_INTEGRATIONS_KEY_SET_SCOPE_KEY = 'integrations_management_
|
|
|
1344
1405
|
/** The hostname and gitRef the last successful apply put live; a difference from the declared ones plans the step again. */
|
|
1345
1406
|
export const PLATFORM_INTEGRATIONS_APPLIED_HOSTNAME_SCOPE_KEY = 'integrations_applied_hostname';
|
|
1346
1407
|
export const PLATFORM_INTEGRATIONS_APPLIED_GITREF_SCOPE_KEY = 'integrations_applied_gitref';
|
|
1408
|
+
/**
|
|
1409
|
+
* #398: the key id currently pinned on the runner as the active entry of
|
|
1410
|
+
* `MANAGEMENT_PUBLIC_KEYS` -- `foundry-1` on the first mint, and whatever a
|
|
1411
|
+
* rotation minted next after that. `collectArchonServiceInputs` reads this
|
|
1412
|
+
* (falling back to the fixed `INTEGRATIONS_MANAGEMENT_KEY_ID` constant for a
|
|
1413
|
+
* platform applied before this key existed) for Archon's own
|
|
1414
|
+
* `INTEGRATIONS_MANAGEMENT_KEY_ID` env, so a rotated id reaches it without
|
|
1415
|
+
* Archon's step needing to know anything happened on the integrations side.
|
|
1416
|
+
*/
|
|
1417
|
+
export const PLATFORM_INTEGRATIONS_KEY_ID_SCOPE_KEY = 'integrations_management_key_id';
|
|
1418
|
+
/**
|
|
1419
|
+
* #398: the key id the runner was last deployed with in its
|
|
1420
|
+
* `MANAGEMENT_PUBLIC_KEYS`, recorded by `provisionPlatformIntegrations` only
|
|
1421
|
+
* after the runner's deployment finished. Archon is not moved to a new key id
|
|
1422
|
+
* until this equals the active id, so Archon never signs with a key the
|
|
1423
|
+
* running runner does not yet hold.
|
|
1424
|
+
*/
|
|
1425
|
+
export const PLATFORM_INTEGRATIONS_RUNNER_KEYRING_DEPLOYED_SCOPE_KEY = 'integrations_runner_keyring_deployed';
|
|
1426
|
+
/**
|
|
1427
|
+
* #398: the same two-key discipline as
|
|
1428
|
+
* {@link PLATFORM_INTEGRATIONS_KEY_UNDELIVERED_SCOPE_KEY}, for a rotation's
|
|
1429
|
+
* new pair: recorded the moment it is minted, before delivery, and cleared
|
|
1430
|
+
* by nothing -- {@link PLATFORM_INTEGRATIONS_KEY_ID_SCOPE_KEY} moving past it
|
|
1431
|
+
* is what later runs read as "delivered and pinned", the same way the
|
|
1432
|
+
* once-only mint's guard is a second key rather than removing the first.
|
|
1433
|
+
* An id here that {@link PLATFORM_INTEGRATIONS_KEY_ID_SCOPE_KEY} has not
|
|
1434
|
+
* caught up to means the pair may be lost or half-applied; the step reports
|
|
1435
|
+
* it and mints no further pair.
|
|
1436
|
+
*/
|
|
1437
|
+
export const PLATFORM_INTEGRATIONS_KEY_ROTATION_UNDELIVERED_SCOPE_KEY = 'integrations_management_key_rotation_undelivered';
|
|
1438
|
+
/**
|
|
1439
|
+
* #398: the previous key id, still pinned in `MANAGEMENT_PUBLIC_KEYS` beside
|
|
1440
|
+
* the new one, pending removal -- set the moment the new pair is pinned
|
|
1441
|
+
* beside it, and cleared (to `''`, the {@link PLATFORM_ARCHON_GATE_CLOSED_SCOPE_KEY}
|
|
1442
|
+
* convention for "was set, now isn't") once the previous id is actually
|
|
1443
|
+
* removed from the keyring. Its presence is the overlap window: both keys
|
|
1444
|
+
* verify a request while it holds.
|
|
1445
|
+
*/
|
|
1446
|
+
export const PLATFORM_INTEGRATIONS_KEY_ROTATION_PREVIOUS_SCOPE_KEY = 'integrations_management_key_rotation_previous';
|
|
1447
|
+
/**
|
|
1448
|
+
* #398: how a rotation is requested. A person records any non-empty value
|
|
1449
|
+
* under this scope key; `planIntegrations` then plans the step again and
|
|
1450
|
+
* `provisionPlatformIntegrations` mints the new pair, clearing the key (to
|
|
1451
|
+
* `''`) as soon as the mint is recorded, so one request is one rotation.
|
|
1452
|
+
*/
|
|
1453
|
+
export const PLATFORM_INTEGRATIONS_KEY_ROTATION_REQUESTED_SCOPE_KEY = 'integrations_management_key_rotation_requested';
|
|
1347
1454
|
/**
|
|
1348
1455
|
* #174: the integrations host (https://github.com/wtfalch/integrations) -- its
|
|
1349
1456
|
* migrate job, web app and queue worker, their shared database, and its
|
|
@@ -1360,6 +1467,13 @@ export const PLATFORM_INTEGRATIONS_APPLIED_GITREF_SCOPE_KEY = 'integrations_appl
|
|
|
1360
1467
|
* Blocked, not planned: an undelivered key, and an executor that cannot run
|
|
1361
1468
|
* the step (no integrations dependencies, or no `@wtfalch/keys` values for
|
|
1362
1469
|
* the host).
|
|
1470
|
+
*
|
|
1471
|
+
* #398: a rotation (see {@link PLATFORM_INTEGRATIONS_KEY_ROTATION_REQUESTED_SCOPE_KEY})
|
|
1472
|
+
* or one still pending removal of the previous key
|
|
1473
|
+
* ({@link PLATFORM_INTEGRATIONS_KEY_ROTATION_PREVIOUS_SCOPE_KEY}) plans the
|
|
1474
|
+
* step again even when `applied`. A rotation whose new pair is minted but
|
|
1475
|
+
* not confirmed delivered is blocked, never planned: the step would throw
|
|
1476
|
+
* on every apply.
|
|
1363
1477
|
*/
|
|
1364
1478
|
function planIntegrations(platform, recordedIds, executorBlocked) {
|
|
1365
1479
|
if (!platform.integrations.enabled)
|
|
@@ -1368,7 +1482,41 @@ function planIntegrations(platform, recordedIds, executorBlocked) {
|
|
|
1368
1482
|
const applied = Boolean(recordedIds[resourceKey('coolify', 'app', PLATFORM_INTEGRATIONS_KEY)]) &&
|
|
1369
1483
|
recordedIds[PLATFORM_INTEGRATIONS_APPLIED_HOSTNAME_SCOPE_KEY] === hostname &&
|
|
1370
1484
|
recordedIds[PLATFORM_INTEGRATIONS_APPLIED_GITREF_SCOPE_KEY] === gitRef;
|
|
1371
|
-
|
|
1485
|
+
const keyId = recordedIds[PLATFORM_INTEGRATIONS_KEY_ID_SCOPE_KEY];
|
|
1486
|
+
const previous = recordedIds[PLATFORM_INTEGRATIONS_KEY_ROTATION_PREVIOUS_SCOPE_KEY];
|
|
1487
|
+
const keySet = Boolean(recordedIds[PLATFORM_INTEGRATIONS_KEY_SET_SCOPE_KEY]);
|
|
1488
|
+
// #398: a rotation key minted but not confirmed delivered and pinned blocks
|
|
1489
|
+
// the step whatever else is recorded: the executor throws on it, and
|
|
1490
|
+
// Archon's env refuses to pair the held key with an older id.
|
|
1491
|
+
const rotationUndelivered = recordedIds[PLATFORM_INTEGRATIONS_KEY_ROTATION_UNDELIVERED_SCOPE_KEY];
|
|
1492
|
+
if (rotationUndelivered && rotationUndelivered !== keyId) {
|
|
1493
|
+
return {
|
|
1494
|
+
step: null,
|
|
1495
|
+
blocked: `the integrations host is blocked, not planned: rotation key ${rotationUndelivered} was minted but not confirmed delivered and pinned; check the held key and the runner's MANAGEMENT_PUBLIC_KEYS, then remove the recorded key ${PLATFORM_INTEGRATIONS_KEY_ROTATION_UNDELIVERED_SCOPE_KEY} and set ${PLATFORM_INTEGRATIONS_KEY_ROTATION_REQUESTED_SCOPE_KEY} again to mint a new one`,
|
|
1496
|
+
};
|
|
1497
|
+
}
|
|
1498
|
+
// A request counts only once the first key is minted and no earlier
|
|
1499
|
+
// rotation is waiting to drop its previous key.
|
|
1500
|
+
const requested = Boolean(recordedIds[PLATFORM_INTEGRATIONS_KEY_ROTATION_REQUESTED_SCOPE_KEY]) &&
|
|
1501
|
+
keySet &&
|
|
1502
|
+
!previous;
|
|
1503
|
+
const requestIgnored = Boolean(recordedIds[PLATFORM_INTEGRATIONS_KEY_ROTATION_REQUESTED_SCOPE_KEY]) && !requested;
|
|
1504
|
+
// The previous key is dropped once Archon holds the new id, or at once when
|
|
1505
|
+
// Archon is not enabled (nothing else holds the old key). While Archon has
|
|
1506
|
+
// not synced there is nothing for this step to do.
|
|
1507
|
+
// Mirrors the executor's drop guards: the previous id is not the active
|
|
1508
|
+
// one, and the last delivered mint is the active one.
|
|
1509
|
+
const dropReady = Boolean(previous) &&
|
|
1510
|
+
previous !== keyId &&
|
|
1511
|
+
rotationUndelivered === keyId &&
|
|
1512
|
+
(!platform.archon.enabled ||
|
|
1513
|
+
recordedIds[PLATFORM_ARCHON_INTEGRATIONS_KEY_SYNCED_SCOPE_KEY] === keyId);
|
|
1514
|
+
// The runner has not been deployed with the active key id yet (a deploy
|
|
1515
|
+
// failed after the key was pinned): plan the step to deploy it.
|
|
1516
|
+
const runnerStale = Boolean(keyId) &&
|
|
1517
|
+
recordedIds[PLATFORM_INTEGRATIONS_RUNNER_KEYRING_DEPLOYED_SCOPE_KEY] !== keyId;
|
|
1518
|
+
const rotating = requested || dropReady || runnerStale;
|
|
1519
|
+
if (applied && !rotating)
|
|
1372
1520
|
return { step: null, blocked: null };
|
|
1373
1521
|
const undelivered = recordedIds[PLATFORM_INTEGRATIONS_KEY_UNDELIVERED_SCOPE_KEY];
|
|
1374
1522
|
if (undelivered && !recordedIds[PLATFORM_INTEGRATIONS_KEY_SET_SCOPE_KEY]) {
|
|
@@ -1388,7 +1536,9 @@ function planIntegrations(platform, recordedIds, executorBlocked) {
|
|
|
1388
1536
|
desired: { hostname, gitRef },
|
|
1389
1537
|
},
|
|
1390
1538
|
step: {
|
|
1391
|
-
description: `provision the integrations host at ${hostname}
|
|
1539
|
+
description: `provision the integrations host at ${hostname}${requestIgnored
|
|
1540
|
+
? `; the recorded rotation request ${PLATFORM_INTEGRATIONS_KEY_ROTATION_REQUESTED_SCOPE_KEY} is ignored and cleared (no key minted yet, or the previous key ${previous ?? ''} is still pending removal); set it again afterwards`
|
|
1541
|
+
: ''}`,
|
|
1392
1542
|
destroys: false,
|
|
1393
1543
|
migrates: false,
|
|
1394
1544
|
},
|
|
@@ -1613,7 +1763,20 @@ function planArchon(platform, recordedIds, executorBlocked, authReady, drifted)
|
|
|
1613
1763
|
Boolean(recordedIds[PLATFORM_ARCHON_APPLIED_BROKER_SCOPE_KEY]) &&
|
|
1614
1764
|
recordedIds[PLATFORM_ARCHON_APPLIED_BROKER_SCOPE_KEY] ===
|
|
1615
1765
|
recordedIds[PLATFORM_AUTH_BROKER_SCOPE_KEY] &&
|
|
1616
|
-
|
|
1766
|
+
(recordedIds[PLATFORM_ARCHON_APPLIED_OPERATOR_SCOPE_KEY] ?? 'none') ===
|
|
1767
|
+
(archonBootstrapOperator(recordedIds) ?? 'none') &&
|
|
1768
|
+
!recordedIds[PLATFORM_ARCHON_GATE_CLOSED_SCOPE_KEY] &&
|
|
1769
|
+
// #398: a rotated integrations key id Archon's env was not yet written
|
|
1770
|
+
// with (see PLATFORM_ARCHON_INTEGRATIONS_KEY_SYNCED_SCOPE_KEY). Absent
|
|
1771
|
+
// before the first rotation on a platform applied before #398.
|
|
1772
|
+
// Only while integrations is enabled, and only once the runner is deployed
|
|
1773
|
+
// with that id (PLATFORM_INTEGRATIONS_RUNNER_KEYRING_DEPLOYED_SCOPE_KEY).
|
|
1774
|
+
(!platform.integrations.enabled ||
|
|
1775
|
+
!recordedIds[PLATFORM_INTEGRATIONS_KEY_ID_SCOPE_KEY] ||
|
|
1776
|
+
recordedIds[PLATFORM_INTEGRATIONS_RUNNER_KEYRING_DEPLOYED_SCOPE_KEY] !==
|
|
1777
|
+
recordedIds[PLATFORM_INTEGRATIONS_KEY_ID_SCOPE_KEY] ||
|
|
1778
|
+
recordedIds[PLATFORM_INTEGRATIONS_KEY_ID_SCOPE_KEY] ===
|
|
1779
|
+
recordedIds[PLATFORM_ARCHON_INTEGRATIONS_KEY_SYNCED_SCOPE_KEY]);
|
|
1617
1780
|
if (applied && !drifted)
|
|
1618
1781
|
return { step: null, blocked: null };
|
|
1619
1782
|
if (!platform.auth.enabled) {
|
|
@@ -68,6 +68,13 @@ export interface OrgPlatformReadiness {
|
|
|
68
68
|
ai: string | null;
|
|
69
69
|
jobs: string | null;
|
|
70
70
|
};
|
|
71
|
+
/**
|
|
72
|
+
* The platform's Lettermint key (`PLATFORM_AUTH_BROKER_MAIL_KEY_REF`) is held,
|
|
73
|
+
* so an entry env from `platformMailKey` can be filled. Absent reads as not held.
|
|
74
|
+
*/
|
|
75
|
+
mailKeyHeld?: boolean;
|
|
76
|
+
/** The host's held keys can `put`, so an entry env from `sharedSecret` can be filled. Absent reads as false. */
|
|
77
|
+
canHoldSecrets?: boolean;
|
|
71
78
|
/**
|
|
72
79
|
* True when the host wired no held-keys reader, so no key could be checked.
|
|
73
80
|
* A missing key is then reported as "no reader given" instead of "not held".
|
|
@@ -91,6 +98,8 @@ export declare const ORG_PLATFORM_KEY_REFS: {
|
|
|
91
98
|
/** The one method of Archon's held keys this package reads. A value is returned to the caller and never logged, recorded or put in a plan. */
|
|
92
99
|
export interface HeldKeyResolver {
|
|
93
100
|
resolve(ref: string): Promise<string | null | undefined>;
|
|
101
|
+
/** Holds a value under `ref`; only the org steps' shared secrets need it. */
|
|
102
|
+
put?(ref: string, value: string): Promise<void>;
|
|
94
103
|
}
|
|
95
104
|
/**
|
|
96
105
|
* Which of {@link ORG_PLATFORM_KEY_REFS} `heldKeys` can resolve right now. A
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CloudflareClient } from '@wtfalch/providers/cloudflare';
|
|
2
|
+
import type { DeclaredOrg } from '../org.js';
|
|
2
3
|
import type { DeclaredPlatform } from '../platform.js';
|
|
3
4
|
import type { DeclaredOrgStore, PlatformStore } from '../store.js';
|
|
4
5
|
import type { StepAction } from './diff.js';
|
|
@@ -243,6 +244,13 @@ export declare class FoundryPlatformStepExecutor implements PlatformStepExecutor
|
|
|
243
244
|
backupStorage?: boolean;
|
|
244
245
|
backupStorageBlocked?: string;
|
|
245
246
|
}>;
|
|
247
|
+
/**
|
|
248
|
+
* Gives the files runner a changed share-domain set and nothing else: its
|
|
249
|
+
* Coolify `domains`, `FILES_PUBLIC_HOSTS` and one redeploy of the runner.
|
|
250
|
+
* Returns the scope ids to record, or `null` when nothing changed
|
|
251
|
+
* (or the runner was never applied).
|
|
252
|
+
*/
|
|
253
|
+
refreshFilesShare(_platform: DeclaredPlatform, recordedIds: Readonly<Record<string, string>>): Promise<Record<string, string> | null>;
|
|
246
254
|
execute(action: StepAction, context: PlatformStepExecutionContext): Promise<{
|
|
247
255
|
resourceId?: string;
|
|
248
256
|
} | undefined>;
|
|
@@ -357,7 +365,7 @@ export declare function defaultStalwartForInstance(input: {
|
|
|
357
365
|
* own shape.
|
|
358
366
|
*/
|
|
359
367
|
export interface PlatformMailDeps {
|
|
360
|
-
coolify: Pick<CoolifyPort, 'findProjectByName' | 'createProject' | 'findOrCreateEnvironment' | 'listServers' | 'findApplicationByName' | 'getApplication' | 'createDockerImageApplication' | 'createDockerfileApplication' | 'raw' | 'listStorages' | 'createPersistentStorage' | 'bulkUpsertEnvs' | 'listEnvs' | 'updateApplication' | 'deploy'>;
|
|
368
|
+
coolify: Pick<CoolifyPort, 'findProjectByName' | 'createProject' | 'findOrCreateEnvironment' | 'listServers' | 'findApplicationByName' | 'getApplication' | 'createDockerImageApplication' | 'createDockerfileApplication' | 'raw' | 'listStorages' | 'createPersistentStorage' | 'bulkUpsertEnvs' | 'listEnvs' | 'updateApplication' | 'deploy' | 'deployAndWait'>;
|
|
361
369
|
cloudflare: Pick<CloudflarePort, 'findZoneByApex' | 'findTunnelByName' | 'createTunnel' | 'putTunnelConfig' | 'getTunnelToken' | 'findDnsRecord' | 'createDnsRecord' | 'updateDnsRecord'>;
|
|
362
370
|
/**
|
|
363
371
|
* Must target the platform's own ZITADEL instance, or `zitadelForInstance`
|
|
@@ -401,7 +409,7 @@ export interface PlatformMailRuntime {
|
|
|
401
409
|
* {@link PlatformStepExecutionContext}, bundled like {@link PlatformAuthDeps}.
|
|
402
410
|
*/
|
|
403
411
|
export interface PlatformArchonDeps {
|
|
404
|
-
coolify: Pick<CoolifyPort, 'findProjectByName' | 'createProject' | 'findOrCreateEnvironment' | 'listServers' | 'findApplicationByName' | 'getApplication' | 'requireGithubApp' | 'createGithubAppApplication' | 'createDockerfileApplication' | 'listDatabases' | 'createPostgresDatabase' | 'getDatabase' | 'raw' | 'bulkUpsertEnvs' | 'listEnvs' | 'updateApplication' | 'deploy'>;
|
|
412
|
+
coolify: Pick<CoolifyPort, 'findProjectByName' | 'createProject' | 'findOrCreateEnvironment' | 'listServers' | 'findApplicationByName' | 'getApplication' | 'requireGithubApp' | 'createGithubAppApplication' | 'createDockerfileApplication' | 'listDatabases' | 'createPostgresDatabase' | 'getDatabase' | 'raw' | 'bulkUpsertEnvs' | 'listEnvs' | 'updateApplication' | 'deploy' | 'deployAndWait'>;
|
|
405
413
|
cloudflare: Pick<CloudflarePort, 'findZoneByApex' | 'findTunnelByName' | 'createTunnel' | 'putTunnelConfig' | 'getTunnelToken' | 'findDnsRecord' | 'createDnsRecord' | 'updateDnsRecord' | 'listAccessApps' | 'createAccessApp' | 'listAccessPolicies' | 'createAccessPolicy' | 'deleteAccessApp'>;
|
|
406
414
|
/** Must target the platform's own ZITADEL instance, or `zitadelForInstance` must be set. */
|
|
407
415
|
zitadel: Pick<ZitadelPort, 'findOrCreateOrg' | 'findOrCreateProject' | 'createOidcApp' | 'listApps' | 'deleteApp'>;
|
|
@@ -447,6 +455,17 @@ export interface PlatformArchonDeps {
|
|
|
447
455
|
coolifyToken: string;
|
|
448
456
|
cloudflareParentToken: string;
|
|
449
457
|
};
|
|
458
|
+
/** Clock, sleep and fetch for the post-deploy health wait (#252); real ones by default. */
|
|
459
|
+
runtime?: PlatformArchonRuntime;
|
|
460
|
+
}
|
|
461
|
+
export interface PlatformArchonRuntime {
|
|
462
|
+
fetch?: typeof fetch;
|
|
463
|
+
sleep?: (ms: number) => Promise<void>;
|
|
464
|
+
now?: () => number;
|
|
465
|
+
/** How long to wait for `/api/health` to answer the new deploy (default 5 minutes). */
|
|
466
|
+
readyTimeoutMs?: number;
|
|
467
|
+
/** Pause between readiness polls (default 5 seconds). */
|
|
468
|
+
readyIntervalMs?: number;
|
|
450
469
|
}
|
|
451
470
|
/**
|
|
452
471
|
* The name of foundry's own Cloudflare Access app in front of Archon. Same
|
|
@@ -562,7 +581,7 @@ export declare class AiManagementKeyUntrustedError extends Error {
|
|
|
562
581
|
}
|
|
563
582
|
/** #172: everything {@link provisionPlatformAi} needs beyond {@link PlatformStepExecutionContext}. */
|
|
564
583
|
export interface PlatformAiDeps {
|
|
565
|
-
coolify: Pick<CoolifyPort, 'findProjectByName' | 'createProject' | 'findOrCreateEnvironment' | 'listServers' | 'findApplicationByName' | 'getApplication' | 'requireGithubApp' | 'createGithubAppApplication' | 'listDatabases' | 'createPostgresDatabase' | 'getDatabase' | 'bulkUpsertEnvs' | 'listEnvs' | 'updateApplication' | 'deploy' | 'raw'>;
|
|
584
|
+
coolify: Pick<CoolifyPort, 'findProjectByName' | 'createProject' | 'findOrCreateEnvironment' | 'listServers' | 'findApplicationByName' | 'getApplication' | 'requireGithubApp' | 'createGithubAppApplication' | 'listDatabases' | 'createPostgresDatabase' | 'getDatabase' | 'bulkUpsertEnvs' | 'listEnvs' | 'updateApplication' | 'deploy' | 'deployAndWait' | 'raw'>;
|
|
566
585
|
deliverSecret: FoundryStepExecutorPorts['deliverSecret'];
|
|
567
586
|
/**
|
|
568
587
|
* Reads back a secret this step delivered on an earlier apply -- needed
|
|
@@ -690,7 +709,7 @@ export declare function brokerDeployRecordValue(brokerAppId: string, authAppsRaw
|
|
|
690
709
|
* {@link PlatformAuthDeps}.
|
|
691
710
|
*/
|
|
692
711
|
export interface PlatformFilesDeps {
|
|
693
|
-
coolify: Pick<CoolifyPort, 'findProjectByName' | 'createProject' | 'findOrCreateEnvironment' | 'listServers' | 'requireGithubApp' | 'findApplicationByName' | 'getApplication' | 'createGithubAppApplication' | 'listDatabases' | 'createPostgresDatabase' | 'getDatabase' | 'bulkUpsertEnvs' | 'listEnvs' | 'updateApplication' | 'deploy' | 'raw'>;
|
|
712
|
+
coolify: Pick<CoolifyPort, 'findProjectByName' | 'createProject' | 'findOrCreateEnvironment' | 'listServers' | 'requireGithubApp' | 'findApplicationByName' | 'getApplication' | 'createGithubAppApplication' | 'listDatabases' | 'createPostgresDatabase' | 'getDatabase' | 'bulkUpsertEnvs' | 'listEnvs' | 'updateApplication' | 'deploy' | 'deployAndWait' | 'raw'>;
|
|
694
713
|
/**
|
|
695
714
|
* Must target the platform's own sign-in (the instance `platform:auth`
|
|
696
715
|
* built), or {@link zitadelForInstance} must be set.
|
|
@@ -799,7 +818,7 @@ export declare const FILES_INTROSPECTION_SECRET_REF = "zitadel:platform:files:in
|
|
|
799
818
|
* {@link FoundryPlatformStepExecutor} keeps compiling with it omitted.
|
|
800
819
|
*/
|
|
801
820
|
export interface PlatformIntegrationsDeps {
|
|
802
|
-
coolify: Pick<CoolifyPort, 'findProjectByName' | 'createProject' | 'findOrCreateEnvironment' | 'listServers' | 'requireGithubApp' | 'findApplicationByName' | 'getApplication' | 'createGithubAppApplication' | 'updateApplication' | 'listDatabases' | 'createPostgresDatabase' | 'getDatabase' | 'bulkUpsertEnvs' | 'listEnvs' | 'deploy' | 'raw'>;
|
|
821
|
+
coolify: Pick<CoolifyPort, 'findProjectByName' | 'createProject' | 'findOrCreateEnvironment' | 'listServers' | 'requireGithubApp' | 'findApplicationByName' | 'getApplication' | 'createGithubAppApplication' | 'updateApplication' | 'listDatabases' | 'createPostgresDatabase' | 'getDatabase' | 'bulkUpsertEnvs' | 'listEnvs' | 'deploy' | 'deployAndWait' | 'raw'>;
|
|
803
822
|
/** Hands the management private key to the host, once (`FoundryStepExecutorPorts.deliverSecret`'s contract). */
|
|
804
823
|
deliverSecret: FoundryStepExecutorPorts['deliverSecret'];
|
|
805
824
|
/**
|
|
@@ -898,4 +917,18 @@ export declare class ProvidersPlatformCoolifyReader implements PlatformCoolifyRe
|
|
|
898
917
|
constructor(client: Pick<CoolifyPort, 'getApplication' | 'getDatabase' | 'listDatabaseBackups'>, platformStore: Pick<PlatformStore, 'getServiceIds'>);
|
|
899
918
|
listResources(): Promise<LiveResource[]>;
|
|
900
919
|
}
|
|
920
|
+
/** Scope id (no `:`): the share domains the runner's Coolify `domains` was last set to, comma-joined. */
|
|
921
|
+
export declare const PLATFORM_FILES_SHARE_DOMAINS_SCOPE_KEY = "platform_files_share_domains";
|
|
922
|
+
/**
|
|
923
|
+
* The runner's `FILES_PUBLIC_HOSTS`: the host-supplied `publicHosts` first, in
|
|
924
|
+
* their given order, then the share domains sorted, with no host twice.
|
|
925
|
+
* Empty when there is neither.
|
|
926
|
+
*/
|
|
927
|
+
export declare function filesPublicHostsValue(publicHosts: string | undefined, shareDomains: readonly string[] | undefined): string;
|
|
928
|
+
/**
|
|
929
|
+
* The share domain of every org that has a domain and an entry declaring
|
|
930
|
+
* `uses.files.shareHost`: `share.<domain>`. The platform files step derives it
|
|
931
|
+
* from the declared orgs, for its apply and its drift check alike.
|
|
932
|
+
*/
|
|
933
|
+
export declare function filesShareDomains(orgs: readonly DeclaredOrg[]): string[];
|
|
901
934
|
export {};
|