@wtfalch/foundry 0.16.16 → 0.17.1
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/apply.d.ts +22 -1
- package/dist/bootstrap/bootstrap.d.ts +6 -1
- package/dist/bootstrap/bootstrap.js +26 -3
- package/dist/bootstrap/declare.d.ts +11 -1
- package/dist/bootstrap/declare.js +18 -6
- package/dist/index.d.ts +11 -6
- package/dist/index.js +8 -1
- package/dist/migrations/0038_calls_version_pin.sql +8 -0
- package/dist/migrations/0039_destroy_record.sql +7 -0
- package/dist/migrations/0040_company_included_uuid.sql +72 -0
- package/dist/migrations/0041_company_included_uuid_keys.sql +132 -0
- package/dist/migrations/0042_mutation_leases.sql +12 -0
- package/dist/migrations/0043_platform_names.sql +25 -0
- package/dist/migrations/0044_platform_mail_lettermint_project.sql +11 -0
- package/dist/org.d.ts +128 -3
- package/dist/org.js +186 -1
- package/dist/plan/adopt.d.ts +76 -0
- package/dist/plan/adopt.js +184 -0
- package/dist/plan/agora.d.ts +21 -0
- package/dist/plan/agora.js +37 -0
- package/dist/plan/broker-entries.d.ts +9 -1
- package/dist/plan/broker-entries.js +13 -5
- package/dist/plan/chat-matrix.d.ts +1 -2
- package/dist/plan/chat-matrix.js +15 -4
- package/dist/plan/core-files.d.ts +25 -0
- package/dist/plan/core-files.js +25 -0
- package/dist/plan/diff.d.ts +27 -3
- package/dist/plan/diff.js +264 -86
- package/dist/plan/edge-worker.d.ts +114 -0
- package/dist/plan/edge-worker.js +201 -0
- package/dist/plan/entry-ai-key.d.ts +3 -3
- package/dist/plan/entry-files-key.d.ts +3 -3
- package/dist/plan/entry-names-key.d.ts +73 -0
- package/dist/plan/entry-names-key.js +236 -0
- package/dist/plan/executor.d.ts +292 -24
- package/dist/plan/executor.js +745 -77
- package/dist/plan/index.d.ts +6 -2
- package/dist/plan/index.js +3 -1
- package/dist/plan/live.d.ts +9 -2
- package/dist/plan/login-host.d.ts +13 -2
- package/dist/plan/login-host.js +26 -7
- package/dist/plan/plan-apply.d.ts +153 -6
- package/dist/plan/plan-apply.js +639 -78
- package/dist/plan/platform-diff.d.ts +106 -1
- package/dist/plan/platform-diff.js +182 -2
- package/dist/plan/platform-org.d.ts +45 -1
- package/dist/plan/platform-org.js +34 -3
- package/dist/plan/platform-providers.d.ts +45 -1
- package/dist/plan/platform-providers.js +585 -16
- package/dist/plan/providers-readers.d.ts +2 -0
- package/dist/plan/providers-readers.js +4 -0
- package/dist/plan/rollout.d.ts +52 -0
- package/dist/plan/rollout.js +37 -0
- package/dist/plan/step-system.d.ts +22 -0
- package/dist/plan/step-system.js +38 -0
- package/dist/plan.d.ts +3 -0
- package/dist/platform.d.ts +22 -3
- package/dist/platform.js +6 -0
- package/dist/state.d.ts +13 -0
- package/dist/store/audit.js +15 -1
- package/dist/store/postgres.d.ts +1 -1
- package/dist/store/postgres.js +142 -15
- package/dist/store/schema.d.ts +202 -5
- package/dist/store/schema.js +18 -1
- package/dist/store.d.ts +47 -4
- package/dist/store.js +9 -1
- package/package.json +1 -1
|
@@ -118,6 +118,23 @@ export declare const PLATFORM_ARCHON_APPLIED_GIT_REF_SCOPE_KEY = "archon_applied
|
|
|
118
118
|
* unrecorded and the old key stays pinned.
|
|
119
119
|
*/
|
|
120
120
|
export declare const PLATFORM_ARCHON_INTEGRATIONS_KEY_SYNCED_SCOPE_KEY = "archon_integrations_key_synced";
|
|
121
|
+
/**
|
|
122
|
+
* #467: sha256 of the exact env set the *last confirmed* Archon deploy ran
|
|
123
|
+
* with, recorded only once that deploy's `waitForHealthyVersion` passed --
|
|
124
|
+
* mirroring {@link PLATFORM_ARCHON_INTEGRATIONS_KEY_SYNCED_SCOPE_KEY}'s own
|
|
125
|
+
* confirm-after-health-check timing, generalized from one env to all of
|
|
126
|
+
* them. `provisionPlatformArchon` clears this to `''` the moment it decides
|
|
127
|
+
* a redeploy is needed, before attempting it: a deploy that then fails (or
|
|
128
|
+
* a crash before the health check passes) must leave the attempt
|
|
129
|
+
* unconfirmed, and the env hash alone cannot tell a failed retry's intended
|
|
130
|
+
* content apart from a successful one's, since both intend the same bytes.
|
|
131
|
+
* Without this, a run that writes new envs and then fails before Archon
|
|
132
|
+
* redeploys -- `envsDiffer` comparing desired envs against the *live*
|
|
133
|
+
* store, which this run's own write already updated -- leaves a retry
|
|
134
|
+
* reading no difference at all, and the container stays on the stale
|
|
135
|
+
* values until someone redeploys by hand (#467).
|
|
136
|
+
*/
|
|
137
|
+
export declare const PLATFORM_ARCHON_ENVS_DEPLOYED_SCOPE_KEY = "archon_envs_deployed_hash";
|
|
121
138
|
/**
|
|
122
139
|
* #211 round 4: the broker app id Archon last registered its entry with.
|
|
123
140
|
* `provisionPlatformArchon` reads the broker to register with from
|
|
@@ -136,6 +153,21 @@ export declare const PLATFORM_ARCHON_APPLIED_BROKER_SCOPE_KEY = "archon_applied_
|
|
|
136
153
|
* has recorded it.
|
|
137
154
|
*/
|
|
138
155
|
export declare const PLATFORM_ARCHON_APPLIED_OPERATOR_SCOPE_KEY = "archon_applied_bootstrap_operator";
|
|
156
|
+
/**
|
|
157
|
+
* #475: whether the last successful Archon apply patched the app with
|
|
158
|
+
* `custom_network_aliases` and `is_consistent_container_name_enabled: false`
|
|
159
|
+
* instead of the old fixed `custom_internal_name` alone. Coolify only runs a
|
|
160
|
+
* rolling, health-checked deploy -- start the new container, wait, then stop
|
|
161
|
+
* the old one -- when the consistent-name flag is off; with it on (set by
|
|
162
|
+
* foundry's own pre-#475 applies, or by hand in the Coolify UI) every deploy
|
|
163
|
+
* stops the old container before the new one exists, which is what took
|
|
164
|
+
* Archon down for #475's incident. Absent on every platform applied before
|
|
165
|
+
* this fix landed; `planArchon` plans the step again until
|
|
166
|
+
* `provisionPlatformArchon` records it, which only happens once that run's
|
|
167
|
+
* redeploy has passed its health check -- the same confirm-after-health-check
|
|
168
|
+
* timing {@link PLATFORM_ARCHON_ENVS_DEPLOYED_SCOPE_KEY} uses.
|
|
169
|
+
*/
|
|
170
|
+
export declare const PLATFORM_ARCHON_APPLIED_ROLLING_UPDATE_SCOPE_KEY = "archon_applied_rolling_update";
|
|
139
171
|
/**
|
|
140
172
|
* The owner's ZITADEL user id Archon takes as its first operator, from
|
|
141
173
|
* {@link PLATFORM_AUTH_OWNER_INVITE_SCOPE_KEY}. Undefined when none is
|
|
@@ -309,6 +341,20 @@ export declare const PLATFORM_ARCHON_CONNECTOR_APP_SCOPE_KEY = "coolify_platform
|
|
|
309
341
|
export declare const PLATFORM_ARCHON_MASTER_COOLIFY_URL_REF = "platform:master:coolify-url";
|
|
310
342
|
export declare const PLATFORM_ARCHON_MASTER_COOLIFY_TOKEN_REF = "platform:master:coolify-token";
|
|
311
343
|
export declare const PLATFORM_ARCHON_MASTER_CLOUDFLARE_TOKEN_REF = "platform:master:cloudflare-token";
|
|
344
|
+
/**
|
|
345
|
+
* #436: a Coolify API token scoped to `read` only, for Archon's own
|
|
346
|
+
* `/backups` page (`COOLIFY_URL`/`COOLIFY_READ_TOKEN`, Archon's
|
|
347
|
+
* `src/lib/backups/coolify.ts`) -- deliberately not
|
|
348
|
+
* {@link PLATFORM_ARCHON_MASTER_COOLIFY_TOKEN_REF}'s root token, which a
|
|
349
|
+
* read-only status page must never hold. Coolify's token UI has no API
|
|
350
|
+
* counterpart to create one (`@wtfalch/providers`'s `CoolifyClient` doc:
|
|
351
|
+
* "Keys & Tokens -> API tokens" only), so a human creates it in Coolify's
|
|
352
|
+
* console and holds it under this ref; `provisionPlatformArchon` only reads
|
|
353
|
+
* it back, and pushes nothing until it is held. `COOLIFY_URL` needs no ref
|
|
354
|
+
* of its own: it is not secret, and {@link PLATFORM_ARCHON_MASTER_COOLIFY_URL_REF}
|
|
355
|
+
* already holds the same panel's address.
|
|
356
|
+
*/
|
|
357
|
+
export declare const PLATFORM_ARCHON_COOLIFY_READ_TOKEN_REF = "platform:archon:coolify-read-token";
|
|
312
358
|
/**
|
|
313
359
|
* #169: `platform:<service>` for the platform's own mail server (Stalwart),
|
|
314
360
|
* mirroring {@link PLATFORM_AUTH_KEY} exactly.
|
|
@@ -395,6 +441,18 @@ export declare function appEnvAppliedScopeKey(entryKey: string): string;
|
|
|
395
441
|
* would leave a stale container that no plan notices.
|
|
396
442
|
*/
|
|
397
443
|
export declare function dbEnvAppliedScopeKey(entryKey: string): string;
|
|
444
|
+
/**
|
|
445
|
+
* #456: the same marker as {@link dbEnvAppliedScopeKey}, for the chat and
|
|
446
|
+
* calls-service sibling platform services' own owner/runtime database split
|
|
447
|
+
* -- in its own `service_db_env_applied.` namespace, never
|
|
448
|
+
* `dbEnvAppliedScopeKey`'s `db_env_applied.`. Those two services are not
|
|
449
|
+
* hosting entries, but `dbEnvAppliedScopeKey`'s `entryKey` is an arbitrary
|
|
450
|
+
* caller-chosen string with no reserved words (`validateHostingEntry` only
|
|
451
|
+
* requires a DNS label), so a hosting entry a company declares with the key
|
|
452
|
+
* `chat` or `calls-service` would otherwise read or clear the sibling
|
|
453
|
+
* service's own marker, and vice versa.
|
|
454
|
+
*/
|
|
455
|
+
export declare function serviceDbEnvAppliedScopeKey(service: 'chat' | 'calls-service'): string;
|
|
398
456
|
/**
|
|
399
457
|
* The settings `coolifyApp` owns on an entry's app (`folder`, `port`,
|
|
400
458
|
* `autoDeploy`) as the JSON the marker below holds. `coolifyApp` records it
|
|
@@ -563,6 +621,50 @@ export declare const PLATFORM_FILES_MANAGEMENT_UNDELIVERED_SCOPE_KEY = "files_ma
|
|
|
563
621
|
*/
|
|
564
622
|
export declare const PLATFORM_FILES_APPLIED_HOSTNAME_SCOPE_KEY = "files_applied_hostname";
|
|
565
623
|
export declare const PLATFORM_FILES_APPLIED_GIT_REF_SCOPE_KEY = "files_applied_git_ref";
|
|
624
|
+
/**
|
|
625
|
+
* #478: the resource key `planNames` plans under and
|
|
626
|
+
* `FoundryPlatformStepExecutor` dispatches on -- the {@link PLATFORM_FILES_KEY}
|
|
627
|
+
* shape for the names host (https://github.com/wtfalch/names). `runPlatformSteps`
|
|
628
|
+
* records the app's uuid under `coolify:app:platform:names` once the step
|
|
629
|
+
* succeeds, which is what stops `planNames` planning it again.
|
|
630
|
+
*/
|
|
631
|
+
export declare const PLATFORM_NAMES_KEY = "platform:names";
|
|
632
|
+
/**
|
|
633
|
+
* #478: the held-key ref the names platform secret's full value is
|
|
634
|
+
* delivered under, once, by {@link FoundryStepExecutorPorts.deliverSecret} --
|
|
635
|
+
* never the host's app env, which gets only its SHA-256 hash
|
|
636
|
+
* (`NAMES_PLATFORM_KEY_HASH`). Archon's own `NamesPort` reads the full
|
|
637
|
+
* secret back from this ref to call names' `mintKey`/`revokeKey`/
|
|
638
|
+
* `setReservedLabels`.
|
|
639
|
+
*/
|
|
640
|
+
export declare const PLATFORM_NAMES_KEY_REF = "names:platform:key";
|
|
641
|
+
/**
|
|
642
|
+
* #478: the held-key ref for names' own Cloudflare API token (tunnel edit +
|
|
643
|
+
* DNS edit on one account) -- the issue's own "never Archon's token": this
|
|
644
|
+
* is a narrower, names-only credential a human provisions and holds
|
|
645
|
+
* separately from whatever broader token `PlatformNamesDeps.cloudflare`
|
|
646
|
+
* uses to stand the host itself up on Coolify.
|
|
647
|
+
*/
|
|
648
|
+
export declare const PLATFORM_NAMES_CLOUDFLARE_TOKEN_REF = "cloudflare:names:apiToken";
|
|
649
|
+
/**
|
|
650
|
+
* #478: scope keys (colon-free) for the names platform key, the
|
|
651
|
+
* {@link PLATFORM_FILES_MANAGEMENT_UNDELIVERED_SCOPE_KEY} pattern once:
|
|
652
|
+
* the undelivered id is recorded and persisted the moment the secret is
|
|
653
|
+
* minted, before delivery; the guard is recorded only once it was delivered
|
|
654
|
+
* AND its hash written to the runner's env. An undelivered id with no guard
|
|
655
|
+
* means the secret is lost (never kept past that point): the planner
|
|
656
|
+
* reports it and never mints again. Neither key holds the secret itself.
|
|
657
|
+
*/
|
|
658
|
+
export declare const PLATFORM_NAMES_PLATFORM_KEY_UNDELIVERED_SCOPE_KEY = "names_platform_key_undelivered";
|
|
659
|
+
export declare const PLATFORM_NAMES_PLATFORM_KEY_SET_SCOPE_KEY = "names_platform_key_set";
|
|
660
|
+
/**
|
|
661
|
+
* #478: the hostname and `gitRef` the last successful names run applied.
|
|
662
|
+
* `planNames` plans the step again when either differs from the declared
|
|
663
|
+
* value; a re-run updates `git_branch` and the runner env and redeploys,
|
|
664
|
+
* and mints no new platform key (the guards above hold).
|
|
665
|
+
*/
|
|
666
|
+
export declare const PLATFORM_NAMES_APPLIED_HOSTNAME_SCOPE_KEY = "names_applied_hostname";
|
|
667
|
+
export declare const PLATFORM_NAMES_APPLIED_GIT_REF_SCOPE_KEY = "names_applied_git_ref";
|
|
566
668
|
/**
|
|
567
669
|
* Options for {@link planPlatform}, replacing four positional arguments that
|
|
568
670
|
* shifted on every merge that added a platform service (#186, #187, #188) --
|
|
@@ -601,6 +703,8 @@ export interface PlanPlatformOptions {
|
|
|
601
703
|
ai?: string;
|
|
602
704
|
archon?: string;
|
|
603
705
|
mail?: string;
|
|
706
|
+
/** #478: why the names host is blocked, or absent when it is not. */
|
|
707
|
+
names?: string;
|
|
604
708
|
/**
|
|
605
709
|
* #257: true once a service's database exists but the host built the
|
|
606
710
|
* platform executor with no backup dependencies -- every such service's
|
|
@@ -652,7 +756,8 @@ export interface PlanPlatformOptions {
|
|
|
652
756
|
* Scope for #167: the frame plus one step, {@link planApexZone}. #173 adds
|
|
653
757
|
* {@link planJobs}, #170 adds {@link planSfu}, #168 adds {@link planAuth},
|
|
654
758
|
* #169 adds {@link planMail}, #174 adds {@link planIntegrations}, #171 adds
|
|
655
|
-
* {@link planFiles}, #172 adds {@link planAi}
|
|
759
|
+
* {@link planFiles}, #172 adds {@link planAi}, #478 adds {@link planNames}
|
|
760
|
+
* and #175 adds
|
|
656
761
|
* {@link planArchon}, the same way #19 shipped org plan/apply against
|
|
657
762
|
* three singletons before #27, #71, #80 and the rest added the remaining
|
|
658
763
|
* twelve. Until then, a live resource under one of those eventual keys is
|
|
@@ -2,6 +2,7 @@ import { VERSIONS } from '../bootstrap/versions.js';
|
|
|
2
2
|
import { APP_ENTRY_KEY } from '../org.js';
|
|
3
3
|
import { VERSION_CHANGE_KIND, } from '../plan.js';
|
|
4
4
|
import { legacyAppHostname } from './hostnames.js';
|
|
5
|
+
import { withStepSystem } from './step-system.js';
|
|
5
6
|
/**
|
|
6
7
|
* The `Plan` (#19's public contract) a {@link PlatformPlanDetail} carries,
|
|
7
8
|
* mirroring `diff.ts`'s `toPlan`. There is no separate platform-shaped `Plan`
|
|
@@ -138,6 +139,23 @@ export const PLATFORM_ARCHON_APPLIED_GIT_REF_SCOPE_KEY = 'archon_applied_git_ref
|
|
|
138
139
|
* unrecorded and the old key stays pinned.
|
|
139
140
|
*/
|
|
140
141
|
export const PLATFORM_ARCHON_INTEGRATIONS_KEY_SYNCED_SCOPE_KEY = 'archon_integrations_key_synced';
|
|
142
|
+
/**
|
|
143
|
+
* #467: sha256 of the exact env set the *last confirmed* Archon deploy ran
|
|
144
|
+
* with, recorded only once that deploy's `waitForHealthyVersion` passed --
|
|
145
|
+
* mirroring {@link PLATFORM_ARCHON_INTEGRATIONS_KEY_SYNCED_SCOPE_KEY}'s own
|
|
146
|
+
* confirm-after-health-check timing, generalized from one env to all of
|
|
147
|
+
* them. `provisionPlatformArchon` clears this to `''` the moment it decides
|
|
148
|
+
* a redeploy is needed, before attempting it: a deploy that then fails (or
|
|
149
|
+
* a crash before the health check passes) must leave the attempt
|
|
150
|
+
* unconfirmed, and the env hash alone cannot tell a failed retry's intended
|
|
151
|
+
* content apart from a successful one's, since both intend the same bytes.
|
|
152
|
+
* Without this, a run that writes new envs and then fails before Archon
|
|
153
|
+
* redeploys -- `envsDiffer` comparing desired envs against the *live*
|
|
154
|
+
* store, which this run's own write already updated -- leaves a retry
|
|
155
|
+
* reading no difference at all, and the container stays on the stale
|
|
156
|
+
* values until someone redeploys by hand (#467).
|
|
157
|
+
*/
|
|
158
|
+
export const PLATFORM_ARCHON_ENVS_DEPLOYED_SCOPE_KEY = 'archon_envs_deployed_hash';
|
|
141
159
|
/**
|
|
142
160
|
* #211 round 4: the broker app id Archon last registered its entry with.
|
|
143
161
|
* `provisionPlatformArchon` reads the broker to register with from
|
|
@@ -156,6 +174,21 @@ export const PLATFORM_ARCHON_APPLIED_BROKER_SCOPE_KEY = 'archon_applied_broker_a
|
|
|
156
174
|
* has recorded it.
|
|
157
175
|
*/
|
|
158
176
|
export const PLATFORM_ARCHON_APPLIED_OPERATOR_SCOPE_KEY = 'archon_applied_bootstrap_operator';
|
|
177
|
+
/**
|
|
178
|
+
* #475: whether the last successful Archon apply patched the app with
|
|
179
|
+
* `custom_network_aliases` and `is_consistent_container_name_enabled: false`
|
|
180
|
+
* instead of the old fixed `custom_internal_name` alone. Coolify only runs a
|
|
181
|
+
* rolling, health-checked deploy -- start the new container, wait, then stop
|
|
182
|
+
* the old one -- when the consistent-name flag is off; with it on (set by
|
|
183
|
+
* foundry's own pre-#475 applies, or by hand in the Coolify UI) every deploy
|
|
184
|
+
* stops the old container before the new one exists, which is what took
|
|
185
|
+
* Archon down for #475's incident. Absent on every platform applied before
|
|
186
|
+
* this fix landed; `planArchon` plans the step again until
|
|
187
|
+
* `provisionPlatformArchon` records it, which only happens once that run's
|
|
188
|
+
* redeploy has passed its health check -- the same confirm-after-health-check
|
|
189
|
+
* timing {@link PLATFORM_ARCHON_ENVS_DEPLOYED_SCOPE_KEY} uses.
|
|
190
|
+
*/
|
|
191
|
+
export const PLATFORM_ARCHON_APPLIED_ROLLING_UPDATE_SCOPE_KEY = 'archon_applied_rolling_update';
|
|
159
192
|
/**
|
|
160
193
|
* The owner's ZITADEL user id Archon takes as its first operator, from
|
|
161
194
|
* {@link PLATFORM_AUTH_OWNER_INVITE_SCOPE_KEY}. Undefined when none is
|
|
@@ -343,6 +376,20 @@ export const PLATFORM_ARCHON_CONNECTOR_APP_SCOPE_KEY = 'coolify_platform_archon_
|
|
|
343
376
|
export const PLATFORM_ARCHON_MASTER_COOLIFY_URL_REF = 'platform:master:coolify-url';
|
|
344
377
|
export const PLATFORM_ARCHON_MASTER_COOLIFY_TOKEN_REF = 'platform:master:coolify-token';
|
|
345
378
|
export const PLATFORM_ARCHON_MASTER_CLOUDFLARE_TOKEN_REF = 'platform:master:cloudflare-token';
|
|
379
|
+
/**
|
|
380
|
+
* #436: a Coolify API token scoped to `read` only, for Archon's own
|
|
381
|
+
* `/backups` page (`COOLIFY_URL`/`COOLIFY_READ_TOKEN`, Archon's
|
|
382
|
+
* `src/lib/backups/coolify.ts`) -- deliberately not
|
|
383
|
+
* {@link PLATFORM_ARCHON_MASTER_COOLIFY_TOKEN_REF}'s root token, which a
|
|
384
|
+
* read-only status page must never hold. Coolify's token UI has no API
|
|
385
|
+
* counterpart to create one (`@wtfalch/providers`'s `CoolifyClient` doc:
|
|
386
|
+
* "Keys & Tokens -> API tokens" only), so a human creates it in Coolify's
|
|
387
|
+
* console and holds it under this ref; `provisionPlatformArchon` only reads
|
|
388
|
+
* it back, and pushes nothing until it is held. `COOLIFY_URL` needs no ref
|
|
389
|
+
* of its own: it is not secret, and {@link PLATFORM_ARCHON_MASTER_COOLIFY_URL_REF}
|
|
390
|
+
* already holds the same panel's address.
|
|
391
|
+
*/
|
|
392
|
+
export const PLATFORM_ARCHON_COOLIFY_READ_TOKEN_REF = 'platform:archon:coolify-read-token';
|
|
346
393
|
/**
|
|
347
394
|
* #169: `platform:<service>` for the platform's own mail server (Stalwart),
|
|
348
395
|
* mirroring {@link PLATFORM_AUTH_KEY} exactly.
|
|
@@ -439,6 +486,20 @@ export function appEnvAppliedScopeKey(entryKey) {
|
|
|
439
486
|
export function dbEnvAppliedScopeKey(entryKey) {
|
|
440
487
|
return `db_env_applied.${entryKey}`;
|
|
441
488
|
}
|
|
489
|
+
/**
|
|
490
|
+
* #456: the same marker as {@link dbEnvAppliedScopeKey}, for the chat and
|
|
491
|
+
* calls-service sibling platform services' own owner/runtime database split
|
|
492
|
+
* -- in its own `service_db_env_applied.` namespace, never
|
|
493
|
+
* `dbEnvAppliedScopeKey`'s `db_env_applied.`. Those two services are not
|
|
494
|
+
* hosting entries, but `dbEnvAppliedScopeKey`'s `entryKey` is an arbitrary
|
|
495
|
+
* caller-chosen string with no reserved words (`validateHostingEntry` only
|
|
496
|
+
* requires a DNS label), so a hosting entry a company declares with the key
|
|
497
|
+
* `chat` or `calls-service` would otherwise read or clear the sibling
|
|
498
|
+
* service's own marker, and vice versa.
|
|
499
|
+
*/
|
|
500
|
+
export function serviceDbEnvAppliedScopeKey(service) {
|
|
501
|
+
return `service_db_env_applied.${service}`;
|
|
502
|
+
}
|
|
442
503
|
export function appSettingsOf(entry) {
|
|
443
504
|
return { folder: entry.folder, port: entry.port, autoDeploy: entry.autoDeploy };
|
|
444
505
|
}
|
|
@@ -636,6 +697,50 @@ export const PLATFORM_FILES_MANAGEMENT_UNDELIVERED_SCOPE_KEY = 'files_management
|
|
|
636
697
|
*/
|
|
637
698
|
export const PLATFORM_FILES_APPLIED_HOSTNAME_SCOPE_KEY = 'files_applied_hostname';
|
|
638
699
|
export const PLATFORM_FILES_APPLIED_GIT_REF_SCOPE_KEY = 'files_applied_git_ref';
|
|
700
|
+
/**
|
|
701
|
+
* #478: the resource key `planNames` plans under and
|
|
702
|
+
* `FoundryPlatformStepExecutor` dispatches on -- the {@link PLATFORM_FILES_KEY}
|
|
703
|
+
* shape for the names host (https://github.com/wtfalch/names). `runPlatformSteps`
|
|
704
|
+
* records the app's uuid under `coolify:app:platform:names` once the step
|
|
705
|
+
* succeeds, which is what stops `planNames` planning it again.
|
|
706
|
+
*/
|
|
707
|
+
export const PLATFORM_NAMES_KEY = 'platform:names';
|
|
708
|
+
/**
|
|
709
|
+
* #478: the held-key ref the names platform secret's full value is
|
|
710
|
+
* delivered under, once, by {@link FoundryStepExecutorPorts.deliverSecret} --
|
|
711
|
+
* never the host's app env, which gets only its SHA-256 hash
|
|
712
|
+
* (`NAMES_PLATFORM_KEY_HASH`). Archon's own `NamesPort` reads the full
|
|
713
|
+
* secret back from this ref to call names' `mintKey`/`revokeKey`/
|
|
714
|
+
* `setReservedLabels`.
|
|
715
|
+
*/
|
|
716
|
+
export const PLATFORM_NAMES_KEY_REF = 'names:platform:key';
|
|
717
|
+
/**
|
|
718
|
+
* #478: the held-key ref for names' own Cloudflare API token (tunnel edit +
|
|
719
|
+
* DNS edit on one account) -- the issue's own "never Archon's token": this
|
|
720
|
+
* is a narrower, names-only credential a human provisions and holds
|
|
721
|
+
* separately from whatever broader token `PlatformNamesDeps.cloudflare`
|
|
722
|
+
* uses to stand the host itself up on Coolify.
|
|
723
|
+
*/
|
|
724
|
+
export const PLATFORM_NAMES_CLOUDFLARE_TOKEN_REF = 'cloudflare:names:apiToken';
|
|
725
|
+
/**
|
|
726
|
+
* #478: scope keys (colon-free) for the names platform key, the
|
|
727
|
+
* {@link PLATFORM_FILES_MANAGEMENT_UNDELIVERED_SCOPE_KEY} pattern once:
|
|
728
|
+
* the undelivered id is recorded and persisted the moment the secret is
|
|
729
|
+
* minted, before delivery; the guard is recorded only once it was delivered
|
|
730
|
+
* AND its hash written to the runner's env. An undelivered id with no guard
|
|
731
|
+
* means the secret is lost (never kept past that point): the planner
|
|
732
|
+
* reports it and never mints again. Neither key holds the secret itself.
|
|
733
|
+
*/
|
|
734
|
+
export const PLATFORM_NAMES_PLATFORM_KEY_UNDELIVERED_SCOPE_KEY = 'names_platform_key_undelivered';
|
|
735
|
+
export const PLATFORM_NAMES_PLATFORM_KEY_SET_SCOPE_KEY = 'names_platform_key_set';
|
|
736
|
+
/**
|
|
737
|
+
* #478: the hostname and `gitRef` the last successful names run applied.
|
|
738
|
+
* `planNames` plans the step again when either differs from the declared
|
|
739
|
+
* value; a re-run updates `git_branch` and the runner env and redeploys,
|
|
740
|
+
* and mints no new platform key (the guards above hold).
|
|
741
|
+
*/
|
|
742
|
+
export const PLATFORM_NAMES_APPLIED_HOSTNAME_SCOPE_KEY = 'names_applied_hostname';
|
|
743
|
+
export const PLATFORM_NAMES_APPLIED_GIT_REF_SCOPE_KEY = 'names_applied_git_ref';
|
|
639
744
|
/**
|
|
640
745
|
* #167: the platform's own diff engine, mirroring `diff.ts`'s
|
|
641
746
|
* `computePlanDetail` for the one shared {@link DeclaredPlatform} record
|
|
@@ -650,7 +755,8 @@ export const PLATFORM_FILES_APPLIED_GIT_REF_SCOPE_KEY = 'files_applied_git_ref';
|
|
|
650
755
|
* Scope for #167: the frame plus one step, {@link planApexZone}. #173 adds
|
|
651
756
|
* {@link planJobs}, #170 adds {@link planSfu}, #168 adds {@link planAuth},
|
|
652
757
|
* #169 adds {@link planMail}, #174 adds {@link planIntegrations}, #171 adds
|
|
653
|
-
* {@link planFiles}, #172 adds {@link planAi}
|
|
758
|
+
* {@link planFiles}, #172 adds {@link planAi}, #478 adds {@link planNames}
|
|
759
|
+
* and #175 adds
|
|
654
760
|
* {@link planArchon}, the same way #19 shipped org plan/apply against
|
|
655
761
|
* three singletons before #27, #71, #80 and the rest added the remaining
|
|
656
762
|
* twelve. Until then, a live resource under one of those eventual keys is
|
|
@@ -699,6 +805,10 @@ export function planPlatform(platform, live, options = {}) {
|
|
|
699
805
|
const ai = planAi(platform, recordedIds, blocked.ai);
|
|
700
806
|
if (ai.step)
|
|
701
807
|
plannedSteps.push(ai.step);
|
|
808
|
+
// #478: independent of auth and files -- the names host needs neither.
|
|
809
|
+
const names = planNames(platform, recordedIds, blocked.names);
|
|
810
|
+
if (names.step)
|
|
811
|
+
plannedSteps.push(names.step);
|
|
702
812
|
// #175: last, on purpose. Archon's sign-in client lives in the sign-in
|
|
703
813
|
// `planAuth` builds, and its env reads what every other service's step
|
|
704
814
|
// records or delivers, so in one apply it runs after all of them.
|
|
@@ -732,6 +842,9 @@ export function planPlatform(platform, live, options = {}) {
|
|
|
732
842
|
integrations.blocked
|
|
733
843
|
? null
|
|
734
844
|
: versionChangeLine('integrations', recordedIds[PLATFORM_INTEGRATIONS_APPLIED_GITREF_SCOPE_KEY], platform.integrations.gitRef, 'git ref, redeploy'),
|
|
845
|
+
names.blocked
|
|
846
|
+
? null
|
|
847
|
+
: versionChangeLine('names', recordedIds[PLATFORM_NAMES_APPLIED_GIT_REF_SCOPE_KEY], platform.names.gitRef, 'git ref, redeploy'),
|
|
735
848
|
auth.blocked
|
|
736
849
|
? null
|
|
737
850
|
: versionNotAppliedLine('zitadel', Boolean(recordedIds[resourceKey('coolify', 'app', PLATFORM_AUTH_KEY)]), recordedIds[PLATFORM_AUTH_ZITADEL_APPLIED_IMAGE_TAG_SCOPE_KEY], VERSIONS.zitadel),
|
|
@@ -763,6 +876,8 @@ export function planPlatform(platform, live, options = {}) {
|
|
|
763
876
|
unmanaged.push({ description: files.blocked, key: 'files' });
|
|
764
877
|
if (ai.blocked)
|
|
765
878
|
unmanaged.push({ description: ai.blocked, key: 'ai' });
|
|
879
|
+
if (names.blocked)
|
|
880
|
+
unmanaged.push({ description: names.blocked, key: 'names' });
|
|
766
881
|
if (archon.blocked)
|
|
767
882
|
unmanaged.push({ description: archon.blocked, key: 'archon' });
|
|
768
883
|
// #398: the old integrations key stays pinned until Archon holds the new
|
|
@@ -777,7 +892,7 @@ export function planPlatform(platform, live, options = {}) {
|
|
|
777
892
|
});
|
|
778
893
|
}
|
|
779
894
|
unmanaged.push(...backups.blocked);
|
|
780
|
-
return { plannedSteps, unmanaged, versionChanges };
|
|
895
|
+
return { plannedSteps: plannedSteps.map(withStepSystem), unmanaged, versionChanges };
|
|
781
896
|
}
|
|
782
897
|
/**
|
|
783
898
|
* The platform's own Cloudflare zone for its shared apex (`platform.ts`'s
|
|
@@ -1631,6 +1746,62 @@ function planFiles(platform, recordedIds, executorBlocked, authReady, drifted) {
|
|
|
1631
1746
|
blocked: null,
|
|
1632
1747
|
};
|
|
1633
1748
|
}
|
|
1749
|
+
/**
|
|
1750
|
+
* #478: the names host (https://github.com/wtfalch/names) -- its database
|
|
1751
|
+
* (owner and runtime roles), its one Coolify app (no separate migrate app:
|
|
1752
|
+
* the Dockerfile migrates inside the runner's own entrypoint, see
|
|
1753
|
+
* `provisionPlatformNames`'s own doc comment), foundry's own platform key
|
|
1754
|
+
* (minted once, its hash alone written to the runner) and a Cloudflare
|
|
1755
|
+
* tunnel + DNS record at `platform.names.hostname`. One `coolify:app` step
|
|
1756
|
+
* keyed {@link PLATFORM_NAMES_KEY}, the {@link planFiles} bundling shape,
|
|
1757
|
+
* public like {@link planMail}/{@link planAuth} rather than internal-only
|
|
1758
|
+
* like {@link planFiles}/{@link planAi}.
|
|
1759
|
+
*
|
|
1760
|
+
* Idempotent on the recorded app id plus the applied hostname and `gitRef`,
|
|
1761
|
+
* for {@link planAuth}'s own reason: no platform reader reports this
|
|
1762
|
+
* Coolify app. A changed hostname or `gitRef` plans the step again (a
|
|
1763
|
+
* redeploy, no new secret).
|
|
1764
|
+
*
|
|
1765
|
+
* Blocked, not planned: a platform key minted but never delivered (lost,
|
|
1766
|
+
* `ServiceError`-free the way {@link planFiles}'s own management-key check
|
|
1767
|
+
* is), or `executorBlocked` -- the host built no names dependencies, or
|
|
1768
|
+
* (CLAUDE.md's "Human steps") no Cloudflare API token is held yet under
|
|
1769
|
+
* {@link PLATFORM_NAMES_CLOUDFLARE_TOKEN_REF}.
|
|
1770
|
+
*/
|
|
1771
|
+
function planNames(platform, recordedIds, executorBlocked) {
|
|
1772
|
+
if (!platform.names.enabled)
|
|
1773
|
+
return { step: null, blocked: null };
|
|
1774
|
+
const undelivered = recordedIds[PLATFORM_NAMES_PLATFORM_KEY_UNDELIVERED_SCOPE_KEY];
|
|
1775
|
+
if (undelivered && !recordedIds[PLATFORM_NAMES_PLATFORM_KEY_SET_SCOPE_KEY]) {
|
|
1776
|
+
return {
|
|
1777
|
+
step: null,
|
|
1778
|
+
blocked: `the names host is blocked, not planned: its platform key (${undelivered}) was minted but never delivered and cannot be shown again; remove the recorded key ${PLATFORM_NAMES_PLATFORM_KEY_UNDELIVERED_SCOPE_KEY} to mint a new one`,
|
|
1779
|
+
};
|
|
1780
|
+
}
|
|
1781
|
+
const applied = Boolean(recordedIds[resourceKey('coolify', 'app', PLATFORM_NAMES_KEY)]) &&
|
|
1782
|
+
recordedIds[PLATFORM_NAMES_APPLIED_HOSTNAME_SCOPE_KEY] === platform.names.hostname &&
|
|
1783
|
+
recordedIds[PLATFORM_NAMES_APPLIED_GIT_REF_SCOPE_KEY] === platform.names.gitRef;
|
|
1784
|
+
if (applied)
|
|
1785
|
+
return { step: null, blocked: null };
|
|
1786
|
+
if (executorBlocked)
|
|
1787
|
+
return { step: null, blocked: executorBlocked };
|
|
1788
|
+
return {
|
|
1789
|
+
step: {
|
|
1790
|
+
action: {
|
|
1791
|
+
system: 'coolify',
|
|
1792
|
+
kind: 'app',
|
|
1793
|
+
key: PLATFORM_NAMES_KEY,
|
|
1794
|
+
desired: { hostname: platform.names.hostname, gitRef: platform.names.gitRef },
|
|
1795
|
+
},
|
|
1796
|
+
step: {
|
|
1797
|
+
description: `provision the names host at ${platform.names.hostname}, with its own database and foundry's platform key`,
|
|
1798
|
+
destroys: false,
|
|
1799
|
+
migrates: false,
|
|
1800
|
+
},
|
|
1801
|
+
},
|
|
1802
|
+
blocked: null,
|
|
1803
|
+
};
|
|
1804
|
+
}
|
|
1634
1805
|
/**
|
|
1635
1806
|
* #172: the platform's AI host (https://github.com/wtfalch/ai) -- a Coolify
|
|
1636
1807
|
* Postgres plus three apps built from that repo's root Dockerfile, one per
|
|
@@ -1765,6 +1936,15 @@ function planArchon(platform, recordedIds, executorBlocked, authReady, drifted)
|
|
|
1765
1936
|
recordedIds[PLATFORM_AUTH_BROKER_SCOPE_KEY] &&
|
|
1766
1937
|
(recordedIds[PLATFORM_ARCHON_APPLIED_OPERATOR_SCOPE_KEY] ?? 'none') ===
|
|
1767
1938
|
(archonBootstrapOperator(recordedIds) ?? 'none') &&
|
|
1939
|
+
// #475: a platform applied before the rolling-update fix keeps planning
|
|
1940
|
+
// this step until a confirmed redeploy has cleared it. #480: this marker
|
|
1941
|
+
// alone does not catch the live app's settings changing back afterward
|
|
1942
|
+
// (the consistent-name flag turned on, the alias removed, the health
|
|
1943
|
+
// check disabled) -- `driftedSteps`' `archonRollingUpdateDrifted` catches
|
|
1944
|
+
// that by re-reading the live app, and sets `drifted` below, which plans
|
|
1945
|
+
// the step again the same way an already-`applied` step does for any
|
|
1946
|
+
// other drift reason.
|
|
1947
|
+
recordedIds[PLATFORM_ARCHON_APPLIED_ROLLING_UPDATE_SCOPE_KEY] === '1' &&
|
|
1768
1948
|
!recordedIds[PLATFORM_ARCHON_GATE_CLOSED_SCOPE_KEY] &&
|
|
1769
1949
|
// #398: a rotated integrations key id Archon's env was not yet written
|
|
1770
1950
|
// with (see PLATFORM_ARCHON_INTEGRATIONS_KEY_SYNCED_SCOPE_KEY). Absent
|
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
import type { DeclaredPlatform } from '../platform.js';
|
|
2
2
|
import type { AgoraHostValues } from './agora.js';
|
|
3
|
+
/**
|
|
4
|
+
* #443: the no-domain company mail apex -- `<slug>.oikos.host`, the same
|
|
5
|
+
* fixed suffix `agora.ts`'s `agoraHostnameFor`, `boule.ts`'s
|
|
6
|
+
* `bouleHostnameFor` and `hostnames.ts`'s own `<slug>.oikos.host` already
|
|
7
|
+
* use for a level-1 org's hostnames. `OrgPlatformReadiness.mailHost.apex`
|
|
8
|
+
* used to be `platform.apex` (the platform's own brand apex, e.g.
|
|
9
|
+
* `companyincluded.com`), which gave a domainless org `slug@companyincluded.com`
|
|
10
|
+
* instead of the settled `slug@oikos.host` contract
|
|
11
|
+
* (`@wtfalch/email`'s `org-mail.ts`'s `resolveDomain` is the only reader of
|
|
12
|
+
* this `apex`, for a level-1 org alone -- a with-domain org returns
|
|
13
|
+
* `org.domain` and never looks at it). `mailHost` itself (the MX server's own
|
|
14
|
+
* hostname, `platformMailMxHost(platform.apex)`) is unaffected: the shared
|
|
15
|
+
* mail server stays the platform's own, whichever apex a mailbox's address
|
|
16
|
+
* uses.
|
|
17
|
+
*/
|
|
18
|
+
export declare const NO_DOMAIN_MAIL_APEX = "oikos.host";
|
|
3
19
|
/**
|
|
4
20
|
* https://github.com/wtfalch/foundry/issues/177: what an org step needs to
|
|
5
21
|
* know about the platform it runs on -- whether each shared service it
|
|
@@ -30,6 +46,21 @@ export interface OrgPlatformReadiness {
|
|
|
30
46
|
apex: string;
|
|
31
47
|
mailHost: string;
|
|
32
48
|
} | null;
|
|
49
|
+
/**
|
|
50
|
+
* #491 (review round 2): `DeclaredPlatform.apex` itself (not `mailHost.apex`
|
|
51
|
+
* above, which is the fixed domainless-org fallback, {@link
|
|
52
|
+
* NO_DOMAIN_MAIL_APEX}) -- `null` only when no platform is declared at all.
|
|
53
|
+
* Deliberately *not* folded into `mailHost` or gated on mail being enabled
|
|
54
|
+
* or applied: an org's `coolify:app` step needs to recognise the platform's
|
|
55
|
+
* own org (its mail domain equals this) even while the platform's mail
|
|
56
|
+
* server is off or not yet applied -- the first review round gated this on
|
|
57
|
+
* `mailHost` instead, which let `ensureAppLettermintMail` take the
|
|
58
|
+
* unprotected path the moment platform mail went from applied back to off
|
|
59
|
+
* while `MAIL_DOMAIN_SCOPE_KEY` was still recorded as the apex.
|
|
60
|
+
*/
|
|
61
|
+
platformApex: string | null;
|
|
62
|
+
/** `DeclaredPlatform.mail.lettermintProjectId`, `null` when undeclared or when no platform is declared. See {@link platformApex}'s own doc comment for why this is unconditional too. */
|
|
63
|
+
platformLettermintProjectId: string | null;
|
|
33
64
|
/** The files host (#171). Files is always on for every org (#80), so `false` always blocks rather than only mattering when a flag is set. */
|
|
34
65
|
filesReady: boolean;
|
|
35
66
|
/**
|
|
@@ -56,6 +87,17 @@ export interface OrgPlatformReadiness {
|
|
|
56
87
|
authReady: boolean;
|
|
57
88
|
/** The jobs scheduler (#173) is enabled, applied, and its admin token is held. */
|
|
58
89
|
jobsReady: boolean;
|
|
90
|
+
/**
|
|
91
|
+
* #317 (F7) and #478: the names service is declared `enabled`, its own
|
|
92
|
+
* `coolify:app` deploy step has applied (`provisionPlatformNames`), and
|
|
93
|
+
* the host's executor was built with a `NamesPort` -- the same
|
|
94
|
+
* `filesReady`/`aiReady` "declared and applied" shape now that #478 built
|
|
95
|
+
* the deploy step #317 shipped ahead of (this field's own doc comment
|
|
96
|
+
* used to say there was none; N1 is no longer deployed by hand).
|
|
97
|
+
*/
|
|
98
|
+
namesReady: boolean;
|
|
99
|
+
/** `https://<platform.names.hostname>`, which an entry with `uses.names` gets as `NAMES_URL`. `null` until `namesReady`. */
|
|
100
|
+
namesUrl?: string | null;
|
|
59
101
|
/**
|
|
60
102
|
* The ref of a platform key an applied service needs that is not held yet,
|
|
61
103
|
* `null` when it is held or when the service is not applied. A ref only,
|
|
@@ -123,7 +165,9 @@ export declare function orgPlatformReadinessFrom(platform: DeclaredPlatform, pla
|
|
|
123
165
|
*/
|
|
124
166
|
heldRefs?: ReadonlySet<string>,
|
|
125
167
|
/** True when the host wired no held-keys reader ({@link OrgPlatformReadiness.noKeyReader}). */
|
|
126
|
-
noKeyReader?: boolean
|
|
168
|
+
noKeyReader?: boolean,
|
|
169
|
+
/** #317: true when the host's executor was built with a `NamesPort` ({@link OrgPlatformReadiness.namesReady}). */
|
|
170
|
+
namesPortPresent?: boolean): OrgPlatformReadiness;
|
|
127
171
|
/**
|
|
128
172
|
* #177: {@link AgoraHostValues} derived from the same platform state, rather
|
|
129
173
|
* than requiring a bespoke host-supplied `AgoraHostPort` for values the
|
|
@@ -1,5 +1,21 @@
|
|
|
1
|
-
import { JOBS_SCHEDULER_KEY, PLATFORM_AI_INTERNAL_URL_SCOPE_KEY, PLATFORM_AI_KEY, PLATFORM_AUTH_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';
|
|
1
|
+
import { JOBS_SCHEDULER_KEY, PLATFORM_AI_INTERNAL_URL_SCOPE_KEY, PLATFORM_AI_KEY, PLATFORM_AUTH_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, PLATFORM_NAMES_KEY, platformMailMxHost, } from './platform-diff.js';
|
|
2
2
|
import { FILES_MANAGEMENT_KEY_REF, JOBS_ADMIN_TOKEN_REF, PLATFORM_AI_MANAGEMENT_KEY_REF, } from './platform-providers.js';
|
|
3
|
+
/**
|
|
4
|
+
* #443: the no-domain company mail apex -- `<slug>.oikos.host`, the same
|
|
5
|
+
* fixed suffix `agora.ts`'s `agoraHostnameFor`, `boule.ts`'s
|
|
6
|
+
* `bouleHostnameFor` and `hostnames.ts`'s own `<slug>.oikos.host` already
|
|
7
|
+
* use for a level-1 org's hostnames. `OrgPlatformReadiness.mailHost.apex`
|
|
8
|
+
* used to be `platform.apex` (the platform's own brand apex, e.g.
|
|
9
|
+
* `companyincluded.com`), which gave a domainless org `slug@companyincluded.com`
|
|
10
|
+
* instead of the settled `slug@oikos.host` contract
|
|
11
|
+
* (`@wtfalch/email`'s `org-mail.ts`'s `resolveDomain` is the only reader of
|
|
12
|
+
* this `apex`, for a level-1 org alone -- a with-domain org returns
|
|
13
|
+
* `org.domain` and never looks at it). `mailHost` itself (the MX server's own
|
|
14
|
+
* hostname, `platformMailMxHost(platform.apex)`) is unaffected: the shared
|
|
15
|
+
* mail server stays the platform's own, whichever apex a mailbox's address
|
|
16
|
+
* uses.
|
|
17
|
+
*/
|
|
18
|
+
export const NO_DOMAIN_MAIL_APEX = 'oikos.host';
|
|
3
19
|
/**
|
|
4
20
|
* What an org's plan reads when no platform is declared: nothing is ready, so
|
|
5
21
|
* every org step that depends on a platform service is blocked (#177). A host
|
|
@@ -9,11 +25,14 @@ import { FILES_MANAGEMENT_KEY_REF, JOBS_ADMIN_TOKEN_REF, PLATFORM_AI_MANAGEMENT_
|
|
|
9
25
|
*/
|
|
10
26
|
export const DEFAULT_ORG_PLATFORM_READINESS = {
|
|
11
27
|
mailHost: null,
|
|
28
|
+
platformApex: null,
|
|
29
|
+
platformLettermintProjectId: null,
|
|
12
30
|
filesReady: false,
|
|
13
31
|
aiReady: false,
|
|
14
32
|
callsReady: false,
|
|
15
33
|
authReady: false,
|
|
16
34
|
jobsReady: false,
|
|
35
|
+
namesReady: false,
|
|
17
36
|
missingKeys: { files: null, ai: null, jobs: null },
|
|
18
37
|
};
|
|
19
38
|
/** The held-key refs an org step needs from the platform, one per service. */
|
|
@@ -60,11 +79,14 @@ export function orgPlatformReadinessFrom(platform, platformRecordedIds,
|
|
|
60
79
|
*/
|
|
61
80
|
heldRefs,
|
|
62
81
|
/** True when the host wired no held-keys reader ({@link OrgPlatformReadiness.noKeyReader}). */
|
|
63
|
-
noKeyReader = false
|
|
82
|
+
noKeyReader = false,
|
|
83
|
+
/** #317: true when the host's executor was built with a `NamesPort` ({@link OrgPlatformReadiness.namesReady}). */
|
|
84
|
+
namesPortPresent = false) {
|
|
64
85
|
const mailApplied = platform.mail.enabled && coolifyAppApplied(platformRecordedIds, PLATFORM_MAIL_KEY);
|
|
65
86
|
const filesApplied = platform.files.enabled && coolifyAppApplied(platformRecordedIds, PLATFORM_FILES_KEY);
|
|
66
87
|
const aiApplied = platform.ai.enabled && coolifyAppApplied(platformRecordedIds, PLATFORM_AI_KEY);
|
|
67
88
|
const jobsApplied = platform.jobs.enabled && Boolean(platformRecordedIds[`coolify:jobs:${JOBS_SCHEDULER_KEY}`]);
|
|
89
|
+
const namesApplied = platform.names.enabled && coolifyAppApplied(platformRecordedIds, PLATFORM_NAMES_KEY);
|
|
68
90
|
const missing = (applied, ref) => applied && heldRefs !== undefined && !heldRefs.has(ref) ? ref : null;
|
|
69
91
|
const missingKeys = {
|
|
70
92
|
files: missing(filesApplied, ORG_PLATFORM_KEY_REFS.files),
|
|
@@ -73,8 +95,15 @@ noKeyReader = false) {
|
|
|
73
95
|
};
|
|
74
96
|
return {
|
|
75
97
|
mailHost: mailApplied
|
|
76
|
-
? { apex:
|
|
98
|
+
? { apex: NO_DOMAIN_MAIL_APEX, mailHost: platformMailMxHost(platform.apex) }
|
|
77
99
|
: null,
|
|
100
|
+
// #491 (review round 2): unconditional -- `platform` here is always a
|
|
101
|
+
// real declared platform (this function's own doc comment: "the platform
|
|
102
|
+
// is either declared ... or this is never called"), so its apex and its
|
|
103
|
+
// mail.lettermintProjectId are facts regardless of whether mail itself is
|
|
104
|
+
// enabled or applied.
|
|
105
|
+
platformApex: platform.apex,
|
|
106
|
+
platformLettermintProjectId: platform.mail.lettermintProjectId ?? null,
|
|
78
107
|
filesReady: filesApplied && missingKeys.files === null,
|
|
79
108
|
filesUrl: filesApplied
|
|
80
109
|
? (platformRecordedIds[PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY] ?? null)
|
|
@@ -84,6 +113,8 @@ noKeyReader = false) {
|
|
|
84
113
|
callsReady: platform.sfu.enabled,
|
|
85
114
|
authReady: platform.auth.enabled && coolifyAppApplied(platformRecordedIds, PLATFORM_AUTH_KEY),
|
|
86
115
|
jobsReady: jobsApplied && missingKeys.jobs === null,
|
|
116
|
+
namesReady: namesApplied && namesPortPresent,
|
|
117
|
+
namesUrl: namesApplied && namesPortPresent ? `https://${platform.names.hostname}` : null,
|
|
87
118
|
missingKeys,
|
|
88
119
|
...(noKeyReader ? { noKeyReader } : {}),
|
|
89
120
|
};
|