@wtfalch/foundry 0.4.0 → 0.6.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 +111 -0
- package/dist/bootstrap/bootstrap.js +323 -0
- package/dist/bootstrap/declare.d.ts +36 -0
- package/dist/bootstrap/declare.js +99 -0
- package/dist/bootstrap/drive.d.ts +40 -0
- package/dist/bootstrap/drive.js +51 -0
- package/dist/bootstrap/index.d.ts +3 -0
- package/dist/bootstrap/index.js +1 -0
- package/dist/calls-sfu-deploy-support.d.ts +17 -6
- package/dist/calls-sfu-deploy-support.js +19 -7
- package/dist/calls-sfu-keys.d.ts +21 -0
- package/dist/calls-sfu-keys.js +27 -0
- package/dist/calls-sfu-ssh-adapter.d.ts +55 -0
- package/dist/calls-sfu-ssh-adapter.js +78 -0
- package/dist/calls-sfu-ssh-client.d.ts +49 -0
- package/dist/calls-sfu-ssh-client.js +82 -0
- package/dist/calls-sfu.d.ts +36 -12
- package/dist/calls-sfu.js +41 -9
- package/dist/index.d.ts +38 -7
- package/dist/index.js +20 -4
- package/dist/migrations/0016_platform.sql +61 -0
- package/dist/migrations/0017_requested_changes.sql +42 -0
- package/dist/migrations/0018_platform_apply.sql +19 -0
- package/dist/migrations/0019_mail_owner_mailbox.sql +15 -0
- package/dist/migrations/0020_mail_apps.sql +7 -0
- package/dist/migrations/0021_mail_role_mailboxes.sql +6 -0
- package/dist/org.d.ts +81 -0
- package/dist/org.js +96 -0
- package/dist/plan/agora.d.ts +77 -0
- package/dist/plan/agora.js +127 -0
- package/dist/plan/cloudflared-connector.d.ts +70 -0
- package/dist/plan/cloudflared-connector.js +106 -0
- package/dist/plan/diff.d.ts +20 -3
- package/dist/plan/diff.js +713 -115
- package/dist/plan/executor.d.ts +680 -172
- package/dist/plan/executor.js +755 -189
- package/dist/plan/index.d.ts +28 -5
- package/dist/plan/index.js +15 -2
- package/dist/plan/jobs-scheduler-http.d.ts +29 -0
- package/dist/plan/jobs-scheduler-http.js +50 -0
- package/dist/plan/live.d.ts +51 -2
- package/dist/plan/live.js +8 -0
- package/dist/plan/mail-copy.d.ts +125 -0
- package/dist/plan/mail-copy.js +166 -0
- package/dist/plan/mail-identity-allowlist.d.ts +50 -0
- package/dist/plan/mail-identity-allowlist.js +11 -0
- package/dist/plan/mail-identity.d.ts +68 -0
- package/dist/plan/mail-identity.js +17 -0
- package/dist/plan/plan-apply.d.ts +197 -3
- package/dist/plan/plan-apply.js +278 -9
- package/dist/plan/platform-diff.d.ts +487 -0
- package/dist/plan/platform-diff.js +1220 -0
- package/dist/plan/platform-org-ports.d.ts +50 -0
- package/dist/plan/platform-org-ports.js +70 -0
- package/dist/plan/platform-org.d.ts +121 -0
- package/dist/plan/platform-org.js +115 -0
- package/dist/plan/platform-providers.d.ts +615 -0
- package/dist/plan/platform-providers.js +4410 -0
- package/dist/plan/platform-sfu.d.ts +128 -0
- package/dist/plan/platform-sfu.js +142 -0
- package/dist/plan/providers-readers.js +22 -1
- package/dist/plan/zitadel-machine-user.d.ts +83 -0
- package/dist/plan/zitadel-machine-user.js +238 -0
- package/dist/plan.d.ts +6 -0
- package/dist/platform.d.ts +55 -0
- package/dist/platform.js +46 -0
- package/dist/requested-changes.d.ts +118 -0
- package/dist/requested-changes.js +125 -0
- package/dist/slug.js +15 -2
- package/dist/store/audit.js +4 -2
- package/dist/store/postgres.d.ts +29 -1
- package/dist/store/postgres.js +203 -4
- package/dist/store/schema.d.ts +755 -0
- package/dist/store/schema.js +70 -0
- package/dist/store.d.ts +53 -0
- package/package.json +1 -1
|
@@ -0,0 +1,615 @@
|
|
|
1
|
+
import type { CloudflareClient } from '@wtfalch/providers/cloudflare';
|
|
2
|
+
import type { DeclaredPlatform } from '../platform.js';
|
|
3
|
+
import type { DeclaredOrgStore, PlatformStore } from '../store.js';
|
|
4
|
+
import type { StepAction } from './diff.js';
|
|
5
|
+
import type { CloudflarePort, CoolifyPort, FoundryStepExecutorPorts, ZitadelPort } from './executor.js';
|
|
6
|
+
import type { LiveResource, PlatformCloudflareReader, PlatformCoolifyReader } from './live.js';
|
|
7
|
+
import type { PlatformStepExecutionContext, PlatformStepExecutor } from './plan-apply.js';
|
|
8
|
+
import { type CallsSfuPlatformDeps } from './platform-sfu.js';
|
|
9
|
+
/**
|
|
10
|
+
* #168: everything {@link provisionPlatformAuth} needs beyond
|
|
11
|
+
* {@link PlatformStepExecutionContext} -- a bundle, not more constructor
|
|
12
|
+
* positions, so every #167-era caller of {@link FoundryPlatformStepExecutor}
|
|
13
|
+
* (zone only) keeps compiling with this third argument simply omitted.
|
|
14
|
+
*/
|
|
15
|
+
export interface PlatformAuthDeps {
|
|
16
|
+
coolify: Pick<CoolifyPort, 'findProjectByName' | 'createProject' | 'findOrCreateEnvironment' | 'listServers' | 'findApplicationByName' | 'getApplication' | 'requireGithubApp' | 'createGithubAppApplication' | 'createDockerImageApplication' | 'createDockerfileApplication' | 'createPostgresDatabase' | 'getDatabase' | 'bulkUpsertEnvs' | 'listEnvs' | 'updateApplication' | 'deploy' | 'raw'>;
|
|
17
|
+
cloudflare: Pick<CloudflarePort, 'findZoneByApex' | 'findTunnelByName' | 'createTunnel' | 'putTunnelConfig' | 'findDnsRecord' | 'createDnsRecord' | 'updateDnsRecord'
|
|
18
|
+
/** #197: fetches the tunnel's connector token for {@link ensureCloudflaredConnector}. */
|
|
19
|
+
| 'getTunnelToken'>;
|
|
20
|
+
/** Must target the platform's own new instance (its issuer), or `zitadelForInstance` must be set. */
|
|
21
|
+
zitadel: Pick<ZitadelPort, 'findOrCreateOrg' | 'mintAdminMachineUser' | 'mintLoginClientMachineUser' | 'listLoginClientPats'>;
|
|
22
|
+
/**
|
|
23
|
+
* Builds the ZITADEL port for the new instance from its issuer URL
|
|
24
|
+
* (`https://<auth.hostname>`). When set it is used instead of
|
|
25
|
+
* {@link zitadel}, so the mint always hits the instance this step just
|
|
26
|
+
* built. See `createZitadelAdminMinter` for the mint half.
|
|
27
|
+
*/
|
|
28
|
+
zitadelForInstance?: (issuerUrl: string) => PlatformAuthDeps['zitadel'];
|
|
29
|
+
deliverSecret: FoundryStepExecutorPorts['deliverSecret'];
|
|
30
|
+
/** Clock, sleep and fetch for the readiness wait and delivery retries; real ones by default. */
|
|
31
|
+
runtime?: PlatformAuthRuntime;
|
|
32
|
+
}
|
|
33
|
+
export interface PlatformAuthRuntime {
|
|
34
|
+
fetch?: typeof fetch;
|
|
35
|
+
sleep?: (ms: number) => Promise<void>;
|
|
36
|
+
now?: () => number;
|
|
37
|
+
/** How long to wait for the new instance to answer (default 5 minutes). */
|
|
38
|
+
readyTimeoutMs?: number;
|
|
39
|
+
/** Pause between readiness polls (default 5 seconds). */
|
|
40
|
+
readyIntervalMs?: number;
|
|
41
|
+
/** Tries at handing the admin key to the host (default 3). */
|
|
42
|
+
deliverAttempts?: number;
|
|
43
|
+
}
|
|
44
|
+
/** The ref the scheduler's admin token is delivered under: the host's real `JobsSchedulerPort` adapter reads it back. */
|
|
45
|
+
export declare const JOBS_ADMIN_TOKEN_REF = "jobs:scheduler:adminToken";
|
|
46
|
+
/**
|
|
47
|
+
* The narrow slice of {@link CoolifyPort} {@link FoundryPlatformStepExecutor}
|
|
48
|
+
* needs for `coolify:jobs` (#173) -- the same project/environment/server/app/
|
|
49
|
+
* database calls `executor.ts`'s `coolifyApp`/`coolifyDatabase` make for an
|
|
50
|
+
* org's own app, reused here for the platform's one scheduler instead of once
|
|
51
|
+
* per org.
|
|
52
|
+
*/
|
|
53
|
+
type JobsCoolifyPort = Pick<CoolifyPort, 'findProjectByName' | 'createProject' | 'findOrCreateEnvironment' | 'listServers' | 'requireGithubApp' | 'findApplicationByName' | 'createGithubAppApplication' | 'listDatabases' | 'createPostgresDatabase' | 'getDatabase' | 'bulkUpsertEnvs' | 'getApplication' | 'deploy'>;
|
|
54
|
+
/**
|
|
55
|
+
* Options for {@link FoundryPlatformStepExecutor}'s constructor, replacing
|
|
56
|
+
* four positional arguments that shifted on every merge that added a
|
|
57
|
+
* platform service (#186, #187, #188) -- see
|
|
58
|
+
* https://github.com/wtfalch/foundry/pull/188#issuecomment-5895935971, the
|
|
59
|
+
* merge comment that named this shape (#189).
|
|
60
|
+
*/
|
|
61
|
+
export interface FoundryPlatformStepExecutorOptions {
|
|
62
|
+
coolify?: JobsCoolifyPort;
|
|
63
|
+
/**
|
|
64
|
+
* Hands a just-generated secret to the host (`FoundryStepExecutorPorts.
|
|
65
|
+
* deliverSecret`'s contract: never returned, recorded, logged or put in
|
|
66
|
+
* an error). Needed by `coolify:jobs` for the scheduler's admin token.
|
|
67
|
+
*/
|
|
68
|
+
deliverSecret?: (ref: string, secret: string) => Promise<void>;
|
|
69
|
+
sfu?: CallsSfuPlatformDeps;
|
|
70
|
+
/**
|
|
71
|
+
* #168: dependencies for `coolify:app` keyed {@link PLATFORM_AUTH_KEY} --
|
|
72
|
+
* optional so every #167-era caller (zone only) keeps compiling; a host
|
|
73
|
+
* that declares `platform.auth.enabled` must construct this with it set,
|
|
74
|
+
* or `execute` throws naming what's missing.
|
|
75
|
+
*/
|
|
76
|
+
auth?: PlatformAuthDeps;
|
|
77
|
+
/**
|
|
78
|
+
* #169: dependencies for `coolify:app` keyed {@link PLATFORM_MAIL_KEY} --
|
|
79
|
+
* the same optionality as {@link auth}, for the identical reason.
|
|
80
|
+
*/
|
|
81
|
+
mail?: PlatformMailDeps;
|
|
82
|
+
/**
|
|
83
|
+
* #174: dependencies for the integrations host, `coolify:app` keyed
|
|
84
|
+
* {@link PLATFORM_INTEGRATIONS_KEY}. Without it, an enabled integrations
|
|
85
|
+
* host is reported blocked by `blockedSteps` rather than planned.
|
|
86
|
+
*/
|
|
87
|
+
integrations?: PlatformIntegrationsDeps;
|
|
88
|
+
/**
|
|
89
|
+
* #171: dependencies for `coolify:app` keyed {@link PLATFORM_FILES_KEY} --
|
|
90
|
+
* optional for the same reason as `auth`; a host that declares
|
|
91
|
+
* `platform.files.enabled` without them has the step reported blocked.
|
|
92
|
+
*/
|
|
93
|
+
files?: PlatformFilesDeps;
|
|
94
|
+
/**
|
|
95
|
+
* #172: dependencies for `coolify:app` keyed {@link PLATFORM_AI_KEY} --
|
|
96
|
+
* optional for the same reason as `auth`; a host that declares
|
|
97
|
+
* `platform.ai.enabled` without it gets the step reported as blocked.
|
|
98
|
+
*/
|
|
99
|
+
ai?: PlatformAiDeps;
|
|
100
|
+
/**
|
|
101
|
+
* #175: dependencies for `coolify:app` keyed {@link PLATFORM_ARCHON_KEY}
|
|
102
|
+
* -- optional for the same reason as {@link auth}.
|
|
103
|
+
*/
|
|
104
|
+
archon?: PlatformArchonDeps;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* The real {@link PlatformStepExecutor} for the two steps built so far:
|
|
108
|
+
* `cloudflare:zone` for the platform's apex (#167) and `coolify:jobs` for the
|
|
109
|
+
* jobs scheduler (#173). `coolify` is optional so every #167-era caller --
|
|
110
|
+
* anything that only ever plans the apex zone -- keeps constructing this with
|
|
111
|
+
* two arguments; a `coolify:jobs` step reaching this executor with no
|
|
112
|
+
* `coolify` port configured throws, naming what is missing, rather than
|
|
113
|
+
* silently doing nothing.
|
|
114
|
+
*
|
|
115
|
+
* `cloudflareZone` mirrors `executor.ts`'s `cloudflareZone`: find the zone by
|
|
116
|
+
* apex and adopt it, create it only when absent. companyincluded.com already
|
|
117
|
+
* exists on Cloudflare, empty, so the first apply adopts it. The owner org
|
|
118
|
+
* (Company Included, whose own domain is the apex) may record the same zone
|
|
119
|
+
* under its own applied state, so that owner is allowed. Any other org that
|
|
120
|
+
* recorded it makes this refuse to adopt. There is no delete here and
|
|
121
|
+
* `PlatformStepExecutor` has none: neither path ever removes the zone.
|
|
122
|
+
*
|
|
123
|
+
* `coolifyJobs` mirrors `executor.ts`'s `coolifyApp`/`coolifyDatabase`,
|
|
124
|
+
* simplified for one platform-level singleton rather than one per org: find
|
|
125
|
+
* or create a dedicated `platform` Coolify project, find or create the
|
|
126
|
+
* scheduler's own database, find or create the scheduler app itself (from
|
|
127
|
+
* https://github.com/wtfalch/jobs, one Dockerfile at its repo root, on the
|
|
128
|
+
* declared `gitRef`), then push the database's `internal_db_url` in as
|
|
129
|
+
* `DATABASE_URL`. There is no park branch yet, matching `planJobs`'s own
|
|
130
|
+
* scope note -- `platform.jobs.enabled` going from true to false plans
|
|
131
|
+
* nothing, so this method is never asked to stop or delete anything.
|
|
132
|
+
*
|
|
133
|
+
* #170 adds `sfu:livekit`, the platform's shared calls SFU. `sfu` is
|
|
134
|
+
* optional: an existing host that constructs this executor for the zone step
|
|
135
|
+
* alone keeps compiling, and a plan that includes an `sfu:livekit` step
|
|
136
|
+
* without it configured refuses clearly rather than silently doing nothing.
|
|
137
|
+
* #152: the step mints one LiveKit key per org with calls enabled rather
|
|
138
|
+
* than one shared pair, so it also needs `declaredOrgStore.list()` -- the
|
|
139
|
+
* same store this class already takes for `findOrgIdForZone`.
|
|
140
|
+
*
|
|
141
|
+
* #168 adds `coolify:app` keyed {@link PLATFORM_AUTH_KEY}, the platform's own
|
|
142
|
+
* sign-in ({@link provisionPlatformAuth}), once a host passes
|
|
143
|
+
* {@link PlatformAuthDeps}.
|
|
144
|
+
*
|
|
145
|
+
* #169 adds `coolify:app` keyed {@link PLATFORM_MAIL_KEY}, the platform's own
|
|
146
|
+
* mail server ({@link provisionPlatformMail}), once a host passes
|
|
147
|
+
* {@link PlatformMailDeps}.
|
|
148
|
+
*
|
|
149
|
+
* #174 adds `coolify:app` keyed {@link PLATFORM_INTEGRATIONS_KEY},
|
|
150
|
+
* the integrations host ({@link provisionPlatformIntegrations}), once a host
|
|
151
|
+
* passes {@link PlatformIntegrationsDeps}.
|
|
152
|
+
*
|
|
153
|
+
* #171 adds `coolify:app` keyed {@link PLATFORM_FILES_KEY}, the files host
|
|
154
|
+
* ({@link provisionPlatformFiles}), once a host passes {@link PlatformFilesDeps}.
|
|
155
|
+
*
|
|
156
|
+
* #172 adds `coolify:app` keyed {@link PLATFORM_AI_KEY}, the platform's AI
|
|
157
|
+
* host ({@link provisionPlatformAi}), once a host passes {@link PlatformAiDeps}.
|
|
158
|
+
*
|
|
159
|
+
* #175 adds `coolify:app` keyed {@link PLATFORM_ARCHON_KEY}, Archon itself
|
|
160
|
+
* ({@link provisionPlatformArchon}), once a host passes
|
|
161
|
+
* {@link PlatformArchonDeps}.
|
|
162
|
+
*/
|
|
163
|
+
export declare class FoundryPlatformStepExecutor implements PlatformStepExecutor {
|
|
164
|
+
private readonly cloudflare;
|
|
165
|
+
private readonly declaredOrgStore;
|
|
166
|
+
private readonly coolify?;
|
|
167
|
+
private readonly deliverSecret?;
|
|
168
|
+
private readonly sfu?;
|
|
169
|
+
private readonly auth?;
|
|
170
|
+
private readonly mail?;
|
|
171
|
+
private readonly integrations?;
|
|
172
|
+
private readonly files?;
|
|
173
|
+
private readonly ai?;
|
|
174
|
+
private readonly archon?;
|
|
175
|
+
constructor(cloudflare: Pick<CloudflarePort, 'findZoneByApex' | 'createZone'>, declaredOrgStore: Pick<DeclaredOrgStore, 'findOrgIdForZone' | 'list'>, options?: FoundryPlatformStepExecutorOptions);
|
|
176
|
+
blockedSteps(platform: DeclaredPlatform, recordedIds?: Readonly<Record<string, string>>): Promise<{
|
|
177
|
+
auth?: string;
|
|
178
|
+
authExecutorMissing?: boolean;
|
|
179
|
+
authOtherInputsMissing?: string[];
|
|
180
|
+
files?: string;
|
|
181
|
+
integrations?: string;
|
|
182
|
+
ai?: string;
|
|
183
|
+
archon?: string;
|
|
184
|
+
mail?: string;
|
|
185
|
+
}>;
|
|
186
|
+
/**
|
|
187
|
+
* Whether Archon's live Access policy lets through anyone but the declared
|
|
188
|
+
* emails, or (#211 round 4) the broker it last registered with no longer
|
|
189
|
+
* carries its entry; or (#216 round 4) the platform's recorded
|
|
190
|
+
* login-client token is no longer the sole live PAT on its ZITADEL machine
|
|
191
|
+
* user.
|
|
192
|
+
*/
|
|
193
|
+
driftedSteps(platform: DeclaredPlatform, recordedIds?: Readonly<Record<string, string>>): Promise<{
|
|
194
|
+
archon?: boolean;
|
|
195
|
+
auth?: boolean;
|
|
196
|
+
}>;
|
|
197
|
+
execute(action: StepAction, context: PlatformStepExecutionContext): Promise<{
|
|
198
|
+
resourceId?: string;
|
|
199
|
+
} | undefined>;
|
|
200
|
+
private cloudflareZone;
|
|
201
|
+
/**
|
|
202
|
+
* #152: the platform `sfu` step owns the shared LiveKit config, so it
|
|
203
|
+
* lists every org with calls enabled itself -- `DeclaredOrgStore.list()`,
|
|
204
|
+
* filtered the same way an org's own `calls.enabled` flag gates its
|
|
205
|
+
* `coolify:calls-service` step (`executor.ts`'s `coolifyCallsService`) --
|
|
206
|
+
* rather than taking the list as an argument nothing else would supply.
|
|
207
|
+
* Sorted by slug so a re-run with the same set of enabled orgs is a no-op
|
|
208
|
+
* even before `applyCallsSfuPlatformStep`'s own sort (its doc comment).
|
|
209
|
+
*/
|
|
210
|
+
private executeSfu;
|
|
211
|
+
private coolifyJobs;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* The fixed description Stalwart's admin API key is minted under
|
|
215
|
+
* (`StalwartClient.createApiKey`'s own signature returns only the secret,
|
|
216
|
+
* never an id) -- what a human revokes it by in Stalwart's own admin UI if
|
|
217
|
+
* it is ever lost between mint and delivery, and what
|
|
218
|
+
* {@link PLATFORM_MAIL_ADMIN_UNDELIVERED_SCOPE_KEY} records in that case.
|
|
219
|
+
*/
|
|
220
|
+
export declare const PLATFORM_MAIL_ADMIN_KEY_DESCRIPTION = "foundry:platform:admin";
|
|
221
|
+
/**
|
|
222
|
+
* Stalwart's own management API, scoped to exactly what this step needs from
|
|
223
|
+
* the instance it just deployed -- mirrors {@link PlatformAuthDeps}'s
|
|
224
|
+
* `zitadel`/`zitadelForInstance` split: a host builds this from the
|
|
225
|
+
* instance's own URL and the recovery-admin credential this step generated
|
|
226
|
+
* ({@link PlatformMailDeps.stalwartForInstance}), the same way
|
|
227
|
+
* `zitadelForInstance` is built from a freshly issued instance's issuer URL.
|
|
228
|
+
* The real implementation wraps `@wtfalch/email/postmaster`'s `openInstance`
|
|
229
|
+
* (for the `StalwartClient`), a hand-built settings write (see
|
|
230
|
+
* `provisionPlatformMail`'s own doc comment for why not `mailObjects`) and
|
|
231
|
+
* `StalwartClient.createApiKey`.
|
|
232
|
+
*/
|
|
233
|
+
export interface PlatformMailAdminPort {
|
|
234
|
+
/**
|
|
235
|
+
* Sets the instance's default hostname and its OIDC directory
|
|
236
|
+
* (`x:SystemSettings`/`x:Directory`/`x:Domain`/`x:Authentication`) --
|
|
237
|
+
* idempotent, safe to call on every apply once the instance is up.
|
|
238
|
+
*/
|
|
239
|
+
applySettings(input: {
|
|
240
|
+
mailHost: string;
|
|
241
|
+
oidc: {
|
|
242
|
+
issuerUrl: string;
|
|
243
|
+
audience: string;
|
|
244
|
+
};
|
|
245
|
+
}): Promise<void>;
|
|
246
|
+
/** `StalwartClient.createApiKey`'s own signature. */
|
|
247
|
+
createApiKey(description: string, permissions: readonly string[] | 'inherit'): Promise<string>;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* #169: everything {@link provisionPlatformMail} needs beyond
|
|
251
|
+
* {@link PlatformStepExecutionContext}, mirroring {@link PlatformAuthDeps}'s
|
|
252
|
+
* own shape.
|
|
253
|
+
*/
|
|
254
|
+
export interface PlatformMailDeps {
|
|
255
|
+
coolify: Pick<CoolifyPort, 'findProjectByName' | 'createProject' | 'findOrCreateEnvironment' | 'listServers' | 'findApplicationByName' | 'getApplication' | 'createDockerImageApplication' | 'createDockerfileApplication' | 'raw' | 'listStorages' | 'createPersistentStorage' | 'bulkUpsertEnvs' | 'listEnvs' | 'updateApplication' | 'deploy'>;
|
|
256
|
+
cloudflare: Pick<CloudflarePort, 'findZoneByApex' | 'findTunnelByName' | 'createTunnel' | 'putTunnelConfig' | 'getTunnelToken' | 'findDnsRecord' | 'createDnsRecord' | 'updateDnsRecord'>;
|
|
257
|
+
/**
|
|
258
|
+
* The ZITADEL resource id an access token's `aud` claim must carry for
|
|
259
|
+
* Stalwart's OIDC directory to accept it -- a resource API registered
|
|
260
|
+
* against the platform's own sign-in (#168), which neither that step nor
|
|
261
|
+
* this one creates. See `provisionPlatformMail`'s own doc comment, "Known
|
|
262
|
+
* gaps".
|
|
263
|
+
*/
|
|
264
|
+
oidcAudience: string;
|
|
265
|
+
/**
|
|
266
|
+
* {@link PlatformMailAdminPort}, built from the instance this step just
|
|
267
|
+
* deployed and the recovery credential it just read back.
|
|
268
|
+
*/
|
|
269
|
+
stalwartForInstance(input: {
|
|
270
|
+
url: string;
|
|
271
|
+
user: string;
|
|
272
|
+
password: string;
|
|
273
|
+
}): PlatformMailAdminPort;
|
|
274
|
+
deliverSecret: FoundryStepExecutorPorts['deliverSecret'];
|
|
275
|
+
/** Clock, sleep and fetch for the readiness wait and delivery retries; real ones by default. */
|
|
276
|
+
runtime?: PlatformMailRuntime;
|
|
277
|
+
}
|
|
278
|
+
export interface PlatformMailRuntime {
|
|
279
|
+
fetch?: typeof fetch;
|
|
280
|
+
sleep?: (ms: number) => Promise<void>;
|
|
281
|
+
now?: () => number;
|
|
282
|
+
/** How long to wait for the new instance to answer (default 5 minutes). */
|
|
283
|
+
readyTimeoutMs?: number;
|
|
284
|
+
/** Pause between readiness polls (default 5 seconds). */
|
|
285
|
+
readyIntervalMs?: number;
|
|
286
|
+
/** Tries at handing the admin key to the host (default 3). */
|
|
287
|
+
deliverAttempts?: number;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* #175: everything {@link provisionPlatformArchon} needs beyond
|
|
291
|
+
* {@link PlatformStepExecutionContext}, bundled like {@link PlatformAuthDeps}.
|
|
292
|
+
*/
|
|
293
|
+
export interface PlatformArchonDeps {
|
|
294
|
+
coolify: Pick<CoolifyPort, 'findProjectByName' | 'createProject' | 'findOrCreateEnvironment' | 'listServers' | 'findApplicationByName' | 'getApplication' | 'requireGithubApp' | 'createGithubAppApplication' | 'createDockerfileApplication' | 'createPostgresDatabase' | 'getDatabase' | 'bulkUpsertEnvs' | 'listEnvs' | 'updateApplication' | 'deploy'>;
|
|
295
|
+
cloudflare: Pick<CloudflarePort, 'findZoneByApex' | 'findTunnelByName' | 'createTunnel' | 'putTunnelConfig' | 'getTunnelToken' | 'findDnsRecord' | 'createDnsRecord' | 'updateDnsRecord' | 'listAccessApps' | 'createAccessApp' | 'listAccessPolicies' | 'createAccessPolicy' | 'deleteAccessApp'>;
|
|
296
|
+
/** Must target the platform's own ZITADEL instance, or `zitadelForInstance` must be set. */
|
|
297
|
+
zitadel: Pick<ZitadelPort, 'findOrCreateOrg' | 'findOrCreateProject' | 'createOidcApp' | 'listApps' | 'deleteApp'>;
|
|
298
|
+
/** {@link PlatformAuthDeps.zitadelForInstance}, for Archon's sign-in client. */
|
|
299
|
+
zitadelForInstance?: (issuerUrl: string) => PlatformArchonDeps['zitadel'];
|
|
300
|
+
/**
|
|
301
|
+
* Who the Cloudflare Access app in front of Archon lets through. Host
|
|
302
|
+
* config, not `DeclaredPlatform`: the platform record names services, not
|
|
303
|
+
* people. Empty blocks the step (`blockedSteps`) rather than building an
|
|
304
|
+
* Access app nobody can pass.
|
|
305
|
+
*/
|
|
306
|
+
accessEmails: readonly string[];
|
|
307
|
+
/**
|
|
308
|
+
* Reads a secret an earlier step handed to the host through
|
|
309
|
+
* `deliverSecret`, or `null` when none is held under `ref` yet -- the same
|
|
310
|
+
* direction `CallsSfuPort.resolveCredentials` reads held keys back. Every
|
|
311
|
+
* value it returns goes straight to `bulkUpsertEnvs` and nowhere else.
|
|
312
|
+
* Optional so a host without it still compiles; while a service is enabled
|
|
313
|
+
* without it (or without a key it holds) Archon is blocked, not skipped.
|
|
314
|
+
*
|
|
315
|
+
* `put` (#213) writes a value under `ref`, once -- see
|
|
316
|
+
* {@link putArchonMasterKeys}. Also optional, and also blocking: while
|
|
317
|
+
* {@link masterKeys} is given but this cannot put, Archon is blocked, not
|
|
318
|
+
* skipped, the same rule.
|
|
319
|
+
*/
|
|
320
|
+
heldKeys?: {
|
|
321
|
+
resolve(ref: string): Promise<string | null>;
|
|
322
|
+
put?(ref: string, value: string): Promise<void>;
|
|
323
|
+
};
|
|
324
|
+
/**
|
|
325
|
+
* #213: the credentials Archon's own onboarding reads back as held keys
|
|
326
|
+
* before it can start (archon#101 round 3, archon#104) -- the Coolify URL
|
|
327
|
+
* and token this package already deploys with, and a Cloudflare parent
|
|
328
|
+
* token with "API Tokens Write" a human creates by hand (#213 "Human
|
|
329
|
+
* steps"), so Archon can mint its own scoped child token from it. Put
|
|
330
|
+
* once, under tenant `platform`, before Archon's app deploys
|
|
331
|
+
* ({@link putArchonMasterKeys}); optional so a host without it still
|
|
332
|
+
* compiles, and Archon is blocked, not skipped, while it (or
|
|
333
|
+
* {@link heldKeys}'s `put`) is missing.
|
|
334
|
+
*/
|
|
335
|
+
masterKeys?: {
|
|
336
|
+
coolifyUrl: string;
|
|
337
|
+
coolifyToken: string;
|
|
338
|
+
cloudflareParentToken: string;
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* The name of foundry's own Cloudflare Access app in front of Archon. Same
|
|
343
|
+
* `foundry:<key>` convention `executor.ts`'s `cloudflareAccess` uses for an
|
|
344
|
+
* org (`foundry:<orgId>`), with the platform's step key in the org id's place.
|
|
345
|
+
*/
|
|
346
|
+
export declare const PLATFORM_ARCHON_ACCESS_APP_NAME = "foundry:platform:archon";
|
|
347
|
+
/** The platform's own ZITADEL org (`foundry`, the one `provisionPlatformAuth` mints its admin user in). */
|
|
348
|
+
export declare const PLATFORM_ZITADEL_ORG_SCOPE_KEY = "zitadel_platform_org";
|
|
349
|
+
/** Archon's OIDC client id: public, not a secret, so a plain scope id. */
|
|
350
|
+
export declare const PLATFORM_ARCHON_CLIENT_ID_SCOPE_KEY = "zitadel_platform_archon_client_id";
|
|
351
|
+
/**
|
|
352
|
+
* #172/#201: mints Archon's own AI management key -- the plain bearer key
|
|
353
|
+
* (`wtfk_...`) `AiOrganisationPort` (`executor.ts`) authenticates with,
|
|
354
|
+
* holding {@link PLATFORM_AI_MANAGEMENT_PERMISSIONS} in ai's platform
|
|
355
|
+
* (operator) tenant, named `archon` (the key is Archon's; foundry only mints
|
|
356
|
+
* it while provisioning -- see {@link provisionPlatformAi}). The secret comes
|
|
357
|
+
* back once; {@link provisionPlatformAi} hands it to `deliverSecret` and
|
|
358
|
+
* records only the credential id.
|
|
359
|
+
*
|
|
360
|
+
* {@link createAiManagementKeyMinter} is the real implementation
|
|
361
|
+
* {@link provisionPlatformAi} builds by default, signing with the Ed25519
|
|
362
|
+
* pair it mints and pins on the runner and calling ai's own
|
|
363
|
+
* `POST /v1/platform/keys` (github.com/wtfalch/ai/issues/141). That route is
|
|
364
|
+
* idempotent by name: a repeat call with the same permissions and expiry
|
|
365
|
+
* returns the live key's id with no secret rather than a second key, so
|
|
366
|
+
* `mintManagementKey` throws {@link AiManagementKeyExistsError} for that
|
|
367
|
+
* case -- {@link provisionPlatformAi} only ever calls it once there is no
|
|
368
|
+
* delivered record, so a 200 here means an earlier mint's secret was lost,
|
|
369
|
+
* never a fresh one to hand back.
|
|
370
|
+
*
|
|
371
|
+
* A test may still inject a different {@link AiManagementKeyMinter} in
|
|
372
|
+
* {@link PlatformAiDeps.managementKeyMinter}, which skips building the
|
|
373
|
+
* default one and its signing-key machinery entirely.
|
|
374
|
+
*/
|
|
375
|
+
export interface AiManagementKeyMinter {
|
|
376
|
+
mintManagementKey(input: {
|
|
377
|
+
/** Where the ai host answers on the Coolify network, e.g. `http://platform-ai:3000`. */
|
|
378
|
+
internalUrl: string;
|
|
379
|
+
name: string;
|
|
380
|
+
expiresDays: number;
|
|
381
|
+
permissions: readonly string[];
|
|
382
|
+
}): Promise<{
|
|
383
|
+
credentialId: string;
|
|
384
|
+
organisationId: string;
|
|
385
|
+
secret: string;
|
|
386
|
+
}>;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Thrown by {@link createAiManagementKeyMinter} (never by a test's own
|
|
390
|
+
* injected minter, unless it chooses to) when ai's `POST /v1/platform/keys`
|
|
391
|
+
* answers 200: a live `archon` key already exists and no secret comes back.
|
|
392
|
+
* See {@link PLATFORM_AI_MANAGEMENT_KEY_CONFLICT_SCOPE_KEY}.
|
|
393
|
+
*/
|
|
394
|
+
export declare class AiManagementKeyExistsError extends Error {
|
|
395
|
+
readonly credentialId: string;
|
|
396
|
+
readonly organisationId: string;
|
|
397
|
+
constructor(credentialId: string, organisationId: string);
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Thrown by {@link createAiManagementKeyMinter} on a 409: a live `archon` key
|
|
401
|
+
* already exists with different permissions or expiry than this request
|
|
402
|
+
* asked for. ai never overwrites it, so this is never retried under another
|
|
403
|
+
* name -- an operator revokes the live key in ai first.
|
|
404
|
+
*/
|
|
405
|
+
export declare class AiManagementKeyConflictError extends Error {
|
|
406
|
+
constructor();
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Thrown by {@link createAiManagementKeyMinter} on a 401 or 403: ai does not
|
|
410
|
+
* trust foundry's signing key yet for this request. Most likely the runner
|
|
411
|
+
* has not redeployed since {@link AI_SIGNING_KEY_REF}'s public half was
|
|
412
|
+
* pinned as `MANAGEMENT_PUBLIC_KEYS`/`MANAGEMENT_ISSUER` -- a Coolify env
|
|
413
|
+
* change reaches the container only on its next deploy.
|
|
414
|
+
*/
|
|
415
|
+
export declare class AiManagementKeyUntrustedError extends Error {
|
|
416
|
+
readonly status: number;
|
|
417
|
+
constructor(status: number);
|
|
418
|
+
}
|
|
419
|
+
/** #172: everything {@link provisionPlatformAi} needs beyond {@link PlatformStepExecutionContext}. */
|
|
420
|
+
export interface PlatformAiDeps {
|
|
421
|
+
coolify: Pick<CoolifyPort, 'findProjectByName' | 'createProject' | 'findOrCreateEnvironment' | 'listServers' | 'findApplicationByName' | 'getApplication' | 'requireGithubApp' | 'createGithubAppApplication' | 'listDatabases' | 'createPostgresDatabase' | 'getDatabase' | 'bulkUpsertEnvs' | 'listEnvs' | 'updateApplication' | 'deploy' | 'raw'>;
|
|
422
|
+
deliverSecret: FoundryStepExecutorPorts['deliverSecret'];
|
|
423
|
+
/**
|
|
424
|
+
* Reads back a secret this step delivered on an earlier apply -- needed
|
|
425
|
+
* only to re-sign a mint request when the signing pair was pinned on a
|
|
426
|
+
* run that crashed before the management key itself was minted and
|
|
427
|
+
* delivered (#201). Not needed on the common path, where both happen in
|
|
428
|
+
* the same run.
|
|
429
|
+
*/
|
|
430
|
+
heldKeys?: {
|
|
431
|
+
resolve(ref: string): Promise<string | null>;
|
|
432
|
+
};
|
|
433
|
+
/** Injected for tests. Absent, {@link provisionPlatformAi} builds the real {@link createAiManagementKeyMinter} from the signing key it mints. */
|
|
434
|
+
managementKeyMinter?: AiManagementKeyMinter;
|
|
435
|
+
/** Injected for tests, e.g. against a fake ai host. Defaults to the global `fetch`. */
|
|
436
|
+
fetchImpl?: typeof fetch;
|
|
437
|
+
runtime?: PlatformAiRuntime;
|
|
438
|
+
}
|
|
439
|
+
export interface PlatformAiRuntime {
|
|
440
|
+
sleep?: (ms: number) => Promise<void>;
|
|
441
|
+
now?: () => number;
|
|
442
|
+
/** How long to wait for the migrate job to report healthy (default 10 minutes). */
|
|
443
|
+
migrateTimeoutMs?: number;
|
|
444
|
+
/** Pause between migrate-job status polls (default 5 seconds). */
|
|
445
|
+
migrateIntervalMs?: number;
|
|
446
|
+
/** Tries at handing a secret to the host (default 3): the management key and the signing key both use this. */
|
|
447
|
+
deliverAttempts?: number;
|
|
448
|
+
}
|
|
449
|
+
/** The ref the AI management key is delivered under; the host's `AiOrganisationPort` adapter reads it back. */
|
|
450
|
+
export declare const PLATFORM_AI_MANAGEMENT_KEY_REF = "ai:platform:managementKey";
|
|
451
|
+
/**
|
|
452
|
+
* What Archon's management key may do in ai: exactly `ai`'s own
|
|
453
|
+
* `ARCHON_AI_PERMISSIONS` (`apps/host/scripts/authz/bootstrap-archon.ts`) --
|
|
454
|
+
* configuration only (offerings, provisioning, offers, budgets, usage, cost)
|
|
455
|
+
* plus deleting (archiving) an organisation, never connections, runs or
|
|
456
|
+
* webhooks. ai's `/v1/platform/keys` route refuses any permission outside
|
|
457
|
+
* this same list, so a request for more than this is a 403, not a wider key.
|
|
458
|
+
*/
|
|
459
|
+
export declare const PLATFORM_AI_MANAGEMENT_PERMISSIONS: readonly string[];
|
|
460
|
+
/** The ref the AI signing key's private half (PKCS#8 PEM) is delivered under -- the {@link INTEGRATIONS_MANAGEMENT_KEY_REF} pattern, for ai instead of integrations. */
|
|
461
|
+
export declare const AI_SIGNING_KEY_REF = "ai:platform:signingPrivateKey";
|
|
462
|
+
/** `MANAGEMENT_ISSUER` on the ai runner: whoever signs with the delivered key signs as this issuer. */
|
|
463
|
+
export declare const AI_MANAGEMENT_ISSUER = "foundry";
|
|
464
|
+
/** The key id the public half is pinned under in `MANAGEMENT_PUBLIC_KEYS` (ai's keyring allows `^[A-Za-z0-9_-]{1,100}$`). Fixed, since there is one key until rotation is built. */
|
|
465
|
+
export declare const AI_MANAGEMENT_KEY_ID = "foundry-1";
|
|
466
|
+
/**
|
|
467
|
+
* The real {@link AiManagementKeyMinter}: signs a management assertion with
|
|
468
|
+
* `privateKeyPem` (the PKCS#8 PEM {@link provisionPlatformAi} delivers under
|
|
469
|
+
* {@link AI_SIGNING_KEY_REF} and pins on the runner under `keyId`) and calls
|
|
470
|
+
* ai's `POST /v1/platform/keys`. `audience` must be the exact origin ai's own
|
|
471
|
+
* `APP_URL` resolves to (`https://<platform.ai.hostname>`), since ai checks
|
|
472
|
+
* the claim against that env var, not against the internal address the
|
|
473
|
+
* request itself is sent to.
|
|
474
|
+
*/
|
|
475
|
+
export declare function createAiManagementKeyMinter(options: {
|
|
476
|
+
privateKeyPem: string;
|
|
477
|
+
keyId: string;
|
|
478
|
+
issuer: string;
|
|
479
|
+
audience: string;
|
|
480
|
+
fetchImpl?: typeof fetch;
|
|
481
|
+
now?: () => number;
|
|
482
|
+
}): AiManagementKeyMinter;
|
|
483
|
+
/**
|
|
484
|
+
* #171: everything {@link provisionPlatformFiles} needs beyond
|
|
485
|
+
* {@link PlatformStepExecutionContext}, bundled for the same reason as
|
|
486
|
+
* {@link PlatformAuthDeps}.
|
|
487
|
+
*/
|
|
488
|
+
export interface PlatformFilesDeps {
|
|
489
|
+
coolify: Pick<CoolifyPort, 'findProjectByName' | 'createProject' | 'findOrCreateEnvironment' | 'listServers' | 'requireGithubApp' | 'findApplicationByName' | 'getApplication' | 'createGithubAppApplication' | 'listDatabases' | 'createPostgresDatabase' | 'getDatabase' | 'bulkUpsertEnvs' | 'listEnvs' | 'updateApplication' | 'deploy' | 'raw'>;
|
|
490
|
+
/**
|
|
491
|
+
* Must target the platform's own sign-in (the instance `platform:auth`
|
|
492
|
+
* built), or {@link zitadelForInstance} must be set.
|
|
493
|
+
*/
|
|
494
|
+
zitadel: Pick<ZitadelPort, 'findOrCreateOrg' | 'findOrCreateProject' | 'createApiApp' | 'listApps' | 'deleteApp'>;
|
|
495
|
+
/**
|
|
496
|
+
* Builds the ZITADEL port for the platform's sign-in from its issuer URL
|
|
497
|
+
* (`https://<auth.hostname>`); used instead of {@link zitadel} when set, the
|
|
498
|
+
* same seam {@link PlatformAuthDeps.zitadelForInstance} is.
|
|
499
|
+
*/
|
|
500
|
+
zitadelForInstance?: (issuerUrl: string) => PlatformFilesDeps['zitadel'];
|
|
501
|
+
deliverSecret: FoundryStepExecutorPorts['deliverSecret'];
|
|
502
|
+
/**
|
|
503
|
+
* The keys worker the files host verifies and seals with, from the host's
|
|
504
|
+
* held keys (`KEYS_WORKER_URL`, `KEYS_CREDENTIAL`). The `files-migrate` app
|
|
505
|
+
* never becomes healthy without them, so the step sets both on both apps,
|
|
506
|
+
* and the executor reports the step blocked while this is unset. Never
|
|
507
|
+
* recorded or logged.
|
|
508
|
+
*/
|
|
509
|
+
keys?: {
|
|
510
|
+
workerUrl: string;
|
|
511
|
+
credential: string;
|
|
512
|
+
};
|
|
513
|
+
/** Clock and sleep for the migration wait and delivery retries; real ones by default. */
|
|
514
|
+
runtime?: PlatformFilesRuntime;
|
|
515
|
+
}
|
|
516
|
+
export interface PlatformFilesRuntime {
|
|
517
|
+
sleep?: (ms: number) => Promise<void>;
|
|
518
|
+
now?: () => number;
|
|
519
|
+
/** How long to wait for `files-migrate` to report healthy (default 15 minutes: it builds first). */
|
|
520
|
+
migrateTimeoutMs?: number;
|
|
521
|
+
/** Pause between `files-migrate` status polls (default 10 seconds). */
|
|
522
|
+
migrateIntervalMs?: number;
|
|
523
|
+
/** Tries at handing each secret to the host (default 3). */
|
|
524
|
+
deliverAttempts?: number;
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* #171: how the host signs a files management request with the key this step
|
|
528
|
+
* delivers under {@link FILES_MANAGEMENT_KEY_REF} (files'
|
|
529
|
+
* `signManagementRequest`'s `SigningIdentity`): `issuer` is
|
|
530
|
+
* {@link FILES_MANAGEMENT_ISSUER}, `keyId` is {@link FILES_MANAGEMENT_KEY_ID},
|
|
531
|
+
* `audience` is `https://<platform.files.hostname>` (the files host's
|
|
532
|
+
* `APP_URL`, which this step sets), and `privateKey` is the delivered PKCS#8
|
|
533
|
+
* PEM. Neither constant is a secret. The files host pins the public half
|
|
534
|
+
* through `MANAGEMENT_ISSUER`/`MANAGEMENT_PUBLIC_KEYS`, which this step sets.
|
|
535
|
+
*/
|
|
536
|
+
export declare const FILES_MANAGEMENT_ISSUER = "foundry";
|
|
537
|
+
export declare const FILES_MANAGEMENT_KEY_ID = "foundry-1";
|
|
538
|
+
/** The ref the management private key (PKCS#8 PEM) is delivered under. */
|
|
539
|
+
export declare const FILES_MANAGEMENT_KEY_REF = "files:platform:managementKey";
|
|
540
|
+
/** The ref the files host's introspection client secret is delivered under. */
|
|
541
|
+
export declare const FILES_INTROSPECTION_SECRET_REF = "zitadel:platform:files:introspection";
|
|
542
|
+
/**
|
|
543
|
+
* #174: everything {@link provisionPlatformIntegrations} needs beyond
|
|
544
|
+
* {@link PlatformStepExecutionContext} -- a bundle, like
|
|
545
|
+
* {@link PlatformAuthDeps}, so every earlier caller of
|
|
546
|
+
* {@link FoundryPlatformStepExecutor} keeps compiling with it omitted.
|
|
547
|
+
*/
|
|
548
|
+
export interface PlatformIntegrationsDeps {
|
|
549
|
+
coolify: Pick<CoolifyPort, 'findProjectByName' | 'createProject' | 'findOrCreateEnvironment' | 'listServers' | 'requireGithubApp' | 'findApplicationByName' | 'getApplication' | 'createGithubAppApplication' | 'updateApplication' | 'listDatabases' | 'createPostgresDatabase' | 'getDatabase' | 'bulkUpsertEnvs' | 'listEnvs' | 'deploy' | 'raw'>;
|
|
550
|
+
/** Hands the management private key to the host, once (`FoundryStepExecutorPorts.deliverSecret`'s contract). */
|
|
551
|
+
deliverSecret: FoundryStepExecutorPorts['deliverSecret'];
|
|
552
|
+
/**
|
|
553
|
+
* The host's own `@wtfalch/keys` settings, written to the apps' env (the
|
|
554
|
+
* migrate app takes the first two). Without every value the planner reports
|
|
555
|
+
* the step blocked: the runner's health check needs them, so the step would
|
|
556
|
+
* never finish. Secrets: only ever passed to `bulkUpsertEnvs`.
|
|
557
|
+
*/
|
|
558
|
+
keysEnv: Readonly<Partial<Record<IntegrationsKeysEnvName, string>>>;
|
|
559
|
+
runtime?: PlatformIntegrationsRuntime;
|
|
560
|
+
}
|
|
561
|
+
export declare const INTEGRATIONS_KEYS_ENV_NAMES: readonly ["KEYS_WORKER_URL", "KEYS_CREDENTIAL", "KEYS_VERIFY_KEYS", "KEYS_AUDIT_TOKEN"];
|
|
562
|
+
export type IntegrationsKeysEnvName = (typeof INTEGRATIONS_KEYS_ENV_NAMES)[number];
|
|
563
|
+
export interface PlatformIntegrationsRuntime {
|
|
564
|
+
sleep?: (ms: number) => Promise<void>;
|
|
565
|
+
now?: () => number;
|
|
566
|
+
/** How long to wait for the migrate app to report healthy (default 20 minutes: it builds the whole repo first). */
|
|
567
|
+
migrateTimeoutMs?: number;
|
|
568
|
+
/** Pause between polls of the migrate app's status (default 10 seconds). */
|
|
569
|
+
migratePollMs?: number;
|
|
570
|
+
/** Tries at handing the management key to the host (default 3). */
|
|
571
|
+
deliverAttempts?: number;
|
|
572
|
+
}
|
|
573
|
+
/** The ref the management private key (PKCS#8 PEM) is delivered under. */
|
|
574
|
+
export declare const INTEGRATIONS_MANAGEMENT_KEY_REF = "integrations:management:privateKey";
|
|
575
|
+
/** `MANAGEMENT_ISSUER` on the integrations host: whoever signs with the delivered key signs as this issuer. */
|
|
576
|
+
export declare const INTEGRATIONS_MANAGEMENT_ISSUER = "foundry";
|
|
577
|
+
/**
|
|
578
|
+
* The key id the public half is pinned under in `MANAGEMENT_PUBLIC_KEYS`
|
|
579
|
+
* (the host's keyring allows `^[A-Za-z0-9_-]{1,100}$`). A signer names it in
|
|
580
|
+
* its signed request; fixed, since there is one key until rotation is built.
|
|
581
|
+
*/
|
|
582
|
+
export declare const INTEGRATIONS_MANAGEMENT_KEY_ID = "foundry-1";
|
|
583
|
+
/**
|
|
584
|
+
* The real {@link PlatformCloudflareReader}: the platform's apex zone as
|
|
585
|
+
* `planApexZone` expects to see it, and nothing else. The zone's DNS records
|
|
586
|
+
* and tunnels belong to no declared platform step yet, so listing them would
|
|
587
|
+
* only report them unmanaged.
|
|
588
|
+
*
|
|
589
|
+
* The zone is fetched by the id the last apply recorded when there is one,
|
|
590
|
+
* because Cloudflare's zone list can lag a create; with no recorded id it is
|
|
591
|
+
* found by apex.
|
|
592
|
+
*/
|
|
593
|
+
export declare class ProvidersPlatformCloudflareReader implements PlatformCloudflareReader {
|
|
594
|
+
private readonly client;
|
|
595
|
+
private readonly platformStore;
|
|
596
|
+
constructor(client: Pick<CloudflareClient, 'findZoneByApex' | 'getZone'>, platformStore: Pick<PlatformStore, 'get' | 'getServiceIds'>);
|
|
597
|
+
listResources(): Promise<LiveResource[]>;
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* #173: the real {@link PlatformCoolifyReader} for the jobs scheduler app --
|
|
601
|
+
* the recorded-id half of `ProvidersPlatformCloudflareReader`'s own strategy
|
|
602
|
+
* above, with no list-by-name fallback: unlike a Cloudflare zone (which
|
|
603
|
+
* exists before foundry ever touches it), the scheduler app has no identity
|
|
604
|
+
* to adopt until this package creates it, so "nothing recorded yet" and
|
|
605
|
+
* "nothing live yet" are the same state here. A recorded id Coolify no
|
|
606
|
+
* longer has (deleted, or moved) reads as not-live, the same "stale id never
|
|
607
|
+
* blocks planning for good" rule {@link recordedZone} follows.
|
|
608
|
+
*/
|
|
609
|
+
export declare class ProvidersPlatformCoolifyReader implements PlatformCoolifyReader {
|
|
610
|
+
private readonly client;
|
|
611
|
+
private readonly platformStore;
|
|
612
|
+
constructor(client: Pick<CoolifyPort, 'getApplication'>, platformStore: Pick<PlatformStore, 'getServiceIds'>);
|
|
613
|
+
listResources(): Promise<LiveResource[]>;
|
|
614
|
+
}
|
|
615
|
+
export {};
|