@wtfalch/foundry 0.60.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/apps/cloudflare.d.ts +173 -0
- package/dist/apps/cloudflare.js +17 -0
- package/dist/apps/deploy.d.ts +116 -0
- package/dist/apps/deploy.js +791 -0
- package/dist/apps/github.d.ts +47 -0
- package/dist/apps/github.js +201 -0
- package/dist/apps/index.d.ts +9 -0
- package/dist/apps/index.js +9 -0
- package/dist/apps/migrations.d.ts +110 -0
- package/dist/apps/migrations.js +458 -0
- package/dist/audit/connector.d.ts +43 -0
- package/dist/audit/connector.js +22 -0
- package/dist/audit/evidence.d.ts +49 -0
- package/dist/audit/evidence.js +56 -0
- package/dist/audit/index.d.ts +9 -0
- package/dist/audit/index.js +9 -0
- package/dist/audit/migrations.d.ts +5 -0
- package/dist/audit/migrations.js +5 -0
- package/dist/audit/registry.d.ts +49 -0
- package/dist/audit/registry.js +44 -0
- package/dist/audit/seal.d.ts +49 -0
- package/dist/audit/seal.js +131 -0
- package/dist/audit/sealer-role.d.ts +33 -0
- package/dist/audit/sealer-role.js +56 -0
- package/dist/audit/signer.d.ts +40 -0
- package/dist/audit/signer.js +78 -0
- package/dist/audit/tick.d.ts +36 -0
- package/dist/audit/tick.js +87 -0
- package/dist/catalogue/copy-en.d.ts +1 -0
- package/dist/catalogue/copy-en.js +129 -0
- package/dist/catalogue/index.d.ts +9 -0
- package/dist/catalogue/index.js +21 -0
- package/dist/catalogue/integrations.d.ts +4 -0
- package/dist/catalogue/integrations.js +104 -0
- package/dist/catalogue/systems.d.ts +7 -0
- package/dist/catalogue/systems.js +31 -0
- package/dist/catalogue/types.d.ts +110 -0
- package/dist/catalogue/types.js +1 -0
- package/dist/entry-rules.d.ts +53 -0
- package/dist/entry-rules.js +71 -0
- package/dist/hosted-label.d.ts +13 -0
- package/dist/hosted-label.js +81 -0
- package/dist/index.d.ts +14 -5
- package/dist/index.js +7 -3
- package/dist/migrations/0062_hosting_github.sql +6 -0
- package/dist/migrations/0063_boule_apps.sql +7 -0
- package/dist/migrations/0064_requested_changes_boule_apps.sql +6 -0
- package/dist/migrations/0065_native_build_runs.sql +32 -0
- package/dist/migrations/0066_org_kosmos.sql +10 -0
- package/dist/modules/ai.d.ts +3 -0
- package/dist/modules/ai.js +64 -0
- package/dist/modules/assistants.d.ts +3 -0
- package/dist/modules/assistants.js +72 -0
- package/dist/modules/audit-ledger.d.ts +29 -0
- package/dist/modules/audit-ledger.js +49 -0
- package/dist/modules/audit.d.ts +7 -0
- package/dist/modules/audit.js +44 -0
- package/dist/modules/database.d.ts +23 -0
- package/dist/modules/database.js +189 -0
- package/dist/modules/email.d.ts +3 -0
- package/dist/modules/email.js +18 -0
- package/dist/modules/entry-parts.d.ts +23 -0
- package/dist/modules/entry-parts.js +63 -0
- package/dist/modules/files.d.ts +3 -0
- package/dist/modules/files.js +64 -0
- package/dist/modules/index.d.ts +74 -0
- package/dist/modules/index.js +774 -0
- package/dist/modules/jobs.d.ts +3 -0
- package/dist/modules/jobs.js +23 -0
- package/dist/modules/organisations.d.ts +3 -0
- package/dist/modules/organisations.js +20 -0
- package/dist/modules/outputs.d.ts +9 -0
- package/dist/modules/outputs.js +38 -0
- package/dist/modules/sign-in.d.ts +3 -0
- package/dist/modules/sign-in.js +197 -0
- package/dist/modules/signed-records.d.ts +3 -0
- package/dist/modules/signed-records.js +32 -0
- package/dist/modules/types.d.ts +194 -0
- package/dist/modules/types.js +1 -0
- package/dist/modules/workspaces.d.ts +3 -0
- package/dist/modules/workspaces.js +75 -0
- package/dist/native-build.d.ts +70 -0
- package/dist/native-build.js +92 -0
- package/dist/org.d.ts +184 -24
- package/dist/org.js +444 -49
- package/dist/plan/agora.d.ts +1 -1
- package/dist/plan/agora.js +4 -0
- package/dist/plan/apps-db-route.d.ts +90 -0
- package/dist/plan/apps-db-route.js +168 -0
- package/dist/plan/apps-zones.d.ts +56 -0
- package/dist/plan/apps-zones.js +118 -0
- package/dist/plan/archon-blue-green.js +11 -1
- package/dist/plan/blue-green-apply.d.ts +37 -1
- package/dist/plan/blue-green-apply.js +55 -15
- package/dist/plan/blue-green.d.ts +60 -8
- package/dist/plan/blue-green.js +115 -10
- package/dist/plan/boule.d.ts +23 -1
- package/dist/plan/boule.js +35 -2
- package/dist/plan/broker-entries.d.ts +28 -3
- package/dist/plan/broker-entries.js +74 -3
- package/dist/plan/broker-registrar.d.ts +168 -0
- package/dist/plan/broker-registrar.js +440 -0
- package/dist/plan/broker-workspaces-database.d.ts +48 -0
- package/dist/plan/broker-workspaces-database.js +87 -0
- package/dist/plan/deploy-health.js +9 -2
- package/dist/plan/desktop-callback.js +15 -7
- package/dist/plan/diff.d.ts +8 -2
- package/dist/plan/diff.js +552 -104
- package/dist/plan/edge-access.d.ts +6 -0
- package/dist/plan/edge-access.js +1 -0
- package/dist/plan/edge-dns.d.ts +6 -0
- package/dist/plan/edge-dns.js +9 -7
- package/dist/plan/entry-ai-key.d.ts +1 -1
- package/dist/plan/entry-ai-key.js +2 -2
- package/dist/plan/entry-audit.d.ts +58 -0
- package/dist/plan/entry-audit.js +122 -0
- package/dist/plan/entry-customer-grants.d.ts +75 -0
- package/dist/plan/entry-customer-grants.js +154 -0
- package/dist/plan/entry-edge-key.d.ts +26 -1
- package/dist/plan/entry-edge-key.js +42 -3
- package/dist/plan/entry-jobs.d.ts +64 -0
- package/dist/plan/entry-jobs.js +92 -0
- package/dist/plan/entry-repo-key-apply.d.ts +55 -0
- package/dist/plan/entry-repo-key-apply.js +185 -0
- package/dist/plan/entry-repo-key.d.ts +92 -0
- package/dist/plan/entry-repo-key.js +235 -0
- package/dist/plan/entry-workspaces.d.ts +55 -0
- package/dist/plan/entry-workspaces.js +90 -0
- package/dist/plan/executor.d.ts +231 -19
- package/dist/plan/executor.js +1648 -129
- package/dist/plan/gates.d.ts +1 -1
- package/dist/plan/gates.js +62 -9
- package/dist/plan/github-deploy-keys.d.ts +78 -0
- package/dist/plan/github-deploy-keys.js +305 -0
- package/dist/plan/github-source.d.ts +53 -0
- package/dist/plan/github-source.js +140 -0
- package/dist/plan/held-key-refs.d.ts +12 -0
- package/dist/plan/held-key-refs.js +12 -0
- package/dist/plan/hostnames.d.ts +9 -0
- package/dist/plan/hostnames.js +28 -1
- package/dist/plan/index.d.ts +9 -0
- package/dist/plan/index.js +6 -0
- package/dist/plan/jobs-scheduler-http.js +3 -0
- package/dist/plan/keys-registrar-http.d.ts +32 -0
- package/dist/plan/keys-registrar-http.js +105 -0
- package/dist/plan/keys-registrar.d.ts +67 -0
- package/dist/plan/keys-registrar.js +103 -0
- package/dist/plan/kosmos.d.ts +56 -0
- package/dist/plan/kosmos.js +84 -0
- package/dist/plan/live.d.ts +1 -1
- package/dist/plan/locked-hostnames.d.ts +2 -2
- package/dist/plan/locked-hostnames.js +7 -4
- package/dist/plan/login-host.js +11 -5
- package/dist/plan/machine-paths.d.ts +36 -0
- package/dist/plan/machine-paths.js +137 -0
- package/dist/plan/mcp.d.ts +1 -1
- package/dist/plan/mcp.js +49 -17
- package/dist/plan/org-apps-admin.d.ts +108 -0
- package/dist/plan/org-apps-admin.js +434 -0
- package/dist/plan/org-names.d.ts +3 -3
- package/dist/plan/org-names.js +15 -2
- package/dist/plan/org-postgres-migrate.js +1 -0
- package/dist/plan/own-diff.d.ts +23 -0
- package/dist/plan/own-diff.js +53 -0
- package/dist/plan/own-hostnames.d.ts +2 -0
- package/dist/plan/own-hostnames.js +1 -0
- package/dist/plan/own-jobs-route.d.ts +47 -0
- package/dist/plan/own-jobs-route.js +91 -0
- package/dist/plan/own-limits.d.ts +6 -0
- package/dist/plan/own-limits.js +1 -0
- package/dist/plan/own-services.d.ts +4 -0
- package/dist/plan/own-services.js +4 -0
- package/dist/plan/plan-apply.d.ts +39 -0
- package/dist/plan/plan-apply.js +163 -9
- package/dist/plan/platform-diff.d.ts +2 -2
- package/dist/plan/platform-diff.js +7 -2
- package/dist/plan/platform-org.d.ts +30 -0
- package/dist/plan/platform-org.js +12 -1
- package/dist/plan/platform-providers.d.ts +2 -0
- package/dist/plan/platform-providers.js +13 -2
- package/dist/plan/post-logout-uris.d.ts +1 -1
- package/dist/plan/post-logout-uris.js +12 -3
- package/dist/plan/private-pages.d.ts +26 -0
- package/dist/plan/private-pages.js +39 -0
- package/dist/plan/providers-readers.js +20 -1
- package/dist/plan/proxy-labels.d.ts +12 -1
- package/dist/plan/proxy-labels.js +32 -2
- package/dist/plan/release-host.d.ts +6 -2
- package/dist/plan/release-host.js +36 -8
- package/dist/plan/stage-env.d.ts +19 -1
- package/dist/plan/stage-env.js +25 -1
- package/dist/plan/stage-guards.d.ts +20 -0
- package/dist/plan/stage-guards.js +24 -0
- package/dist/plan/stage-isolation.d.ts +24 -0
- package/dist/plan/stage-isolation.js +29 -1
- package/dist/plan/worker-app-database.d.ts +46 -42
- package/dist/plan/worker-app-database.js +150 -153
- package/dist/plan/worker-app.d.ts +5 -65
- package/dist/plan/worker-app.js +28 -105
- package/dist/plan/worker.d.ts +2 -2
- package/dist/plan/worker.js +2 -1
- package/dist/plan/workspace-client.d.ts +25 -0
- package/dist/plan/workspace-client.js +48 -0
- package/dist/plan/workspace-sign-in.d.ts +55 -0
- package/dist/plan/workspace-sign-in.js +273 -0
- package/dist/plan/workspace-worker.d.ts +4 -0
- package/dist/plan/workspace-worker.js +13 -0
- package/dist/plan/workspace.d.ts +101 -0
- package/dist/plan/workspace.js +305 -0
- package/dist/release/decide.d.ts +30 -1
- package/dist/release/decide.js +73 -11
- package/dist/release/health.d.ts +8 -3
- package/dist/release/health.js +58 -5
- package/dist/release/migration-compatibility.d.ts +66 -0
- package/dist/release/migration-compatibility.js +340 -0
- package/dist/release/run.d.ts +2 -5
- package/dist/release/run.js +4 -0
- package/dist/release/state.d.ts +3 -1
- package/dist/release/state.js +6 -0
- package/dist/release/types.d.ts +20 -0
- package/dist/requested-changes.d.ts +11 -2
- package/dist/requested-changes.js +13 -1
- package/dist/service-labels.d.ts +4 -0
- package/dist/service-labels.js +11 -0
- package/dist/slug.d.ts +2 -0
- package/dist/slug.js +20 -7
- package/dist/store/postgres.js +25 -2
- package/dist/store/schema.d.ts +73 -1
- package/dist/store/schema.js +10 -0
- package/dist/worker-app-config.d.ts +1 -11
- package/dist/worker-app-config.js +4 -17
- package/dist/worker-app-migrations.d.ts +105 -0
- package/dist/worker-app-migrations.js +166 -0
- package/dist/worker-app-upload.d.ts +103 -0
- package/dist/worker-app-upload.js +128 -0
- package/package.json +19 -2
package/dist/plan/plan-apply.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { runAuditSealTick } from '../audit/tick.js';
|
|
2
3
|
import { VERSIONS, floatingTagBlocked } from '../bootstrap/versions.js';
|
|
3
4
|
import { sfuSlotPorts } from '../calls-sfu.js';
|
|
4
5
|
import { ORG_SERVICE_SCHEMAS, heldEnvRef, ownsServices, validateHosting } from '../org.js';
|
|
@@ -8,6 +9,7 @@ import { StaleLeaseError, } from '../store.js';
|
|
|
8
9
|
import { entryHasDatabase } from '../worker-app-config.js';
|
|
9
10
|
import { AdoptRefusedError, adoptCandidates, adoptedKeysUnreadable, adoptedScopeKey, parseAdoptSnapshot, planAdoptFrom, } from './adopt.js';
|
|
10
11
|
import { NO_AGORA_HOST_VALUES, readAgoraHost, } from './agora.js';
|
|
12
|
+
import { APPS_DB_ENTRY_KEY } from './apps-db-route.js';
|
|
11
13
|
import { NetworkMoveBackupMissingError, OrgPostgresMigrationBackupMissingError, SigninDbMoveBackupMissingError, UpgradeBackupMissingError, platformDatabasesForService, requireFreshBackupForOrgPostgresMigration, requireFreshBackupForSigninMove, requireFreshBackupsForMove, requireFreshBackupsForUpgrades, } from './backup-gate.js';
|
|
12
14
|
import { ARCHON_REPO, PRIMARY_SERVER_SCOPE_KEY, archonSubject, blueGreenSubjects, declaresBlueGreen, lbOrigin, } from './blue-green.js';
|
|
13
15
|
import { readLiveBuilds } from './build-record.js';
|
|
@@ -15,22 +17,27 @@ import { LEGACY_BACKUP_NAMES } from './company-backups.js';
|
|
|
15
17
|
import { unconfirmedUpgrades } from './confirm-upgrade.js';
|
|
16
18
|
import { AI_KEY_UNDELIVERED_SCOPE_KEY, JOBS_REGISTRATION_UNDELIVERED_SCOPE_KEY, computePlanDetail, toPlan, } from './diff.js';
|
|
17
19
|
import { entryAiUndeliveredScopeKey } from './entry-ai-key.js';
|
|
18
|
-
import { BOULE_EDGE_ENTRY_KEY, entryEdgeRetiredScopeKey, entryEdgeUndeliveredScopeKey, } from './entry-edge-key.js';
|
|
20
|
+
import { BOULE_EDGE_ENTRY_KEY, bouleEdgeKeyWanted, entryEdgeRetiredScopeKey, entryEdgeUndeliveredScopeKey, } from './entry-edge-key.js';
|
|
19
21
|
import { FILES_ROOT_UNDELIVERED_SCOPE_KEY, entryFilesUndeliveredScopeKey, } from './entry-files-key.js';
|
|
22
|
+
import { probeRepoKeys, repoKeyProbeTargets, repoKeysInPlay, } from './entry-repo-key.js';
|
|
23
|
+
import { PLATFORM_GITHUB_APP_REFS } from './held-key-refs.js';
|
|
24
|
+
import { KEYS_REGISTRAR_MINT_STARTED_SCOPE_KEY, KEYS_REGISTRAR_SCOPE_KEY, KEYS_REGISTRAR_UNDELIVERED_SCOPE_KEY, } from './keys-registrar.js';
|
|
20
25
|
import { readLivePlatformResources, readLiveResources, } from './live.js';
|
|
21
26
|
import { hostingNames } from './locked-hostnames.js';
|
|
22
27
|
import { agoraIssuerHostFor, loginHostServesAgora, readLoginHostDiscovery, } from './login-host.js';
|
|
23
28
|
import { OWN_MAIL_SMTP_INTERNAL_SCOPE_KEY, OWN_MAIL_SMTP_PORT_BASE, OWN_MAIL_SMTP_PORT_MAX, OWN_MAIL_SMTP_PORT_SCOPE_KEY, PLATFORM_MAIL_RELAY_ROUTES_SCOPE_KEY, isOwnMailSmtpPort, mailRelayFingerprint, } from './mail-relay.js';
|
|
29
|
+
import { ORG_APPS_ADMIN_UNDELIVERED_SCOPE_KEY } from './org-apps-admin.js';
|
|
24
30
|
import { findNameClashes, nameClashMessage } from './org-names.js';
|
|
25
31
|
import { InvalidMigrationConfirmationError, ORG_POSTGRES_MIGRATION_CONFIRMED_SCOPE_KEY, OrgPostgresMigrationNotConfirmedError, invalidMigrationConfirmation, migrateHomeOf, parseMigrationConfirmation, } from './org-postgres-migrate.js';
|
|
26
32
|
import { ownHostnames } from './own-hostnames.js';
|
|
27
33
|
import { OWN_FILES_R2_UNDELIVERED_SCOPE_KEY, orgTarget } from './own-services.js';
|
|
28
34
|
import { PLATFORM_ARCHON_KEY, PLATFORM_BACKUP_DATABASE_SCOPE_KEYS, PLATFORM_FILES_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_FILES_KEY, appStepKey, planPlatform as computePlatformPlanDetail, toPlatformPlan, } from './platform-diff.js';
|
|
29
|
-
import { DEFAULT_ORG_PLATFORM_READINESS, ORG_PLATFORM_KEY_REFS, agoraHostValuesForOwn, agoraHostValuesFromPlatform, dnsBlockedReason, heldOwnKeyRefs, heldPlatformKeyRefs, orgPlatformReadinessFor, orgPlatformReadinessFrom, ownerChatFrom, ownerFilesFrom, ownerSignInFrom, platformOwnerUserIdFrom, serviceGitRefsOf, } from './platform-org.js';
|
|
35
|
+
import { DEFAULT_ORG_PLATFORM_READINESS, ORG_PLATFORM_KEY_REFS, agoraHostValuesForOwn, agoraHostValuesFromPlatform, dnsBlockedReason, heldOwnKeyRefs, heldPlatformKeyRefs, orgPlatformReadinessFor, orgPlatformReadinessFrom, ownerChatFrom, ownerFilesFrom, ownerJobsPublicUrl, ownerSignInFrom, platformOwnerUserIdFrom, serviceGitRefsOf, } from './platform-org.js';
|
|
30
36
|
import { PLATFORM_AUTH_BROKER_MAIL_KEY_REF, PLATFORM_BACKUP_S3_STORAGE_SCOPE_KEY, } from './platform-providers.js';
|
|
31
|
-
import {
|
|
37
|
+
import { privatePageGateKey } from './private-pages.js';
|
|
38
|
+
import { CLOUDFLARE_TUNNEL_APPS_HOSTNAMES_SCOPE_KEY, CLOUDFLARE_TUNNEL_ENTRY_ROUTES_SCOPE_KEY, CLOUDFLARE_TUNNEL_OIKOS_HOSTNAMES_SCOPE_KEY, } from './proxy-labels.js';
|
|
32
39
|
import { runSubjectRelease } from './release-host.js';
|
|
33
|
-
import { remoteDatabaseFactsFrom } from './remote-database.js';
|
|
40
|
+
import { remoteDatabaseFactsFrom, remoteDatabaseKeys } from './remote-database.js';
|
|
34
41
|
import { liveWorkerApps, workerAppTraceScopeKeys } from './worker-app.js';
|
|
35
42
|
import { liveWorkers, workerTraceScopeKeys } from './worker.js';
|
|
36
43
|
/** #441: the lease scope that serializes every mutation of one company, and the one for the shared platform. */
|
|
@@ -129,7 +136,10 @@ const HOSTNAME_STEPS = new Set([
|
|
|
129
136
|
'coolify:bluegreen',
|
|
130
137
|
'coolify:bluegreen-lb',
|
|
131
138
|
'coolify:bluegreen-connector',
|
|
139
|
+
'coolify:workspace',
|
|
140
|
+
'coolify:promote',
|
|
132
141
|
'coolify:broker-entry',
|
|
142
|
+
'coolify:workspace-broker-entry',
|
|
133
143
|
'coolify:database',
|
|
134
144
|
'cloudflare:tunnel',
|
|
135
145
|
'cloudflare:access',
|
|
@@ -173,6 +183,7 @@ export class FoundryPlanApply {
|
|
|
173
183
|
edgeWorkers;
|
|
174
184
|
workerApps;
|
|
175
185
|
workerAppKeysUrl;
|
|
186
|
+
keysRegistrarUrl;
|
|
176
187
|
workerAppDatabase;
|
|
177
188
|
workerAppRegistryAccountId;
|
|
178
189
|
ownerInviteMail;
|
|
@@ -189,7 +200,11 @@ export class FoundryPlanApply {
|
|
|
189
200
|
platformDeps;
|
|
190
201
|
runner;
|
|
191
202
|
releaseStore;
|
|
203
|
+
githubDeployKeys;
|
|
204
|
+
auditSealing;
|
|
192
205
|
constructor(options) {
|
|
206
|
+
this.githubDeployKeys = options.githubDeployKeys;
|
|
207
|
+
this.auditSealing = options.auditSealing;
|
|
193
208
|
this.store = options.store;
|
|
194
209
|
this.readers = options.readers;
|
|
195
210
|
this.executor = options.executor;
|
|
@@ -211,6 +226,7 @@ export class FoundryPlanApply {
|
|
|
211
226
|
this.edgeWorkers = options.edgeWorkers ?? false;
|
|
212
227
|
this.workerApps = options.workerApps ?? false;
|
|
213
228
|
this.workerAppKeysUrl = options.workerAppKeysUrl;
|
|
229
|
+
this.keysRegistrarUrl = options.keysRegistrarUrl;
|
|
214
230
|
this.workerAppDatabase = options.workerAppDatabase ?? false;
|
|
215
231
|
this.workerAppRegistryAccountId = options.workerAppRegistryAccountId;
|
|
216
232
|
this.ownerInviteMail = options.ownerInviteMail ?? false;
|
|
@@ -796,6 +812,7 @@ export class FoundryPlanApply {
|
|
|
796
812
|
// The tunnel is gone, so what was written into it is too.
|
|
797
813
|
delete serviceIds[CLOUDFLARE_TUNNEL_ENTRY_ROUTES_SCOPE_KEY];
|
|
798
814
|
delete serviceIds[CLOUDFLARE_TUNNEL_OIKOS_HOSTNAMES_SCOPE_KEY];
|
|
815
|
+
delete serviceIds[CLOUDFLARE_TUNNEL_APPS_HOSTNAMES_SCOPE_KEY];
|
|
799
816
|
}
|
|
800
817
|
if (target.system === 'cloudflare' && target.kind === 'worker') {
|
|
801
818
|
// #318: its partial-deploy traces too, or a retry after a later
|
|
@@ -1249,6 +1266,37 @@ export class FoundryPlanApply {
|
|
|
1249
1266
|
}
|
|
1250
1267
|
return results;
|
|
1251
1268
|
}
|
|
1269
|
+
/**
|
|
1270
|
+
* The recurring audit sealing run (foundry#929): seals every ledger registered by an `audit:ledger` step with the
|
|
1271
|
+
* platform signer and has the timestamp authority stamp the new checkpoints. Foundry has no scheduler, so the host
|
|
1272
|
+
* calls this every `AUDIT_SEAL_EVERY_MINUTES` (15). One ledger failing never stops the others; each result and a
|
|
1273
|
+
* log line say what happened and why when nothing did. It writes nothing to the org's records.
|
|
1274
|
+
*/
|
|
1275
|
+
async runAuditSealTick() {
|
|
1276
|
+
const orgs = await this.store.list();
|
|
1277
|
+
const options = this.auditSealing;
|
|
1278
|
+
if (!options) {
|
|
1279
|
+
if (orgs.some((org) => org.hosting.enabled && org.hosting.entries.some((e) => e.uses?.audit === true))) {
|
|
1280
|
+
console.info('audit sealing: the host gave no auditSealing options, so no ledger is sealed');
|
|
1281
|
+
}
|
|
1282
|
+
return [];
|
|
1283
|
+
}
|
|
1284
|
+
const recorded = new Map();
|
|
1285
|
+
return runAuditSealTick({
|
|
1286
|
+
...options,
|
|
1287
|
+
orgs,
|
|
1288
|
+
now: this.now,
|
|
1289
|
+
recorded: async (org) => {
|
|
1290
|
+
const cached = recorded.get(org.id);
|
|
1291
|
+
if (cached)
|
|
1292
|
+
return cached;
|
|
1293
|
+
const ids = (await this.store.getAppliedState(org.id))?.serviceIds ?? {};
|
|
1294
|
+
recorded.set(org.id, ids);
|
|
1295
|
+
return ids;
|
|
1296
|
+
},
|
|
1297
|
+
allowed: (org) => this.authz.canApply(this.actor, org.id),
|
|
1298
|
+
});
|
|
1299
|
+
}
|
|
1252
1300
|
async runOrgReleaseTick(id) {
|
|
1253
1301
|
const empty = { orgId: id, steps: [], unmanaged: [] };
|
|
1254
1302
|
if (!(await this.authz.canApply(this.actor, id))) {
|
|
@@ -1317,6 +1365,12 @@ export class FoundryPlanApply {
|
|
|
1317
1365
|
const stageSinkDown = sinkId && recorded.stage_sink_applied && declaresBlueGreen(org) && this.executor.stageSinkDown
|
|
1318
1366
|
? await this.executor.stageSinkDown(sinkId).catch(() => false)
|
|
1319
1367
|
: false;
|
|
1368
|
+
const brokerWorkspacesDatabase = org.hosting.entries.some((e) => e.uses?.auth?.workspaces)
|
|
1369
|
+
? ((await this.executor.brokerHasWorkspacesDatabase?.(org, recorded)) ?? false)
|
|
1370
|
+
: false;
|
|
1371
|
+
const provisionWorkspacesDatabase = !brokerWorkspacesDatabase && org.hosting.entries.some((e) => e.uses?.auth?.workspaces)
|
|
1372
|
+
? ((await this.executor.canProvisionWorkspacesDatabase?.(org, recorded)) ?? false)
|
|
1373
|
+
: false;
|
|
1320
1374
|
const detail = computePlanDetail(org, live, applied?.serviceIds ?? {}, agoraHost, this.jobsScheduler, platformReadiness, {
|
|
1321
1375
|
loginHostBindings: this.loginHostBindings,
|
|
1322
1376
|
loginHostClientOrganizations: this.loginHostClientOrganizations,
|
|
@@ -1326,12 +1380,15 @@ export class FoundryPlanApply {
|
|
|
1326
1380
|
zitadelRedirectUris: this.zitadelRedirectUris,
|
|
1327
1381
|
zitadelMcp: this.zitadelMcp,
|
|
1328
1382
|
zitadelOrgMetadata: this.zitadelOrgMetadata,
|
|
1383
|
+
brokerWorkspacesDatabase,
|
|
1384
|
+
provisionWorkspacesDatabase,
|
|
1329
1385
|
broker: ownsServices(org) && this.hostOwnServices && this.executor.ownBrokerPort
|
|
1330
1386
|
? await this.executor.ownBrokerPort(org, recorded)
|
|
1331
1387
|
: this.broker,
|
|
1332
1388
|
edgeWorkers: this.edgeWorkers,
|
|
1333
1389
|
workerApps: this.workerApps,
|
|
1334
1390
|
workerAppKeysUrl: this.workerAppKeysUrl,
|
|
1391
|
+
keysRegistrarUrl: this.keysRegistrarUrl,
|
|
1335
1392
|
workerAppDatabase: this.workerAppDatabase,
|
|
1336
1393
|
workerAppRegistryAccountId: this.workerAppRegistryAccountId,
|
|
1337
1394
|
ownerInviteMail: this.ownerInviteMail,
|
|
@@ -1462,6 +1519,10 @@ export class FoundryPlanApply {
|
|
|
1462
1519
|
const base = {
|
|
1463
1520
|
...(await this.sharedPlatformReadiness()),
|
|
1464
1521
|
heldEnv: await this.heldEnvReadiness(org),
|
|
1522
|
+
...(org.hosting.github ? { githubAppKeys: await this.githubAppKeysReadiness() } : {}),
|
|
1523
|
+
...(repoKeysInPlay(org, orgRecordedIds)
|
|
1524
|
+
? { repoKeys: await this.repoKeysReadiness(org, orgRecordedIds) }
|
|
1525
|
+
: {}),
|
|
1465
1526
|
};
|
|
1466
1527
|
const platform = await this.platformDeps?.store.get();
|
|
1467
1528
|
// The auth release gates the shared-host organisation check and the cookie domain of every company.
|
|
@@ -1474,6 +1535,37 @@ export class FoundryPlanApply {
|
|
|
1474
1535
|
const readiness = orgPlatformReadinessFor(org, base, orgRecordedIds, held ? await heldOwnKeyRefs(held, org) : new Set());
|
|
1475
1536
|
return withRefs(readiness);
|
|
1476
1537
|
}
|
|
1538
|
+
/** F12: asks GitHub about the repo of each entry with a deploy key to make, check or remove. Never throws; a failure is a plain line. */
|
|
1539
|
+
async repoKeysReadiness(org, recorded) {
|
|
1540
|
+
const port = this.githubDeployKeys;
|
|
1541
|
+
if (!port)
|
|
1542
|
+
return { store: false, probes: {} };
|
|
1543
|
+
const probes = {};
|
|
1544
|
+
for (const { entryKey, target } of repoKeyProbeTargets(org, recorded)) {
|
|
1545
|
+
probes[entryKey] = await probeRepoKeys(port, target);
|
|
1546
|
+
}
|
|
1547
|
+
return { store: true, probes };
|
|
1548
|
+
}
|
|
1549
|
+
/** Which refs of the platform GitHub App hold no value now. A resolver that throws counts as not held; only refs are kept. */
|
|
1550
|
+
async githubAppKeysReadiness() {
|
|
1551
|
+
const heldKeys = this.platformDeps?.heldKeys;
|
|
1552
|
+
if (!heldKeys)
|
|
1553
|
+
return { store: false, missing: [] };
|
|
1554
|
+
const missing = [];
|
|
1555
|
+
for (const ref of Object.values(PLATFORM_GITHUB_APP_REFS)) {
|
|
1556
|
+
let held;
|
|
1557
|
+
try {
|
|
1558
|
+
held = await heldKeys.resolve(ref);
|
|
1559
|
+
}
|
|
1560
|
+
catch {
|
|
1561
|
+
// Not held: the plan reports the ref, and the error is dropped.
|
|
1562
|
+
held = null;
|
|
1563
|
+
}
|
|
1564
|
+
if (!held)
|
|
1565
|
+
missing.push(ref);
|
|
1566
|
+
}
|
|
1567
|
+
return { store: true, missing };
|
|
1568
|
+
}
|
|
1477
1569
|
/**
|
|
1478
1570
|
* The org's `heldSecret` envs: the refs that hold a value now, which of those differ from the live
|
|
1479
1571
|
* production env, which could not be read, and which fit no ref. A resolver that throws counts as
|
|
@@ -1614,8 +1706,10 @@ export class FoundryPlanApply {
|
|
|
1614
1706
|
const ownerRecorded = ownerOrg
|
|
1615
1707
|
? (await this.store.getAppliedState(ownerOrg.id))?.serviceIds
|
|
1616
1708
|
: {};
|
|
1709
|
+
const jobsPublicUrl = ownerJobsPublicUrl(ownerRecorded);
|
|
1617
1710
|
return {
|
|
1618
1711
|
...readiness,
|
|
1712
|
+
...(jobsPublicUrl ? { jobsPublicUrl } : {}),
|
|
1619
1713
|
platformOwnerUserId: platformOwnerUserIdFrom(ownerOrg, ownerRecorded ?? {}),
|
|
1620
1714
|
mailKeyHeld,
|
|
1621
1715
|
canHoldSecrets: typeof heldKeys?.put === 'function',
|
|
@@ -1888,6 +1982,9 @@ export function stepEntryKey(step, orgId, entries = []) {
|
|
|
1888
1982
|
}
|
|
1889
1983
|
const entryOwned = (system === 'coolify' && kind === 'app') ||
|
|
1890
1984
|
(system === 'cloudflare' && (kind === 'worker' || kind === 'worker-app' || kind === 'gate')) ||
|
|
1985
|
+
(system === 'jobs' && kind === 'project') ||
|
|
1986
|
+
(system === 'github' && kind === 'deploy-key') ||
|
|
1987
|
+
(system === 'audit' && kind === 'ledger') ||
|
|
1891
1988
|
(kind === 'key' && (system === 'ai' || system === 'edge' || system === 'files'));
|
|
1892
1989
|
if (!entryOwned)
|
|
1893
1990
|
return null;
|
|
@@ -1914,7 +2011,8 @@ function isOrgDatabasePark(step, orgId) {
|
|
|
1914
2011
|
}
|
|
1915
2012
|
/**
|
|
1916
2013
|
* The delete of one of the org's gates that no entry declares any more. The step names only the
|
|
1917
|
-
* gate, so the caller names the removed entry's gates (`entryConfirmation.gates`)
|
|
2014
|
+
* gate, so the caller names the removed entry's gates (`entryConfirmation.gates`), or the former paths of a
|
|
2015
|
+
* remaining entry's private pages (`entryConfirmation.privatePages`), and only those pass.
|
|
1918
2016
|
*/
|
|
1919
2017
|
function isUndeclaredGateDelete(step, orgId) {
|
|
1920
2018
|
const { system, kind, key, desired } = step.action;
|
|
@@ -1923,6 +2021,10 @@ function isUndeclaredGateDelete(step, orgId) {
|
|
|
1923
2021
|
key.startsWith(`${orgId}:`) &&
|
|
1924
2022
|
desired?.mode === 'delete');
|
|
1925
2023
|
}
|
|
2024
|
+
/** Whether `gateKey` is the gate of a page the confirmed entry had (live or workspace) and has no more. */
|
|
2025
|
+
function isFormerPrivatePageGate(gateKey, confirmation) {
|
|
2026
|
+
return (confirmation.privatePages ?? []).some((path) => ['live', 'workspace'].some((scope) => privatePageGateKey(scope, confirmation.entryKey, path) === gateKey));
|
|
2027
|
+
}
|
|
1926
2028
|
/** Whether `confirmation` passes every destroying or migrating step of `steps`: all belong to its entry, and it typed the key. */
|
|
1927
2029
|
export function entryConfirms(confirmation, steps, orgId, entries) {
|
|
1928
2030
|
if (!confirmation ||
|
|
@@ -1936,8 +2038,9 @@ export function entryConfirms(confirmation, steps, orgId, entries) {
|
|
|
1936
2038
|
(isOrgDatabasePark(planned, orgId) && !entries.some(entryHasDatabase)) ||
|
|
1937
2039
|
(isUndeclaredGateDelete(planned, orgId) &&
|
|
1938
2040
|
!planned.step.migrates &&
|
|
1939
|
-
(
|
|
1940
|
-
|
|
2041
|
+
(entries.some((e) => e.key === confirmation.entryKey)
|
|
2042
|
+
? isFormerPrivatePageGate(planned.action.key.slice(orgId.length + 1), confirmation)
|
|
2043
|
+
: (confirmation.gates ?? []).includes(planned.action.key.slice(orgId.length + 1)))));
|
|
1941
2044
|
}
|
|
1942
2045
|
/** `AppliedState.serviceIds`' two flat ZITADEL scope keys (`executor.ts`'s `ensureZitadelProject`/`zitadelIssuer`) -- no `:`, unlike a resource key. */
|
|
1943
2046
|
const ZITADEL_PROJECT_SCOPE_KEY = 'zitadel_project';
|
|
@@ -1987,9 +2090,13 @@ function destroyTargets(serviceIds, orgId) {
|
|
|
1987
2090
|
'cloudflare:worker-app:',
|
|
1988
2091
|
'coolify:database:',
|
|
1989
2092
|
'coolify:app:',
|
|
2093
|
+
// The company's GitHub source, after its apps; the shared private key it uses is left alone.
|
|
2094
|
+
'coolify:github-source:',
|
|
1990
2095
|
// An own files host's storage key: the token id is the step's recorded resource id.
|
|
1991
2096
|
'coolify:own-files-r2:',
|
|
1992
2097
|
'zitadel:customers:',
|
|
2098
|
+
// The workspaces' test customers organisation.
|
|
2099
|
+
'zitadel:customers-test:',
|
|
1993
2100
|
// F6a: an entry's ai key, `<orgId>:<entry>`; the org's own key has no `:` and is left alone.
|
|
1994
2101
|
'ai:key:',
|
|
1995
2102
|
// F6b: an entry's files key, `<orgId>:<entry>`; the org's own key has no `:` and is left alone.
|
|
@@ -2083,6 +2190,21 @@ function destroyTargets(serviceIds, orgId) {
|
|
|
2083
2190
|
continue;
|
|
2084
2191
|
targets.push({ system: 'edge', kind: 'key', key: entryKey, id });
|
|
2085
2192
|
}
|
|
2193
|
+
// F7: the signed-records registrar key, live or minted and never delivered, is revoked with the org.
|
|
2194
|
+
for (const [what, scopeKey] of [
|
|
2195
|
+
['live', KEYS_REGISTRAR_SCOPE_KEY],
|
|
2196
|
+
['undelivered', KEYS_REGISTRAR_UNDELIVERED_SCOPE_KEY],
|
|
2197
|
+
]) {
|
|
2198
|
+
const id = serviceIds[scopeKey];
|
|
2199
|
+
if (id)
|
|
2200
|
+
targets.push({ system: 'coolify', kind: 'keys-registrar', key: what, id });
|
|
2201
|
+
}
|
|
2202
|
+
// A mint whose answer was lost may have left a key no id points at: the company's registrar keys all go.
|
|
2203
|
+
if (serviceIds[KEYS_REGISTRAR_SCOPE_KEY] ||
|
|
2204
|
+
serviceIds[KEYS_REGISTRAR_UNDELIVERED_SCOPE_KEY] ||
|
|
2205
|
+
serviceIds[KEYS_REGISTRAR_MINT_STARTED_SCOPE_KEY]) {
|
|
2206
|
+
targets.push({ system: 'coolify', kind: 'keys-registrar', key: 'all', id: orgId });
|
|
2207
|
+
}
|
|
2086
2208
|
// A storage key minted but never delivered has no main id; its token is deleted too.
|
|
2087
2209
|
const r2Undelivered = serviceIds[OWN_FILES_R2_UNDELIVERED_SCOPE_KEY];
|
|
2088
2210
|
if (r2Undelivered) {
|
|
@@ -2198,12 +2320,15 @@ const STEP_FIELDS = {
|
|
|
2198
2320
|
'zitadel:issuer': 'issuer.usesDomain',
|
|
2199
2321
|
'zitadel:owner': 'owners',
|
|
2200
2322
|
'zitadel:customers': 'hosting.entries.uses.auth',
|
|
2323
|
+
'zitadel:customers-test': 'hosting.entries.workspace',
|
|
2201
2324
|
'zitadel:id-token-userinfo': 'hosting.entries.uses.auth',
|
|
2202
2325
|
'zitadel:desktop-callback': 'hosting.entries.uses.auth',
|
|
2203
2326
|
'zitadel:mcp-project': 'hosting.entries.uses.auth.mcp',
|
|
2204
2327
|
'zitadel:mcp-access': 'hosting.entries.uses.auth.mcp',
|
|
2205
2328
|
'zitadel:access-token-lifetime': 'hosting.entries.uses.auth.mcp',
|
|
2206
2329
|
'coolify:broker-entry': 'hosting.entries.uses.auth',
|
|
2330
|
+
'coolify:workspace-broker-entry': 'hosting.entries.workspace',
|
|
2331
|
+
'coolify:workspaces-database': 'hosting.entries.uses.auth.workspaces',
|
|
2207
2332
|
'coolify:mail': 'mail',
|
|
2208
2333
|
'coolify:chat': 'chat.usesDomain',
|
|
2209
2334
|
'coolify:own-auth': 'services.own',
|
|
@@ -2212,6 +2337,7 @@ const STEP_FIELDS = {
|
|
|
2212
2337
|
'coolify:own-files': 'services.own',
|
|
2213
2338
|
'coolify:own-files-r2': 'services.own',
|
|
2214
2339
|
'coolify:own-jobs': 'services.own',
|
|
2340
|
+
'coolify:own-jobs-route': 'services.own',
|
|
2215
2341
|
'coolify:own-mail': 'services.own',
|
|
2216
2342
|
'coolify:own-sfu': 'services.own',
|
|
2217
2343
|
'coolify:calls': 'calls.enabled',
|
|
@@ -2221,9 +2347,12 @@ const STEP_FIELDS = {
|
|
|
2221
2347
|
'ai:key': 'ai',
|
|
2222
2348
|
'cms:site': 'site',
|
|
2223
2349
|
'coolify:app': 'hosting.enabled',
|
|
2350
|
+
'coolify:github-source': 'hosting.github',
|
|
2224
2351
|
'coolify:bluegreen': 'hosting.entries.blueGreen',
|
|
2225
2352
|
'coolify:bluegreen-lb': 'hosting.entries.blueGreen',
|
|
2226
2353
|
'coolify:bluegreen-connector': 'hosting.entries.blueGreen',
|
|
2354
|
+
'coolify:workspace': 'hosting.entries.workspace',
|
|
2355
|
+
'coolify:promote': 'hosting.entries.liveCommit',
|
|
2227
2356
|
'coolify:database': 'hosting.database',
|
|
2228
2357
|
'cloudflare:tunnel': 'hosting.tunnel',
|
|
2229
2358
|
'cloudflare:zone': 'hosting.usesDomain',
|
|
@@ -2232,6 +2361,10 @@ const STEP_FIELDS = {
|
|
|
2232
2361
|
'cloudflare:ratelimit': 'hosting.usesDomain',
|
|
2233
2362
|
'cloudflare:worker': 'hosting.entries.worker',
|
|
2234
2363
|
'cloudflare:worker-app': 'hosting.entries.workerApp',
|
|
2364
|
+
// F4 (#925): both hang off the company's Apps switch.
|
|
2365
|
+
'coolify:org-apps-admin': 'boule.apps',
|
|
2366
|
+
'cloudflare:apps-db-route': 'boule.apps',
|
|
2367
|
+
'cloudflare:apps-zones': 'boule.apps',
|
|
2235
2368
|
};
|
|
2236
2369
|
function stepField(action) {
|
|
2237
2370
|
if (action.system === 'coolify' &&
|
|
@@ -2249,6 +2382,15 @@ function stepField(action) {
|
|
|
2249
2382
|
// An entry's edge key (F7): always `<orgId>:<entry>`, edge has no org-root key.
|
|
2250
2383
|
if (action.system === 'edge' && action.kind === 'key')
|
|
2251
2384
|
return 'hosting.entries.uses.edge';
|
|
2385
|
+
// An entry's jobs project: always `<orgId>:<entry>`.
|
|
2386
|
+
if (action.system === 'jobs' && action.kind === 'project')
|
|
2387
|
+
return 'hosting.entries.uses.jobs';
|
|
2388
|
+
// An entry's audit ledger registration: always `<orgId>:<entry>`.
|
|
2389
|
+
if (action.system === 'audit' && action.kind === 'ledger')
|
|
2390
|
+
return 'hosting.entries.uses.audit';
|
|
2391
|
+
// An entry's deploy key for its own repo (F12): always `<orgId>:<entry>`.
|
|
2392
|
+
if (action.system === 'github' && action.kind === 'deploy-key')
|
|
2393
|
+
return 'hosting.entries.uses.repo';
|
|
2252
2394
|
// An org declares several issuer apps, each keyed by its name (#71).
|
|
2253
2395
|
if (action.system === 'zitadel' && action.kind === 'app') {
|
|
2254
2396
|
return `hosting.issuerApps.${action.key}`;
|
|
@@ -2257,6 +2399,10 @@ function stepField(action) {
|
|
|
2257
2399
|
if (action.system === 'cloudflare' && action.kind === 'access' && action.desired.ownedBypass) {
|
|
2258
2400
|
return 'hosting.entries.gates';
|
|
2259
2401
|
}
|
|
2402
|
+
// F11: a bypass for a machine path of a gated entry.
|
|
2403
|
+
if (action.system === 'cloudflare' && action.kind === 'access' && action.desired.machineBypass) {
|
|
2404
|
+
return 'hosting.entries.uses';
|
|
2405
|
+
}
|
|
2260
2406
|
return STEP_FIELDS[`${action.system}:${action.kind}`] ?? `${action.system}.${action.kind}`;
|
|
2261
2407
|
}
|
|
2262
2408
|
/**
|
|
@@ -2312,10 +2458,18 @@ function failedFields(org, detail, completed, serviceIds) {
|
|
|
2312
2458
|
failed.add('hosting.entries.uses.edge');
|
|
2313
2459
|
}
|
|
2314
2460
|
}
|
|
2315
|
-
// F3: Boule's own key, wanted whenever the org has a domain.
|
|
2316
|
-
if (org
|
|
2461
|
+
// F3: Boule's own key, wanted whenever the org has a domain or its Boule runs apps.
|
|
2462
|
+
if (bouleEdgeKeyWanted(org) && serviceIds[entryEdgeUndeliveredScopeKey(BOULE_EDGE_ENTRY_KEY)]) {
|
|
2317
2463
|
failed.add('hosting.entries.uses.edge');
|
|
2318
2464
|
}
|
|
2465
|
+
// F7: a registrar key minted and never delivered stays unresolved until a later apply revokes it.
|
|
2466
|
+
if (serviceIds[KEYS_REGISTRAR_UNDELIVERED_SCOPE_KEY])
|
|
2467
|
+
failed.add('boule.apps');
|
|
2468
|
+
// F4 (#925): the same for an apps admin role or a route service token minted and never delivered.
|
|
2469
|
+
if (serviceIds[ORG_APPS_ADMIN_UNDELIVERED_SCOPE_KEY] ||
|
|
2470
|
+
serviceIds[remoteDatabaseKeys(APPS_DB_ENTRY_KEY).undelivered]) {
|
|
2471
|
+
failed.add('boule.apps');
|
|
2472
|
+
}
|
|
2319
2473
|
// The same for a jobs registration whose credentials never reached the host.
|
|
2320
2474
|
if (serviceIds[JOBS_REGISTRATION_UNDELIVERED_SCOPE_KEY] &&
|
|
2321
2475
|
!serviceIds[`jobs:registration:${org.id}`]) {
|
|
@@ -1416,8 +1416,8 @@ export declare const JOBS_SCHEDULER_KEY = "jobs";
|
|
|
1416
1416
|
/**
|
|
1417
1417
|
* The scheduler's address on the Coolify network (`http://<custom_internal_name>:8080`),
|
|
1418
1418
|
* the value the org steps' jobs port and Archon's `JOBS_URL` are read from.
|
|
1419
|
-
* Not a secret.
|
|
1420
|
-
*
|
|
1419
|
+
* Not a secret. Only containers on that network reach it; an entry's app uses
|
|
1420
|
+
* the owner company's public origin `JOBS_PUBLIC_URL_SCOPE_KEY` (`own-jobs-route.ts`) instead.
|
|
1421
1421
|
*/
|
|
1422
1422
|
export declare const JOBS_INTERNAL_URL_SCOPE_KEY = "jobs_internal_url";
|
|
1423
1423
|
/**
|
|
@@ -899,6 +899,11 @@ export function resolveAppEnv(org, entry, recorded, available = { sharedSecret:
|
|
|
899
899
|
else if (typeof declared !== 'string')
|
|
900
900
|
unresolved.push([name, declared.from]);
|
|
901
901
|
}
|
|
902
|
+
// F13: the entry's customer-grants credential is held and written by the app step. The marker names it,
|
|
903
|
+
// never its value, so adding or removing the flag replans the step.
|
|
904
|
+
if (entry.uses?.customerGrants && available.sharedSecret) {
|
|
905
|
+
marker.CUSTOMER_GRANTS_CREDENTIAL = 'secret:customerGrants';
|
|
906
|
+
}
|
|
902
907
|
return { values, unresolved, secrets, unresolvedSecrets, heldSecrets, unresolvedHeld, marker };
|
|
903
908
|
}
|
|
904
909
|
/**
|
|
@@ -1720,8 +1725,8 @@ export const JOBS_SCHEDULER_KEY = 'jobs';
|
|
|
1720
1725
|
/**
|
|
1721
1726
|
* The scheduler's address on the Coolify network (`http://<custom_internal_name>:8080`),
|
|
1722
1727
|
* the value the org steps' jobs port and Archon's `JOBS_URL` are read from.
|
|
1723
|
-
* Not a secret.
|
|
1724
|
-
*
|
|
1728
|
+
* Not a secret. Only containers on that network reach it; an entry's app uses
|
|
1729
|
+
* the owner company's public origin `JOBS_PUBLIC_URL_SCOPE_KEY` (`own-jobs-route.ts`) instead.
|
|
1725
1730
|
*/
|
|
1726
1731
|
export const JOBS_INTERNAL_URL_SCOPE_KEY = 'jobs_internal_url';
|
|
1727
1732
|
/**
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type DeclaredOrg, type VersionPin } from '../org.js';
|
|
2
2
|
import type { DeclaredPlatform } from '../platform.js';
|
|
3
3
|
import { type AgoraHostValues } from './agora.js';
|
|
4
|
+
import type { RepoKeyProbe } from './entry-repo-key.js';
|
|
4
5
|
/**
|
|
5
6
|
* #443: the no-domain company mail apex -- `<slug>.oikos.host`, the same
|
|
6
7
|
* fixed suffix `agora.ts`'s `agoraHostnameFor`, `boule.ts`'s
|
|
@@ -80,6 +81,12 @@ export interface OrgPlatformReadiness {
|
|
|
80
81
|
* gets as `AI_URL`. `null` or absent until the platform has applied the AI host.
|
|
81
82
|
*/
|
|
82
83
|
aiUrl?: string | null;
|
|
84
|
+
/**
|
|
85
|
+
* The public origin of the jobs scheduler an entry with `uses.jobs` registers on, which it gets as
|
|
86
|
+
* `JOBS_SCHEDULER_URL`. A company on shared services reads the owner company's (`ownerJobsPublicUrl`); a
|
|
87
|
+
* company that runs its own services reads its own. `null` or absent until that route is recorded.
|
|
88
|
+
*/
|
|
89
|
+
jobsPublicUrl?: string | null;
|
|
83
90
|
/**
|
|
84
91
|
* The shared calls SFU (#170) an org's own calls Matrix stack depends on.
|
|
85
92
|
* Declared is enough here: `platform-diff.ts`'s `planSfu` plans the step
|
|
@@ -187,6 +194,24 @@ export interface OrgPlatformReadiness {
|
|
|
187
194
|
unreadable?: readonly string[];
|
|
188
195
|
invalid?: readonly string[];
|
|
189
196
|
};
|
|
197
|
+
/**
|
|
198
|
+
* The platform GitHub App's held keys (`PLATFORM_GITHUB_APP_REFS`), read only for a company that declares
|
|
199
|
+
* `hosting.github`. `store` is false when the host wired no held-keys reader; `missing` are the refs that hold
|
|
200
|
+
* no value now. Refs only, never a value. Absent reads as no store.
|
|
201
|
+
*/
|
|
202
|
+
githubAppKeys?: {
|
|
203
|
+
store: boolean;
|
|
204
|
+
missing: readonly string[];
|
|
205
|
+
};
|
|
206
|
+
/**
|
|
207
|
+
* F12: what GitHub said about the repo of each entry that declares `uses.repo` (or has a key to remove), by
|
|
208
|
+
* entry key. `store` is false when the host wired no GitHub deploy-key client. Lines only, never a credential.
|
|
209
|
+
* Absent for an org with neither.
|
|
210
|
+
*/
|
|
211
|
+
repoKeys?: {
|
|
212
|
+
store: boolean;
|
|
213
|
+
probes: Readonly<Record<string, RepoKeyProbe>>;
|
|
214
|
+
};
|
|
190
215
|
/**
|
|
191
216
|
* True when the host wired no held-keys reader, so no key could be checked.
|
|
192
217
|
* A missing key is then reported as "no reader given" instead of "not held".
|
|
@@ -277,6 +302,11 @@ export declare const OWN_FILES_RUNNER_SCOPE_KEY = "coolify_platform_files_runner
|
|
|
277
302
|
* `base`. Any other org gets `base` back unchanged.
|
|
278
303
|
*/
|
|
279
304
|
export declare function orgPlatformReadinessFor(org: DeclaredOrg, base: OrgPlatformReadiness, orgRecordedIds: Readonly<Record<string, string>>, heldRefs: ReadonlySet<string>): OrgPlatformReadiness;
|
|
305
|
+
/**
|
|
306
|
+
* The public origin of the owner company's jobs scheduler, which a company on shared services registers its
|
|
307
|
+
* entries on: what the owner's `coolify:own-jobs-route` step recorded, or `undefined` before it ran.
|
|
308
|
+
*/
|
|
309
|
+
export declare function ownerJobsPublicUrl(ownerRecordedIds: Readonly<Record<string, string>> | undefined): string | undefined;
|
|
280
310
|
/** The own org's held-key refs `heldKeys` resolves right now. A resolver that throws counts as not held, and its error goes nowhere. */
|
|
281
311
|
export declare function heldOwnKeyRefs(heldKeys: HeldKeyResolver, org: Pick<DeclaredOrg, 'slug'>): Promise<Set<string>>;
|
|
282
312
|
/** {@link agoraHostValuesFromPlatform}'s counterpart for an own org: every value is the org's own, null until recorded. */
|
|
@@ -6,6 +6,7 @@ import { FILES_MANAGEMENT_KEY_REF, JOBS_ADMIN_TOKEN_REF, PLATFORM_AI_MANAGEMENT_
|
|
|
6
6
|
import { OWN_AUTH_APPLIED_HOSTNAME_SCOPE_KEY, ownAuthApplied } from './login-host.js';
|
|
7
7
|
import { oikosIngressApplied } from './oikos-ingress.js';
|
|
8
8
|
import { ownHostnames } from './own-hostnames.js';
|
|
9
|
+
import { JOBS_PUBLIC_URL_SCOPE_KEY } from './own-jobs-route.js';
|
|
9
10
|
import { OWN_SERVICES_APPLIED_SCOPE_KEY, orgTarget } from './own-services.js';
|
|
10
11
|
import { JOBS_INTERNAL_URL_SCOPE_KEY, JOBS_SCHEDULER_KEY, PLATFORM_ACCESS_ZITADEL_APP_SCOPE_KEY, PLATFORM_ACCESS_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_AI_KEY, PLATFORM_AI_TUNNEL_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_BROKER_SCOPE_KEY, PLATFORM_ARCHON_ZITADEL_APP_SCOPE_KEY, PLATFORM_ARCHON_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_AUTH_BROKER_SCOPE_KEY, PLATFORM_AUTH_KEY, PLATFORM_EDGE_KEY, PLATFORM_FILES_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, PLATFORM_FILES_KEY, PLATFORM_MAIL_INTERNAL_URL_SCOPE_KEY, PLATFORM_MAIL_KEY, PLATFORM_MAIL_ZITADEL_PROJECT_SCOPE_KEY, platformMailMxHost, } from './platform-diff.js';
|
|
11
12
|
import { SIGN_IN_LOGIN_APP_SCOPE_KEY, inAppSignIn } from './sign-in.js';
|
|
@@ -223,7 +224,8 @@ export function orgPlatformReadinessFor(org, base, orgRecordedIds, heldRefs) {
|
|
|
223
224
|
ai: base.missingKeys.ai,
|
|
224
225
|
jobs: missing(jobsApplied, refs.jobsAdminToken),
|
|
225
226
|
};
|
|
226
|
-
const { jobsUrlMissing: _shared, ...rest } = base;
|
|
227
|
+
const { jobsUrlMissing: _shared, jobsPublicUrl: _sharedRoute, ...rest } = base;
|
|
228
|
+
const ownJobsUrl = orgRecordedIds[JOBS_PUBLIC_URL_SCOPE_KEY];
|
|
227
229
|
return {
|
|
228
230
|
...rest,
|
|
229
231
|
authReady,
|
|
@@ -232,6 +234,7 @@ export function orgPlatformReadinessFor(org, base, orgRecordedIds, heldRefs) {
|
|
|
232
234
|
filesUrl: filesApplied ? (filesUrl ?? null) : null,
|
|
233
235
|
jobsReady: jobsApplied && jobsUrl !== undefined && missingKeys.jobs === null,
|
|
234
236
|
...(jobsApplied && jobsUrl === undefined ? { jobsUrlMissing: true } : {}),
|
|
237
|
+
...(jobsApplied && ownJobsUrl ? { jobsPublicUrl: ownJobsUrl } : {}),
|
|
235
238
|
callsReady: ownStepRecorded(orgRecordedIds, org, 'own-sfu'),
|
|
236
239
|
// The MX name is the shared relay's, `mx.<platform apex>`: no platform mail step is needed.
|
|
237
240
|
// The apex is the company's mail zone: its domain when mail runs on it, else oikos.host.
|
|
@@ -244,6 +247,14 @@ export function orgPlatformReadinessFor(org, base, orgRecordedIds, heldRefs) {
|
|
|
244
247
|
missingKeys,
|
|
245
248
|
};
|
|
246
249
|
}
|
|
250
|
+
/**
|
|
251
|
+
* The public origin of the owner company's jobs scheduler, which a company on shared services registers its
|
|
252
|
+
* entries on: what the owner's `coolify:own-jobs-route` step recorded, or `undefined` before it ran.
|
|
253
|
+
*/
|
|
254
|
+
export function ownerJobsPublicUrl(ownerRecordedIds) {
|
|
255
|
+
const url = ownerRecordedIds?.[JOBS_PUBLIC_URL_SCOPE_KEY];
|
|
256
|
+
return url?.startsWith('https://') ? url : undefined;
|
|
257
|
+
}
|
|
247
258
|
/** The own org's held-key refs `heldKeys` resolves right now. A resolver that throws counts as not held, and its error goes nowhere. */
|
|
248
259
|
export async function heldOwnKeyRefs(heldKeys, org) {
|
|
249
260
|
const held = new Set();
|
|
@@ -496,6 +496,8 @@ export interface JobsHostInput {
|
|
|
496
496
|
export declare function provisionJobsHost(scope: StepScope, deps: JobsHostDeps, target: ServiceTarget, input: JobsHostInput): Promise<{
|
|
497
497
|
resourceId?: string;
|
|
498
498
|
}>;
|
|
499
|
+
/** The Coolify project of the platform's sign-in stack, the broker included. */
|
|
500
|
+
export declare const PLATFORM_AUTH_PROJECT = "platform";
|
|
499
501
|
/**
|
|
500
502
|
* Fixed `custom_internal_name`s (`diff.ts`'s `coolifyCalls` sets the same
|
|
501
503
|
* kind of name on Synapse/MAS) so the Cloudflare tunnel below can address
|
|
@@ -5,7 +5,7 @@ import { VERSIONS, coolifyImageFor, floatingTagBlocked, pinnedRef } from '../boo
|
|
|
5
5
|
import { redactSecrets } from '../calls-sfu-deploy-support.js';
|
|
6
6
|
import { ownerMailboxes, ownsServices, sharedSecretRef, } from '../org.js';
|
|
7
7
|
import { applyArchonBlueGreen } from './archon-blue-green.js';
|
|
8
|
-
import { ARCHON_REPO, archonSubject, healthTokenName, tunnelOriginFor, } from './blue-green.js';
|
|
8
|
+
import { ARCHON_REPO, archonSubject, healthTokenName, pinKeyName, tunnelOriginFor, } from './blue-green.js';
|
|
9
9
|
import { BOULE_BRIDGE_SECRET_ENV, BOULE_PEER_PASSWORD_ENV, BOULE_PEER_ROLE, bouleHostnameFor, } from './boule.js';
|
|
10
10
|
import { recordBuiltCommit } from './build-record.js';
|
|
11
11
|
import { ensureCloudflaredConnector } from './cloudflared-connector.js';
|
|
@@ -215,6 +215,8 @@ export class FoundryPlatformStepExecutor {
|
|
|
215
215
|
coolify: archon.coolify,
|
|
216
216
|
healthToken: async () => (await archon.heldKeys?.resolve(sharedSecretRef(platform.ownerOrgId, healthTokenName(subject.id)))) ?? null,
|
|
217
217
|
secret: (value) => archon.runtime?.onSecret?.(value),
|
|
218
|
+
pinRef: sharedSecretRef(platform.ownerOrgId, pinKeyName(subject.id)),
|
|
219
|
+
...(archon.heldKeys ? { heldKeys: archon.heldKeys } : {}),
|
|
218
220
|
primaryUuid,
|
|
219
221
|
primaryBlueAppId,
|
|
220
222
|
runtime: this.runtime,
|
|
@@ -1272,7 +1274,8 @@ export async function provisionJobsHost(scope, deps, target, input) {
|
|
|
1272
1274
|
// ---------------------------------------------------------------------------
|
|
1273
1275
|
// #168: the platform's own sign-in (ZITADEL)
|
|
1274
1276
|
// ---------------------------------------------------------------------------
|
|
1275
|
-
|
|
1277
|
+
/** The Coolify project of the platform's sign-in stack, the broker included. */
|
|
1278
|
+
export const PLATFORM_AUTH_PROJECT = 'platform';
|
|
1276
1279
|
/**
|
|
1277
1280
|
* Fixed `custom_internal_name`s (`diff.ts`'s `coolifyCalls` sets the same
|
|
1278
1281
|
* kind of name on Synapse/MAS) so the Cloudflare tunnel below can address
|
|
@@ -3902,6 +3905,9 @@ async function waitForMailInstance(url, runtime, sleep) {
|
|
|
3902
3905
|
for (;;) {
|
|
3903
3906
|
try {
|
|
3904
3907
|
const response = await fetchImpl(url);
|
|
3908
|
+
// #760: an unread body left undici's parser paused when the socket
|
|
3909
|
+
// ended, throwing an uncaught assertion in the host.
|
|
3910
|
+
await response.body?.cancel();
|
|
3905
3911
|
if (response.ok)
|
|
3906
3912
|
return;
|
|
3907
3913
|
}
|
|
@@ -4027,6 +4033,8 @@ export const PLATFORM_ARCHON_PUBLIC_PATHS = [
|
|
|
4027
4033
|
{ name: 'org-apps', path: '/api/orgs/*/apps' },
|
|
4028
4034
|
{ name: 'org-requests', path: '/api/orgs/*/requests' },
|
|
4029
4035
|
{ name: 'github-releases', path: '/api/github/releases' },
|
|
4036
|
+
{ name: 'github-install-callback', path: '/api/github/install/callback' },
|
|
4037
|
+
{ name: 'org-github', path: '/api/orgs/*/github' },
|
|
4030
4038
|
];
|
|
4031
4039
|
const PLATFORM_ARCHON_PUBLIC_POLICY_NAME = 'foundry: public path';
|
|
4032
4040
|
const archonPublicAppName = (name) => `${PLATFORM_ARCHON_ACCESS_APP_NAME}:public:${name}`;
|
|
@@ -7030,6 +7038,9 @@ async function waitForInstance(url, runtime, sleep, label = 'platform:auth') {
|
|
|
7030
7038
|
const response = await fetchImpl(url, {
|
|
7031
7039
|
signal: AbortSignal.timeout(runtime.readyRequestTimeoutMs ?? 10_000),
|
|
7032
7040
|
});
|
|
7041
|
+
// #760: an unread body left undici's parser paused when the socket
|
|
7042
|
+
// ended, throwing an uncaught assertion in the host.
|
|
7043
|
+
await response.body?.cancel();
|
|
7033
7044
|
if (response.ok)
|
|
7034
7045
|
return;
|
|
7035
7046
|
}
|
|
@@ -16,7 +16,7 @@ export declare function zitadelAppPostLogoutAppliedScopeKey(appKey: string): str
|
|
|
16
16
|
/**
|
|
17
17
|
* The post-logout addresses company app `appKey` is created with and kept at.
|
|
18
18
|
* Agora signs out locally and has no signed-out page, so it holds only its
|
|
19
|
-
* root; every `@wtfalch/auth` app (cms, boule, the app, entries with
|
|
19
|
+
* root; every `@wtfalch/auth` app (cms, boule, kosmos, the app, entries with
|
|
20
20
|
* `uses.auth`) holds both on each of its hosts.
|
|
21
21
|
*/
|
|
22
22
|
export declare function companyAppPostLogoutUris(org: DeclaredOrg, appKey: string): string[];
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { agoraHostnameFor } from './agora.js';
|
|
2
2
|
import { bouleHostnameFor } from './boule.js';
|
|
3
3
|
import { appHostname, cmsStudioHostnameFor, entryHostnames } from './hostnames.js';
|
|
4
|
+
import { kosmosHostnameFor } from './kosmos.js';
|
|
5
|
+
import { workspaceAuthEntryOf, workspaceClientHosts } from './workspace-client.js';
|
|
4
6
|
/** The page `@wtfalch/auth` apps land on after ZITADEL ends the session. */
|
|
5
7
|
export const SIGNED_OUT_PATH = '/auth/signed-out';
|
|
6
8
|
/** Both landing addresses of a host: its root and its signed-out page. */
|
|
@@ -26,18 +28,25 @@ export function zitadelAppPostLogoutAppliedScopeKey(appKey) {
|
|
|
26
28
|
/**
|
|
27
29
|
* The post-logout addresses company app `appKey` is created with and kept at.
|
|
28
30
|
* Agora signs out locally and has no signed-out page, so it holds only its
|
|
29
|
-
* root; every `@wtfalch/auth` app (cms, boule, the app, entries with
|
|
31
|
+
* root; every `@wtfalch/auth` app (cms, boule, kosmos, the app, entries with
|
|
30
32
|
* `uses.auth`) holds both on each of its hosts.
|
|
31
33
|
*/
|
|
32
34
|
export function companyAppPostLogoutUris(org, appKey) {
|
|
33
35
|
const authEntry = org.hosting.entries.find((e) => e.key === appKey && e.uses?.auth);
|
|
34
|
-
const
|
|
36
|
+
const workspaceEntry = authEntry ? undefined : workspaceAuthEntryOf(org, appKey);
|
|
37
|
+
const hosts = authEntry
|
|
38
|
+
? entryHostnames(org, authEntry)
|
|
39
|
+
: workspaceEntry
|
|
40
|
+
? workspaceClientHosts(org, workspaceEntry)
|
|
41
|
+
: null;
|
|
35
42
|
if (appKey === 'agora')
|
|
36
43
|
return [`https://${agoraHostnameFor(org)}/`];
|
|
37
44
|
const hostname = appKey === 'cms' && org.site.enabled
|
|
38
45
|
? cmsStudioHostnameFor(org)
|
|
39
46
|
: appKey === 'boule'
|
|
40
47
|
? bouleHostnameFor(org)
|
|
41
|
-
:
|
|
48
|
+
: appKey === 'kosmos'
|
|
49
|
+
? kosmosHostnameFor(org)
|
|
50
|
+
: appHostname(org);
|
|
42
51
|
return postLogoutUrisFor(hosts ?? [hostname]);
|
|
43
52
|
}
|