@wtfalch/foundry 0.9.0 → 0.11.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/apply.d.ts +16 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.js +1 -1
- package/dist/migrations/0030_hosting_entry_hostnames.sql +24 -0
- package/dist/migrations/0031_adoptions.sql +37 -0
- package/dist/org.d.ts +49 -4
- package/dist/org.js +134 -1
- package/dist/plan/adopt.d.ts +112 -0
- package/dist/plan/adopt.js +252 -0
- package/dist/plan/adoption-vault.d.ts +9 -0
- package/dist/plan/adoption-vault.js +1 -0
- package/dist/plan/agora.d.ts +7 -0
- package/dist/plan/agora.js +9 -0
- package/dist/plan/broker-entries.d.ts +108 -0
- package/dist/plan/broker-entries.js +214 -0
- package/dist/plan/cloudflared-connector.d.ts +10 -2
- package/dist/plan/cloudflared-connector.js +24 -16
- package/dist/plan/diff.d.ts +14 -0
- package/dist/plan/diff.js +488 -47
- package/dist/plan/executor.d.ts +141 -3
- package/dist/plan/executor.js +1100 -149
- package/dist/plan/hostnames.d.ts +24 -0
- package/dist/plan/hostnames.js +56 -0
- package/dist/plan/index.d.ts +2 -0
- package/dist/plan/index.js +1 -0
- package/dist/plan/locked-hostnames.d.ts +8 -0
- package/dist/plan/locked-hostnames.js +26 -0
- package/dist/plan/login-host.d.ts +1 -1
- package/dist/plan/login-host.js +5 -2
- package/dist/plan/plan-apply.d.ts +63 -3
- package/dist/plan/plan-apply.js +163 -5
- package/dist/plan/platform-diff.d.ts +66 -13
- package/dist/plan/platform-diff.js +117 -15
- package/dist/plan/platform-providers.d.ts +95 -2
- package/dist/plan/platform-providers.js +393 -27
- package/dist/plan/providers-readers.d.ts +8 -1
- package/dist/plan/providers-readers.js +58 -4
- package/dist/plan.d.ts +6 -0
- package/dist/store/postgres.d.ts +6 -1
- package/dist/store/postgres.js +80 -5
- package/dist/store/schema.d.ts +162 -0
- package/dist/store/schema.js +19 -1
- package/dist/store.d.ts +37 -0
- package/package.json +1 -1
package/dist/plan/executor.d.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { OrgMailOptions, OrgMailOrg, OrgMailResult } from '@wtfalch/email/postmaster';
|
|
2
2
|
import type { DeclaredOrg, HostingBuildPack } from '../org.js';
|
|
3
|
-
import type {
|
|
3
|
+
import type { UnmanagedItem } from '../plan.js';
|
|
4
|
+
import type { Adoption, AdoptionStore, DeclaredOrgStore } from '../store.js';
|
|
5
|
+
import { type IngressRoute } from './adopt.js';
|
|
6
|
+
import type { AdoptionVault } from './adoption-vault.js';
|
|
7
|
+
import { type BrokerPort } from './broker-entries.js';
|
|
4
8
|
import type { StepAction } from './diff.js';
|
|
5
9
|
import { type LoginHostBindingsPort } from './login-host.js';
|
|
6
10
|
import { type MailCopyPort } from './mail-copy.js';
|
|
@@ -97,6 +101,19 @@ export interface CoolifyPort {
|
|
|
97
101
|
domains?: string;
|
|
98
102
|
/** Selects which of two Dockerfiles in one repo to build (`cmsSite`'s own site/cms apps). */
|
|
99
103
|
dockerfile_location?: string;
|
|
104
|
+
/** The port the app listens on (`HostingEntry.port`). */
|
|
105
|
+
ports_exposes?: string;
|
|
106
|
+
/** `false` for an internal entry: Coolify makes no domain for it. */
|
|
107
|
+
autogenerate_domain?: boolean;
|
|
108
|
+
/** The folder a `static` entry serves (`HostingEntry.folder`). */
|
|
109
|
+
publish_directory?: string;
|
|
110
|
+
/** Whether a push to `git_branch` deploys (`HostingEntry.autoDeploy`). */
|
|
111
|
+
is_auto_deploy_enabled?: boolean;
|
|
112
|
+
/**
|
|
113
|
+
* The app's folder in the repo (`HostingEntry.folder`). Coolify's create
|
|
114
|
+
* endpoint takes it; `@wtfalch/providers` forwards the body as it is.
|
|
115
|
+
*/
|
|
116
|
+
base_directory?: string;
|
|
100
117
|
}): Promise<{
|
|
101
118
|
uuid: string;
|
|
102
119
|
}>;
|
|
@@ -167,9 +184,13 @@ export interface CoolifyPort {
|
|
|
167
184
|
* `is_preview === false` the same way a deployed-config reader must.
|
|
168
185
|
*/
|
|
169
186
|
listEnvs(applicationUuid: string): Promise<Array<{
|
|
187
|
+
uuid?: string;
|
|
170
188
|
key: string;
|
|
171
189
|
value: string;
|
|
172
190
|
is_preview: boolean;
|
|
191
|
+
is_buildtime?: boolean;
|
|
192
|
+
is_literal?: boolean;
|
|
193
|
+
is_multiline?: boolean;
|
|
173
194
|
}>>;
|
|
174
195
|
/** Escape hatch (`CoolifyClient.raw`) — used for stop/start, which have no typed method yet. */
|
|
175
196
|
raw(method: string, path: string, body?: unknown): Promise<unknown>;
|
|
@@ -316,12 +337,20 @@ export interface CoolifyPort {
|
|
|
316
337
|
* `CoolifyClient.listDatabaseBackups` method-for-method.
|
|
317
338
|
* `provisionPlatformBackup` reads this to decide whether a database
|
|
318
339
|
* already has an enabled, `save_s3` schedule before creating a second one.
|
|
340
|
+
* `s3_storage_uuid` (#257 fix round 3) is the field a replaced storage's
|
|
341
|
+
* repoint compares against -- Coolify's "Update database backup" operation
|
|
342
|
+
* (https://coolify.io/docs/api-reference/api/operations/update-database-backup)
|
|
343
|
+
* documents it as a field of the same scheduled-backup resource this list
|
|
344
|
+
* returns, but the list endpoint's own response has not been checked
|
|
345
|
+
* against a live instance, so it is optional here rather than assumed
|
|
346
|
+
* present.
|
|
319
347
|
*/
|
|
320
348
|
listDatabaseBackups(databaseUuid: string): Promise<Array<{
|
|
321
349
|
uuid: string;
|
|
322
350
|
frequency: string;
|
|
323
351
|
enabled: boolean;
|
|
324
352
|
save_s3: boolean;
|
|
353
|
+
s3_storage_uuid?: string;
|
|
325
354
|
}>>;
|
|
326
355
|
/**
|
|
327
356
|
* #257: schedules dumps of a database to the registered S3 storage --
|
|
@@ -358,6 +387,18 @@ export interface CloudflarePort {
|
|
|
358
387
|
name: string;
|
|
359
388
|
}>;
|
|
360
389
|
putTunnelConfig(tunnelId: string, config: unknown): Promise<unknown>;
|
|
390
|
+
/**
|
|
391
|
+
* #312: the tunnel's live configuration, `config` null for a tunnel never
|
|
392
|
+
* configured. Matches `@wtfalch/providers`' `CloudflareClient.getTunnelConfig`.
|
|
393
|
+
* `cloudflareTunnel` reads it on every apply of an adopted tunnel, to merge
|
|
394
|
+
* its routes into the live ingress list rather than overwrite it.
|
|
395
|
+
*/
|
|
396
|
+
getTunnelConfig(tunnelId: string): Promise<{
|
|
397
|
+
config: {
|
|
398
|
+
ingress: IngressRoute[];
|
|
399
|
+
[field: string]: unknown;
|
|
400
|
+
} | null;
|
|
401
|
+
}>;
|
|
361
402
|
/**
|
|
362
403
|
* The tunnel's connector token (#197) -- a `cloudflared` container reads
|
|
363
404
|
* this at boot to dial into the tunnel. A live credential: matches
|
|
@@ -577,14 +618,17 @@ export interface MailPort {
|
|
|
577
618
|
* (#162). The host implements this with `@wtfalch/email/postmaster`'s own
|
|
578
619
|
* `createMailbox`, resolving `org`'s domain to a `domainId` the same way
|
|
579
620
|
* `applyOrgMail` does -- that function already refuses a duplicate `local`
|
|
580
|
-
* on the resolved domain, so this port method never has to. `
|
|
621
|
+
* on the resolved domain, so this port method never has to. `options` is
|
|
622
|
+
* the same `{ apex, mailHost }` `applyOrgMail`/`parkOrgMail` take, needed
|
|
623
|
+
* for the same reason: `@wtfalch/email/postmaster`'s `createMailbox` takes
|
|
624
|
+
* it to resolve the shared mail apex (#335). `ensureOwnerMailbox`
|
|
581
625
|
* and `ensureRoleMailboxes` (#230) below are this executor's callers, and each checks its own recorded scope
|
|
582
626
|
* id first, so a second apply never calls this at all.
|
|
583
627
|
*/
|
|
584
628
|
createMailbox(org: OrgMailOrg, mailbox: {
|
|
585
629
|
address: string;
|
|
586
630
|
name: string;
|
|
587
|
-
}): Promise<void>;
|
|
631
|
+
}, options: OrgMailOptions): Promise<void>;
|
|
588
632
|
/**
|
|
589
633
|
* Upserts this org's own outbound relay route on Stalwart (#149): an
|
|
590
634
|
* `MtaRoute` through Lettermint, restricted to `org`'s domain, keyed by
|
|
@@ -1134,6 +1178,13 @@ export interface FoundryStepExecutorPorts {
|
|
|
1134
1178
|
* or repointed without the binding.
|
|
1135
1179
|
*/
|
|
1136
1180
|
loginHostBindings?: LoginHostBindingsPort;
|
|
1181
|
+
/**
|
|
1182
|
+
* F5: the broker's `AUTH_APPS`, where an entry with `uses.auth` gets its own
|
|
1183
|
+
* entry. Optional the same way `loginHostBindings` is: without it the plan
|
|
1184
|
+
* reports each broker entry blocked (the host also sets
|
|
1185
|
+
* `FoundryPlanApplyOptions.broker`).
|
|
1186
|
+
*/
|
|
1187
|
+
broker?: BrokerPort;
|
|
1137
1188
|
/**
|
|
1138
1189
|
* Hands a just-minted, one-time secret (today: an API app's
|
|
1139
1190
|
* `clientSecret`) to the host, immediately after the provider returns it
|
|
@@ -1156,6 +1207,17 @@ export interface FoundryStepExecutorPorts {
|
|
|
1156
1207
|
* passes the same store instance it constructs `FoundryPlanApply` with.
|
|
1157
1208
|
*/
|
|
1158
1209
|
declaredOrgStore: Pick<DeclaredOrgStore, 'findOrgIdForZone'>;
|
|
1210
|
+
/**
|
|
1211
|
+
* #311: where an adoption keeps the env values of the live app it takes
|
|
1212
|
+
* over. Optional with `adoptionStore`: without both, the planner reports
|
|
1213
|
+
* adoption as blocked and plans no `coolify:adopt` step
|
|
1214
|
+
* (`FoundryPlanApplyOptions`).
|
|
1215
|
+
*/
|
|
1216
|
+
adoptionVault?: AdoptionVault;
|
|
1217
|
+
/** #311: the rows that hold the non-secret part of each snapshot. */
|
|
1218
|
+
adoptionStore?: AdoptionStore;
|
|
1219
|
+
/** #311: the clock of a snapshot's `takenAt`; the host passes `FoundryPlanApply`'s own. Defaults to the real clock. */
|
|
1220
|
+
now?: () => Date;
|
|
1159
1221
|
}
|
|
1160
1222
|
/**
|
|
1161
1223
|
* Thrown by name for a step this executor deliberately does not implement:
|
|
@@ -1326,6 +1388,49 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
|
|
|
1326
1388
|
private ensureZitadelProject;
|
|
1327
1389
|
private ensureCoolifyProject;
|
|
1328
1390
|
private resolveServer;
|
|
1391
|
+
/** The live app Coolify holds under `uuid`. The error names the id only (rule 6). */
|
|
1392
|
+
private readLiveApp;
|
|
1393
|
+
/** The live ingress routes of the adopted tunnel `id`. The error names the id only (rule 6). */
|
|
1394
|
+
private readAdoptedIngress;
|
|
1395
|
+
/**
|
|
1396
|
+
* #311: for each adopted resource, the lines its `coolify:adopt` step shows
|
|
1397
|
+
* (a setting with old value beside new; an env var by name only) and what
|
|
1398
|
+
* is blocked. Read from the live app on every plan. No value of an env var
|
|
1399
|
+
* leaves this method (rule 6).
|
|
1400
|
+
*/
|
|
1401
|
+
adoptionReport(org: DeclaredOrg, recorded: Readonly<Record<string, string>>): Promise<{
|
|
1402
|
+
details: Record<string, string[]>;
|
|
1403
|
+
blocked: UnmanagedItem[];
|
|
1404
|
+
withheld: string[];
|
|
1405
|
+
}>;
|
|
1406
|
+
/**
|
|
1407
|
+
* #311: puts each adopted app back as its snapshot recorded it, touching
|
|
1408
|
+
* only what foundry writes: the settings it patches and the env names it
|
|
1409
|
+
* owns (an edit a person made to anything else stays). Owned names the
|
|
1410
|
+
* snapshot has are written back with its value and flags; owned names it
|
|
1411
|
+
* lacks are deleted; preview rows are left alone. Every vault entry and
|
|
1412
|
+
* live read happens first, so a missing entry or a row that cannot be
|
|
1413
|
+
* deleted refuses before any Coolify write. Errors name ids only, and the
|
|
1414
|
+
* vault's env values go to `bulkUpsertEnvs` and nowhere else (rule 6).
|
|
1415
|
+
*/
|
|
1416
|
+
restoreAdoption(org: DeclaredOrg, recorded: Readonly<Record<string, string>>, rows: readonly Adoption[]): Promise<{
|
|
1417
|
+
envDeleted: string[];
|
|
1418
|
+
tokenLeft: string[];
|
|
1419
|
+
}>;
|
|
1420
|
+
/**
|
|
1421
|
+
* #311: records what a live app or database looked like before foundry
|
|
1422
|
+
* first writes to it, in this order: the env values to the vault, the
|
|
1423
|
+
* non-secret settings to the row, then both read back and compared. The
|
|
1424
|
+
* step writes nothing to Coolify, so a snapshot that fails stops the apply
|
|
1425
|
+
* before any later step does. Every error names ids only (rule 6).
|
|
1426
|
+
*/
|
|
1427
|
+
private coolifyAdopt;
|
|
1428
|
+
/** #311: whether the resource was adopted: its row is `adopted` or `confirmed`, or `id` is a declared adopt uuid. */
|
|
1429
|
+
private isAdopted;
|
|
1430
|
+
/** #311: refuses to manage as foundry's own a resource whose row protects its key and whose declaration has no `adopt`. */
|
|
1431
|
+
private refuseTakeover;
|
|
1432
|
+
/** #311: refuses to write to a declared adopt uuid that has no active snapshot. */
|
|
1433
|
+
private requireSnapshot;
|
|
1329
1434
|
private coolifyApp;
|
|
1330
1435
|
/**
|
|
1331
1436
|
* #75: mounts a persistent volume at {@link LOG_DIR} on the app, unless one
|
|
@@ -1401,6 +1506,21 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
|
|
|
1401
1506
|
* provisioned, where this one is unconditional (#239).
|
|
1402
1507
|
*/
|
|
1403
1508
|
private pushAppEnv;
|
|
1509
|
+
/**
|
|
1510
|
+
* F5: the app's `AUTH_APP_KEY`, minted once and only when the app has none
|
|
1511
|
+
* of at least 32 characters (the broker refuses a shorter one). `null` once
|
|
1512
|
+
* it has one: the app's env is where the key lives, and
|
|
1513
|
+
* `coolifyBrokerEntry` reads it back from there. It goes to this env and
|
|
1514
|
+
* the broker's, nowhere else.
|
|
1515
|
+
*/
|
|
1516
|
+
private ensureAuthAppKey;
|
|
1517
|
+
/**
|
|
1518
|
+
* `plan/diff.ts`'s `planBrokerEntries` (F5): the entry's `AUTH_APPS` entry,
|
|
1519
|
+
* written by `writeBrokerEntry` with the key `pushAppEnv` put in the app's
|
|
1520
|
+
* env. Records what it wrote (never the key) and any foreign entry the
|
|
1521
|
+
* broker would drop.
|
|
1522
|
+
*/
|
|
1523
|
+
private coolifyBrokerEntry;
|
|
1404
1524
|
/**
|
|
1405
1525
|
* `null` once the app already has one -- `AUTH_COOKIE_SECRET` is generated
|
|
1406
1526
|
* only the first time (task brief), never regenerated or overwritten, so a
|
|
@@ -1774,6 +1894,24 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
|
|
|
1774
1894
|
/** `deliverSecret`, tried up to {@link JOBS_DELIVERY_ATTEMPTS} times; true once one attempt succeeds. */
|
|
1775
1895
|
private deliverWithRetry;
|
|
1776
1896
|
private cloudflareTunnel;
|
|
1897
|
+
/**
|
|
1898
|
+
* Runs `call`; for an adopted resource a failure that is not one of this
|
|
1899
|
+
* file's own messages (which name hostnames and record types only) is
|
|
1900
|
+
* replaced by `label`, because a client's error text carries response
|
|
1901
|
+
* bodies (rule 6, #312).
|
|
1902
|
+
*/
|
|
1903
|
+
private guarded;
|
|
1904
|
+
/**
|
|
1905
|
+
* #312: writes the org's routes into the adopted tunnel's LIVE ingress list
|
|
1906
|
+
* (`mergeIngress`), not the snapshot, so a route a person added since is
|
|
1907
|
+
* kept too. The declared names go into the tunnel row's `written` only after
|
|
1908
|
+
* the write that carried them succeeded, so a crash in between leaves a
|
|
1909
|
+
* route taken as foreign and kept. Writes nothing when the merged list is the
|
|
1910
|
+
* live one. Errors name the tunnel id and the operation only.
|
|
1911
|
+
*/
|
|
1912
|
+
private mergeAdoptedIngress;
|
|
1913
|
+
/** #312: replaces the `settings` of an adoption row, read fresh so no other key is lost. The state and `takenAt` stay. */
|
|
1914
|
+
private updateAdoptionSettings;
|
|
1777
1915
|
/**
|
|
1778
1916
|
* #236: when this tunnel's DNS was last published under a domain that
|
|
1779
1917
|
* differs from the one `org` now declares, deletes every CNAME in the
|