@wtfalch/foundry 0.7.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bootstrap/bootstrap.d.ts +12 -8
- package/dist/bootstrap/bootstrap.js +4 -12
- package/dist/bootstrap/versions.d.ts +8 -0
- package/dist/bootstrap/versions.js +31 -0
- package/dist/calls-sfu-deploy-support.d.ts +1 -1
- package/dist/calls-sfu-deploy-support.js +1 -1
- package/dist/calls-sfu-ssh-adapter.d.ts +1 -1
- package/dist/calls-sfu-ssh-adapter.js +1 -1
- package/dist/calls-sfu.d.ts +5 -4
- package/dist/calls-sfu.js +5 -4
- package/dist/index.d.ts +1 -2
- package/dist/index.js +4 -1
- package/dist/migrations/0024_audit_events.sql +1 -1
- package/dist/migrations/0025_audit_display.sql +1 -1
- package/dist/migrations/0026_audit_erase_email_guard.sql +1 -1
- package/dist/migrations/0027_audit_chain.sql +1 -1
- package/dist/migrations/0028_audit_rls.sql +2 -2
- package/dist/migrations-dir.d.ts +9 -0
- package/dist/migrations-dir.js +10 -0
- package/dist/plan/calls-matrix.d.ts +2 -0
- package/dist/plan/calls-matrix.js +2 -0
- package/dist/plan/diff.d.ts +3 -1
- package/dist/plan/diff.js +76 -14
- package/dist/plan/executor.d.ts +305 -16
- package/dist/plan/executor.js +606 -9
- package/dist/plan/login-host.d.ts +24 -0
- package/dist/plan/login-host.js +21 -0
- package/dist/plan/plan-apply.d.ts +2 -0
- package/dist/plan/plan-apply.js +20 -1
- package/dist/plan/platform-diff.d.ts +79 -1
- package/dist/plan/platform-diff.js +197 -1
- package/dist/plan/platform-providers.d.ts +36 -1
- package/dist/plan/platform-providers.js +153 -21
- package/dist/plan.d.ts +43 -0
- package/dist/plan.js +16 -1
- package/dist/store/audit.d.ts +13 -17
- package/dist/store/audit.js +14 -18
- package/dist/store/postgres.d.ts +14 -0
- package/dist/store/postgres.js +13 -8
- package/package.json +6 -3
- package/versions.json +1 -1
- package/dist/migrate.d.ts +0 -38
- package/dist/migrate.js +0 -47
package/dist/plan/executor.d.ts
CHANGED
|
@@ -282,6 +282,59 @@ export interface CoolifyPort {
|
|
|
282
282
|
* deploy` method-for-method.
|
|
283
283
|
*/
|
|
284
284
|
deploy(uuid: string, force?: boolean): Promise<unknown>;
|
|
285
|
+
/**
|
|
286
|
+
* #257: every S3-compatible storage registered for the team -- matches
|
|
287
|
+
* `CoolifyClient.listS3Storages` method-for-method. `provisionPlatformBackup`
|
|
288
|
+
* (`platform-providers.ts`) uses this to find the platform's shared R2
|
|
289
|
+
* bucket by name before registering a second one.
|
|
290
|
+
*/
|
|
291
|
+
listS3Storages(): Promise<Array<{
|
|
292
|
+
uuid: string;
|
|
293
|
+
name: string;
|
|
294
|
+
bucket: string;
|
|
295
|
+
endpoint: string;
|
|
296
|
+
is_usable?: boolean;
|
|
297
|
+
}>>;
|
|
298
|
+
/**
|
|
299
|
+
* #257: registers a bucket as a Coolify team S3 storage -- matches
|
|
300
|
+
* `CoolifyClient.createS3Storage` method-for-method.
|
|
301
|
+
*/
|
|
302
|
+
createS3Storage(input: {
|
|
303
|
+
name: string;
|
|
304
|
+
description?: string;
|
|
305
|
+
endpoint: string;
|
|
306
|
+
bucket: string;
|
|
307
|
+
region: string;
|
|
308
|
+
key: string;
|
|
309
|
+
secret: string;
|
|
310
|
+
is_usable?: boolean;
|
|
311
|
+
}): Promise<{
|
|
312
|
+
uuid: string;
|
|
313
|
+
}>;
|
|
314
|
+
/**
|
|
315
|
+
* #257: the scheduled backups of one database -- matches
|
|
316
|
+
* `CoolifyClient.listDatabaseBackups` method-for-method.
|
|
317
|
+
* `provisionPlatformBackup` reads this to decide whether a database
|
|
318
|
+
* already has an enabled, `save_s3` schedule before creating a second one.
|
|
319
|
+
*/
|
|
320
|
+
listDatabaseBackups(databaseUuid: string): Promise<Array<{
|
|
321
|
+
uuid: string;
|
|
322
|
+
frequency: string;
|
|
323
|
+
enabled: boolean;
|
|
324
|
+
save_s3: boolean;
|
|
325
|
+
}>>;
|
|
326
|
+
/**
|
|
327
|
+
* #257: schedules dumps of a database to the registered S3 storage --
|
|
328
|
+
* matches `CoolifyClient.createDatabaseBackup` method-for-method.
|
|
329
|
+
*/
|
|
330
|
+
createDatabaseBackup(databaseUuid: string, input: {
|
|
331
|
+
frequency: string;
|
|
332
|
+
enabled?: boolean;
|
|
333
|
+
save_s3?: boolean;
|
|
334
|
+
s3_storage_uuid?: string;
|
|
335
|
+
}): Promise<{
|
|
336
|
+
uuid: string;
|
|
337
|
+
}>;
|
|
285
338
|
}
|
|
286
339
|
/**
|
|
287
340
|
* The Cloudflare calls this executor makes, shaped to match
|
|
@@ -344,11 +397,8 @@ export interface CloudflarePort {
|
|
|
344
397
|
deleteAccessApp(appId: string): Promise<void>;
|
|
345
398
|
/**
|
|
346
399
|
* `FoundryPlanApply.destroy`'s teardown of any DNS record foundry created
|
|
347
|
-
* for the org
|
|
348
|
-
*
|
|
349
|
-
* `diff.ts`/`executor.ts` today), so `destroy` has no dispatch target for
|
|
350
|
-
* it either -- this port method exists ready for whichever step starts
|
|
351
|
-
* creating one.
|
|
400
|
+
* for the org -- `destroy`'s `cloudflare:tunnel` case uses it to remove
|
|
401
|
+
* every CNAME {@link publishTunnelDnsRecords} published (#236).
|
|
352
402
|
*/
|
|
353
403
|
deleteDnsRecord(zoneId: string, recordId: string): Promise<void>;
|
|
354
404
|
/** `coolify:mail`'s lookup of an existing MX/TXT record before upserting it, filtered by type so a stale record of a different type is never mistaken for a match. */
|
|
@@ -358,6 +408,27 @@ export interface CloudflarePort {
|
|
|
358
408
|
content: string;
|
|
359
409
|
name: string;
|
|
360
410
|
}>>;
|
|
411
|
+
/**
|
|
412
|
+
* #236: `destroy`'s `cloudflare:tunnel` case and
|
|
413
|
+
* {@link FoundryStepExecutor.deleteStaleTunnelDnsRecords} each fall back to
|
|
414
|
+
* this for a domain recorded before
|
|
415
|
+
* {@link CLOUDFLARE_TUNNEL_DNS_HOSTNAMES_SCOPE_KEY} existed, with no
|
|
416
|
+
* hostname list to look up instead -- the one case left that must still
|
|
417
|
+
* see every CNAME in the zone, not one matched by name. Round 3: this is
|
|
418
|
+
* no longer either caller's normal path, since the real adapter
|
|
419
|
+
* (`@wtfalch/providers`' `cloudflare.ts`) asks for one page of 100 and
|
|
420
|
+
* never walks `result_info.total_pages`, so a zone with more records than
|
|
421
|
+
* that would silently lose everything past the first page.
|
|
422
|
+
*/
|
|
423
|
+
listDnsRecords(zoneId: string, opts?: {
|
|
424
|
+
type?: string;
|
|
425
|
+
name?: string;
|
|
426
|
+
}): Promise<Array<{
|
|
427
|
+
id: string;
|
|
428
|
+
type: string;
|
|
429
|
+
content: string;
|
|
430
|
+
name: string;
|
|
431
|
+
}>>;
|
|
361
432
|
createDnsRecord(zoneId: string, rec: {
|
|
362
433
|
type: string;
|
|
363
434
|
name: string;
|
|
@@ -620,22 +691,56 @@ export interface ZitadelPort {
|
|
|
620
691
|
}>;
|
|
621
692
|
deleteApp(orgId: string, projectId: string, appId: string): Promise<void>;
|
|
622
693
|
/**
|
|
623
|
-
* `zitadelApp`'s kind-change path reads
|
|
694
|
+
* `zitadelApp`'s kind-change path reads `apps` to find an app a prior,
|
|
624
695
|
* lost-response create already made under the new name, before calling
|
|
625
696
|
* `createOidcApp`/`createApiApp` again and orphaning that one under the
|
|
626
|
-
* same name
|
|
627
|
-
* listApps` return shape exactly, so that client satisfies this port with
|
|
628
|
-
* no adapter -- it does not carry `clientId`/`clientSecret` (that provider
|
|
697
|
+
* same name -- it does not carry `clientId`/`clientSecret` (that provider
|
|
629
698
|
* method never parses either, and a secret is minted once, on creation,
|
|
630
699
|
* never handed back again), which is why the reuse path below cannot
|
|
631
700
|
* re-deliver either one.
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
701
|
+
*
|
|
702
|
+
* `loginBaseUri` and `complete` are `zitadelLoginHost`'s own #274
|
|
703
|
+
* additions, neither satisfied by the real (unpublished-ahead-of-pin)
|
|
704
|
+
* `@wtfalch/providers` `ZitadelClient.listApps` yet -- {@link
|
|
705
|
+
* adaptZitadelListApps} below is the adapter for both until a tracked
|
|
706
|
+
* change lands (https://github.com/wtfalch/providers/issues/29):
|
|
707
|
+
*
|
|
708
|
+
* - `loginBaseUri`: each app's current ZITADEL Login V2 base URI, used to
|
|
709
|
+
* decide whether a host a company-login-host change left behind is
|
|
710
|
+
* still in use -- rather than guessing from which apps this step itself
|
|
711
|
+
* repointed. Three states, not two (#274 fix round 4): a `string` is
|
|
712
|
+
* the URI itself; `null` means this call *read* the app and it has
|
|
713
|
+
* never had one set -- common, and never a reason to keep a host
|
|
714
|
+
* pending; `undefined` means this call could not read the field at all
|
|
715
|
+
* (every app, through {@link adaptZitadelListApps}, until the tracked
|
|
716
|
+
* change above lands) and is the only one of the three
|
|
717
|
+
* `zitadelLoginHost` fails a pending host closed over. Collapsing
|
|
718
|
+
* "confirmed unset" into "unreadable" would never let a host clear once
|
|
719
|
+
* an app with no login flow of its own existed, forever, even after the
|
|
720
|
+
* tracked change lands and genuinely-unset apps start reporting `null`.
|
|
721
|
+
* The real client's own `_search` response parsing (`appFrom`)
|
|
722
|
+
* deliberately reads only enough of each app to classify its kind,
|
|
723
|
+
* "everything else... ignored on purpose".
|
|
724
|
+
* - `complete`: whether this call read every one of ZITADEL's own result
|
|
725
|
+
* pages, not only the first -- the real client's `_search` call sends
|
|
726
|
+
* no `query.offset`/`limit` yet, so {@link adaptZitadelListApps} reports
|
|
727
|
+
* `false` until that lands. #274 fix round 3: a result count alone can
|
|
728
|
+
* never answer this (any threshold guessed at the boundary between
|
|
729
|
+
* "every app fit on one page" and "the page was cut off" is
|
|
730
|
+
* indistinguishable from the server's own page size, which nothing here
|
|
731
|
+
* is told), so `zitadelLoginHost` treats `complete: false` as "cannot
|
|
732
|
+
* safely tell" rather than trusting however many apps did come back.
|
|
733
|
+
*/
|
|
734
|
+
listApps(orgId: string, projectId: string): Promise<{
|
|
735
|
+
apps: Array<{
|
|
736
|
+
id: string;
|
|
737
|
+
name: string;
|
|
738
|
+
appType: 'web' | 'native' | 'api';
|
|
739
|
+
state: string;
|
|
740
|
+
loginBaseUri?: string | null;
|
|
741
|
+
}>;
|
|
742
|
+
complete: boolean;
|
|
743
|
+
}>;
|
|
639
744
|
/**
|
|
640
745
|
* The org-owner flow (goal line): find-or-create the human user by email in
|
|
641
746
|
* the org, then have ZITADEL email them an invite to set up a passkey.
|
|
@@ -779,6 +884,17 @@ export interface ZitadelPort {
|
|
|
779
884
|
ensureTrustedDomain(domain: string): Promise<{
|
|
780
885
|
added: boolean;
|
|
781
886
|
}>;
|
|
887
|
+
/**
|
|
888
|
+
* #274: removes a host from the ZITADEL instance's trusted domains
|
|
889
|
+
* (`@wtfalch/providers`' `ZitadelClient.deleteTrustedDomain`) -- the old
|
|
890
|
+
* host a company login host change or an org destroy leaves behind, which
|
|
891
|
+
* {@link ensureTrustedDomain} only ever adds to, never removes from. No
|
|
892
|
+
* idempotency wrapper on the real client's own side (matching `deleteApp`/
|
|
893
|
+
* `deleteOrg`/`deleteProject`): a host already gone throws the same
|
|
894
|
+
* `status`-bearing not-found `isNotFoundError` catches everywhere else in
|
|
895
|
+
* this package.
|
|
896
|
+
*/
|
|
897
|
+
deleteTrustedDomain(domain: string): Promise<void>;
|
|
782
898
|
/**
|
|
783
899
|
* #267: moves an already-created app's Login V2 base URI, read-modify-
|
|
784
900
|
* write (`@wtfalch/providers`' `ZitadelClient.updateOidcAppLoginBaseUri`).
|
|
@@ -787,6 +903,36 @@ export interface ZitadelPort {
|
|
|
787
903
|
*/
|
|
788
904
|
updateOidcAppLoginBaseUri(orgId: string, projectId: string, appId: string, loginBaseUri: string): Promise<void>;
|
|
789
905
|
}
|
|
906
|
+
/**
|
|
907
|
+
* #274 fix round 4: bridges `@wtfalch/providers`' real `ZitadelClient.
|
|
908
|
+
* listApps` -- a plain array, with no `loginBaseUri` or completeness signal
|
|
909
|
+
* -- into {@link ZitadelPort.listApps}'s own shape, so a host wiring that
|
|
910
|
+
* real client in satisfies this port without reaching into `executor.ts`
|
|
911
|
+
* itself to build the wrapper by hand. `complete: false` on every call:
|
|
912
|
+
* the real client's own `_search` request has no pagination on it yet
|
|
913
|
+
* (https://github.com/wtfalch/providers/issues/29), so this adapter cannot
|
|
914
|
+
* promise it read more than one page. `loginBaseUri` comes back `undefined`
|
|
915
|
+
* for every app for the identical reason -- the real client does not read
|
|
916
|
+
* it back at all yet, not "read it back as absent" -- which is exactly the
|
|
917
|
+
* state `zitadelLoginHost`'s fail-closed rule exists for: every pending
|
|
918
|
+
* host stays pending until the tracked change above lands and this adapter
|
|
919
|
+
* (or its replacement) can start reporting `string | null` for real.
|
|
920
|
+
*/
|
|
921
|
+
export declare function adaptZitadelListApps(client: {
|
|
922
|
+
listApps(orgId: string, projectId: string): Promise<ZitadelListAppsRawApp[]>;
|
|
923
|
+
}): ZitadelPort['listApps'];
|
|
924
|
+
/**
|
|
925
|
+
* The real `@wtfalch/providers` `ZitadelClient.listApps`' own return shape
|
|
926
|
+
* (its `ZitadelApp`) -- restated rather than imported, the same rule this
|
|
927
|
+
* package's doc comments already follow wherever a provider type is cited
|
|
928
|
+
* without a runtime dependency on the package that owns it existing yet.
|
|
929
|
+
*/
|
|
930
|
+
export interface ZitadelListAppsRawApp {
|
|
931
|
+
id: string;
|
|
932
|
+
name: string;
|
|
933
|
+
appType: 'web' | 'native' | 'api';
|
|
934
|
+
state: string;
|
|
935
|
+
}
|
|
790
936
|
/**
|
|
791
937
|
* Provisions one org's organisation on Files (#80), matching
|
|
792
938
|
* `files/packages/sdk/src/client.ts:136-145`'s
|
|
@@ -1095,6 +1241,66 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
|
|
|
1095
1241
|
* domains, writes zitadel-login's binding, then repoints the apps whose
|
|
1096
1242
|
* client id that binding holds. Apps created later are not in it yet:
|
|
1097
1243
|
* the next run replans (`diff.ts`'s `planLoginHost`) and moves them.
|
|
1244
|
+
*
|
|
1245
|
+
* #274: when the org's host has moved (`recorded[key]`, the *previous*
|
|
1246
|
+
* run's resourceId, differs from this run's `host`), also removes every
|
|
1247
|
+
* host that is no longer the org's current one from both places --
|
|
1248
|
+
* `ensureTrustedDomain` and `upsertBinding` above only ever add, so
|
|
1249
|
+
* without this an old host stays trusted and bound forever, and a later
|
|
1250
|
+
* org that reuses the old slug inherits it unasked.
|
|
1251
|
+
*
|
|
1252
|
+
* #274 fix round 2: this always returns `host` -- the *current* desired
|
|
1253
|
+
* host -- as its `resourceId`, never an old one, however many of its
|
|
1254
|
+
* removals are still outstanding. Fix round 1 returned `previousHost`
|
|
1255
|
+
* while a removal was skipped, banking on `planLoginHost` replanning the
|
|
1256
|
+
* whole step off that mismatch alone; that loses track of the *new* host
|
|
1257
|
+
* the moment a second host change moves `previousHost` on again (A -> B
|
|
1258
|
+
* skipped, then B -> C: A's own removal has nothing left recording it was
|
|
1259
|
+
* ever owed). `LOGIN_HOST_PENDING_REMOVAL_SCOPE_KEY`
|
|
1260
|
+
* (`pendingRemovalHosts`/`pendingRemovalState`, `login-host.ts`) is the
|
|
1261
|
+
* durable record instead -- every host still awaiting removal, independent
|
|
1262
|
+
* of which one is current -- and `planLoginHost` replans the step whenever
|
|
1263
|
+
* it is non-empty, not only when the current host itself differs.
|
|
1264
|
+
*
|
|
1265
|
+
* Whether a host is still "in use" is decided from each live app's actual
|
|
1266
|
+
* ZITADEL Login V2 base URI (`ZitadelPort.listApps`' `loginBaseUri`), not
|
|
1267
|
+
* from `boundAppIds` membership: fix round 1 skipped removal only while an
|
|
1268
|
+
* app outside `boundAppIds` existed, but `boundAppIds` is deliberately
|
|
1269
|
+
* narrower than "every live app" (it excludes `native`/`api` apps and any
|
|
1270
|
+
* `web` app without a recorded client id yet) and a host can safely be
|
|
1271
|
+
* removed even while such an app exists, so long as nothing is actually
|
|
1272
|
+
* pointed at it. Removing by the wrong signal either breaks sign-in (an
|
|
1273
|
+
* app repointed is not the same fact as an app with no client id yet) or
|
|
1274
|
+
* strands a host forever (an org with a `native`/`api` app would never
|
|
1275
|
+
* satisfy "every live app bound").
|
|
1276
|
+
*
|
|
1277
|
+
* #274 fix round 3, four more defects fix round 2 left:
|
|
1278
|
+
*
|
|
1279
|
+
* 1. The *current* host is excluded from the candidates for removal.
|
|
1280
|
+
* Without this, A -> B (A pending, not yet safe) -> A deletes the host
|
|
1281
|
+
* this very call just (re-)trusted and bound: a host already in
|
|
1282
|
+
* `LOGIN_HOST_PENDING_REMOVAL_SCOPE_KEY` from an earlier run can be
|
|
1283
|
+
* exactly the one the org's host is moving back to.
|
|
1284
|
+
* 2. Fail closed on missing information, rather than ever reading
|
|
1285
|
+
* "nothing uses it" off of one: an app whose `loginBaseUri` cannot be
|
|
1286
|
+
* read at all (`undefined` -- which, until a real adapter is wired per
|
|
1287
|
+
* `ZitadelPort.listApps`'s own doc comment, is every app, every call)
|
|
1288
|
+
* means every pending host stays pending this run, not just the ones a
|
|
1289
|
+
* narrower check happened to miss.
|
|
1290
|
+
* 3. Same fail-closed rule for `listApps`' own `complete` flag: an
|
|
1291
|
+
* incomplete read (today, always, for the identical reason) means the
|
|
1292
|
+
* set of apps this call saw could be missing one still pointed at a
|
|
1293
|
+
* pending host, so nothing is removed off of it either.
|
|
1294
|
+
* 4. A base URI compares after lowercasing and trimming its trailing
|
|
1295
|
+
* `/` (`normalizedLoginBaseUri`) -- ZITADEL's own value and the one
|
|
1296
|
+
* this step constructs are not guaranteed to agree on case or a
|
|
1297
|
+
* trailing slash, and a comparison that is case- or slash-sensitive
|
|
1298
|
+
* both ways can report "in use" as "not" by accident.
|
|
1299
|
+
* 5. Each live app's base URI is read as of *after* this call's own
|
|
1300
|
+
* repoint loop above, not the stale value `listApps` returned before
|
|
1301
|
+
* it: a bound app this very call just repointed away from a pending
|
|
1302
|
+
* host must not hold that host pending for one more run only because
|
|
1303
|
+
* the snapshot taken before the repoint still named it.
|
|
1098
1304
|
*/
|
|
1099
1305
|
private zitadelLoginHost;
|
|
1100
1306
|
/**
|
|
@@ -1568,6 +1774,89 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
|
|
|
1568
1774
|
/** `deliverSecret`, tried up to {@link JOBS_DELIVERY_ATTEMPTS} times; true once one attempt succeeds. */
|
|
1569
1775
|
private deliverWithRetry;
|
|
1570
1776
|
private cloudflareTunnel;
|
|
1777
|
+
/**
|
|
1778
|
+
* #236: when this tunnel's DNS was last published under a domain that
|
|
1779
|
+
* differs from the one `org` now declares, deletes every CNAME in the
|
|
1780
|
+
* *old* domain's zone that points at this tunnel, so a domain switch
|
|
1781
|
+
* never leaves the old domain still answering for it. Takes the previous
|
|
1782
|
+
* domain and hostnames as plain arguments rather than reading them off
|
|
1783
|
+
* `context` itself (round 3): `cloudflareTunnel` reads the old values
|
|
1784
|
+
* before calling either this or `publishTunnelDnsRecords`, and (round 4
|
|
1785
|
+
* finding 3) does not record the new domain/hostnames until *after* this
|
|
1786
|
+
* method has returned, so a crash here still sees the old domain on retry.
|
|
1787
|
+
* A no-op when the old zone itself is already gone.
|
|
1788
|
+
*
|
|
1789
|
+
* `previousHostnames` (round 3) is {@link CLOUDFLARE_TUNNEL_DNS_HOSTNAMES_SCOPE_KEY}'s
|
|
1790
|
+
* recorded list, looked up one by one instead of a zone-wide
|
|
1791
|
+
* `listDnsRecords` -- the real adapter's version of that call asks for one
|
|
1792
|
+
* page and never walks the rest. A domain change recorded before that key
|
|
1793
|
+
* existed falls back to the old zone-wide pass, the one case where no
|
|
1794
|
+
* hostname list was ever written to look up instead.
|
|
1795
|
+
*
|
|
1796
|
+
* `previousTunnelId` (round 4 finding 1): the old domain's CNAMEs were
|
|
1797
|
+
* published under whatever tunnel id was live the *last* time this step
|
|
1798
|
+
* recorded that domain, which is `previousTunnelId`, not necessarily
|
|
1799
|
+
* `tunnelId` -- a tunnel recreated under the same name
|
|
1800
|
+
* (`findTunnelByName` returning a new id) in the same apply that also
|
|
1801
|
+
* changes the domain leaves the old domain's records pointed at the old
|
|
1802
|
+
* id. Matching only `tunnelId`'s target left them live forever. Both
|
|
1803
|
+
* targets are checked so this stays a no-op once a retry has already
|
|
1804
|
+
* moved the old domain onto the new id.
|
|
1805
|
+
*/
|
|
1806
|
+
private deleteStaleTunnelDnsRecords;
|
|
1807
|
+
/**
|
|
1808
|
+
* #236: upserts a proxied CNAME to this tunnel for each of
|
|
1809
|
+
* `tunnelIngressHostnames(org)` that actually belongs to `domain`'s own
|
|
1810
|
+
* zone -- a hosting app still served off the shared `*.oikos.host`
|
|
1811
|
+
* wildcard tunnel (`scripts/shared-ingress.mjs`) can appear in that list
|
|
1812
|
+
* alongside `agora.<domain>` (#164's unconditional Agora hostname) when
|
|
1813
|
+
* `hosting.usesDomain` is off, and `<slug>.oikos.host` is not this org's
|
|
1814
|
+
* zone to touch.
|
|
1815
|
+
*
|
|
1816
|
+
* Checks every hostname for a conflicting record *before* writing any
|
|
1817
|
+
* (round 2 finding: a conflict found only partway through a hostname-by-
|
|
1818
|
+
* hostname pass left earlier hostnames written but the domain never
|
|
1819
|
+
* recorded -- an orphaned partial write). An existing A or AAAA record,
|
|
1820
|
+
* or a CNAME already pointed anywhere but this tunnel or (round 3) its own
|
|
1821
|
+
* immediately prior tunnel id, blocks the whole step with a named
|
|
1822
|
+
* conflict rather than being adopted or replaced, the same do-not-adopt
|
|
1823
|
+
* policy {@link cloudflareZone} itself follows for the zone. A CNAME
|
|
1824
|
+
* already pointed at this tunnel is left alone, so a second apply changes
|
|
1825
|
+
* nothing. The caller ({@link cloudflareTunnel}) records the domain and the
|
|
1826
|
+
* returned hostname list as scope ids only once this method -- and, on a
|
|
1827
|
+
* domain change, the old domain's own cleanup -- has returned successfully
|
|
1828
|
+
* (round 4 finding 3), so a crash mid-write is recovered by re-running
|
|
1829
|
+
* this same idempotent step, never silently skipped.
|
|
1830
|
+
*
|
|
1831
|
+
* Round 3: `previousTunnelId` is the tunnel id this same step recorded
|
|
1832
|
+
* last time it ran (`cloudflareTunnel`'s own `cloudflare:tunnel:<key>`
|
|
1833
|
+
* resource id, read before `tunnel` above is computed for this run) -- a
|
|
1834
|
+
* tunnel Cloudflare shows as recreated under the same name
|
|
1835
|
+
* (`findTunnelByName` returning a new id for `<slug>-tunnel`) leaves every
|
|
1836
|
+
* CNAME still pointed at the *old* id. That is this org's own stale
|
|
1837
|
+
* record, not a foreign one, so it is repointed rather than refused.
|
|
1838
|
+
*
|
|
1839
|
+
* Round 4 finding 2: `previousHostnames` is this domain's own previously
|
|
1840
|
+
* recorded hostname list (empty when the domain just changed --
|
|
1841
|
+
* {@link cloudflareTunnel} hands the *whole* old domain to
|
|
1842
|
+
* {@link deleteStaleTunnelDnsRecords} for that case instead). A name in it
|
|
1843
|
+
* that the org's current config no longer produces is dropped from
|
|
1844
|
+
* `hostnames` below but was never cleaned up before this round: its CNAME
|
|
1845
|
+
* stayed live and its name stayed in the recorded list forever, because
|
|
1846
|
+
* only a domain *change* ever ran cleanup. Each dropped name's CNAME is
|
|
1847
|
+
* deleted here when it still points at this tunnel or its immediately
|
|
1848
|
+
* prior id; a name already gone, or now pointed at something foreign, is
|
|
1849
|
+
* simply left out of the returned list -- nothing here is this method's to
|
|
1850
|
+
* reclaim or report as a conflict.
|
|
1851
|
+
*
|
|
1852
|
+
* Round 5 finding 1: `context` is used only to record
|
|
1853
|
+
* {@link CLOUDFLARE_TUNNEL_DNS_PENDING_DOMAIN_SCOPE_KEY}/
|
|
1854
|
+
* {@link CLOUDFLARE_TUNNEL_DNS_PENDING_HOSTNAMES_SCOPE_KEY} after each
|
|
1855
|
+
* individual CNAME write below succeeds, so a crash or a thrown conflict
|
|
1856
|
+
* partway through never leaves an already-created CNAME out of every
|
|
1857
|
+
* recorded list -- `destroy` checks this pending pair too.
|
|
1858
|
+
*/
|
|
1859
|
+
private publishTunnelDnsRecords;
|
|
1571
1860
|
/**
|
|
1572
1861
|
* `plan/diff.ts`'s `planZone`: finds or creates the Cloudflare zone for the
|
|
1573
1862
|
* org's own domain. Refuses to adopt a zone the store already records
|