@wtfalch/foundry 0.33.1 → 0.35.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/bootstrap/bootstrap.d.ts +1 -1
- package/dist/bootstrap/bootstrap.js +49 -11
- package/dist/bootstrap/declare.d.ts +12 -5
- package/dist/bootstrap/declare.js +33 -18
- package/dist/bootstrap/versions.d.ts +24 -0
- package/dist/bootstrap/versions.js +46 -0
- package/dist/calls-sfu.d.ts +10 -0
- package/dist/calls-sfu.js +44 -3
- package/dist/index.d.ts +21 -8
- package/dist/index.js +11 -4
- package/dist/migrations/0051_org_services.sql +13 -0
- package/dist/migrations/0052_platform_mail_relay.sql +8 -0
- package/dist/migrations/0053_heartbeats_provider.sql +9 -0
- package/dist/migrations/0053_org_version_pin.sql +6 -0
- package/dist/org.d.ts +76 -12
- package/dist/org.js +136 -20
- package/dist/plan/backup-gate.d.ts +93 -0
- package/dist/plan/backup-gate.js +261 -0
- package/dist/plan/broker-entries.d.ts +9 -1
- package/dist/plan/broker-entries.js +20 -3
- package/dist/plan/build-record.d.ts +56 -0
- package/dist/plan/build-record.js +109 -0
- package/dist/plan/calls-matrix.d.ts +9 -0
- package/dist/plan/calls-matrix.js +18 -1
- package/dist/plan/cloudflared-connector.d.ts +7 -1
- package/dist/plan/cloudflared-connector.js +15 -2
- package/dist/plan/company-backups.d.ts +31 -0
- package/dist/plan/company-backups.js +101 -0
- package/dist/plan/deploy-health.d.ts +12 -2
- package/dist/plan/deploy-health.js +3 -1
- package/dist/plan/destinations.d.ts +73 -0
- package/dist/plan/destinations.js +217 -0
- package/dist/plan/diff.d.ts +46 -8
- package/dist/plan/diff.js +367 -126
- package/dist/plan/edge-access.d.ts +15 -0
- package/dist/plan/edge-access.js +21 -2
- package/dist/plan/executor.d.ts +276 -5
- package/dist/plan/executor.js +1111 -109
- package/dist/plan/health-check-settings.d.ts +28 -0
- package/dist/plan/health-check-settings.js +36 -0
- package/dist/plan/health-gaps.d.ts +33 -0
- package/dist/plan/health-gaps.js +89 -0
- package/dist/plan/health-targets.d.ts +1 -3
- package/dist/plan/health-targets.js +36 -36
- package/dist/plan/held-key-refs.d.ts +15 -0
- package/dist/plan/held-key-refs.js +15 -0
- package/dist/plan/hostnames.d.ts +10 -0
- package/dist/plan/hostnames.js +14 -0
- package/dist/plan/index.d.ts +19 -6
- package/dist/plan/index.js +10 -3
- package/dist/plan/live.d.ts +11 -2
- package/dist/plan/live.js +16 -2
- package/dist/plan/locked-hostnames.d.ts +7 -3
- package/dist/plan/locked-hostnames.js +25 -4
- package/dist/plan/login-host.d.ts +61 -12
- package/dist/plan/login-host.js +99 -11
- package/dist/plan/mail-relay.d.ts +139 -0
- package/dist/plan/mail-relay.js +400 -0
- package/dist/plan/network-move.d.ts +20 -0
- package/dist/plan/network-move.js +41 -0
- package/dist/plan/oikos-ingress.d.ts +111 -0
- package/dist/plan/oikos-ingress.js +231 -0
- package/dist/plan/org-names.d.ts +30 -0
- package/dist/plan/org-names.js +108 -0
- package/dist/plan/own-diff.d.ts +170 -0
- package/dist/plan/own-diff.js +440 -0
- package/dist/plan/own-dns.d.ts +6 -0
- package/dist/plan/own-dns.js +15 -0
- package/dist/plan/own-hostnames.d.ts +27 -0
- package/dist/plan/own-hostnames.js +24 -0
- package/dist/plan/own-limits.d.ts +213 -0
- package/dist/plan/own-limits.js +232 -0
- package/dist/plan/own-services.d.ts +193 -0
- package/dist/plan/own-services.js +169 -0
- package/dist/plan/own-sfu.d.ts +69 -0
- package/dist/plan/own-sfu.js +256 -0
- package/dist/plan/plan-apply.d.ts +121 -10
- package/dist/plan/plan-apply.js +357 -18
- package/dist/plan/platform-diff.d.ts +155 -11
- package/dist/plan/platform-diff.js +271 -66
- package/dist/plan/platform-org.d.ts +107 -4
- package/dist/plan/platform-org.js +175 -3
- package/dist/plan/platform-providers.d.ts +474 -81
- package/dist/plan/platform-providers.js +1407 -560
- package/dist/plan/proxy-labels.d.ts +57 -11
- package/dist/plan/proxy-labels.js +94 -14
- package/dist/plan/run-options.d.ts +19 -0
- package/dist/plan/run-options.js +46 -0
- package/dist/plan/sign-in-org.d.ts +1 -1
- package/dist/plan/sign-in-org.js +20 -2
- package/dist/plan/zitadel-login-policy.d.ts +1 -1
- package/dist/plan/zitadel-login-policy.js +77 -9
- package/dist/plan/zitadel-system-api.d.ts +22 -1
- package/dist/plan/zitadel-system-api.js +49 -8
- package/dist/platform.d.ts +40 -0
- package/dist/platform.js +16 -0
- package/dist/provision.d.ts +2 -0
- package/dist/provision.js +3 -1
- package/dist/slug.d.ts +3 -1
- package/dist/slug.js +26 -3
- package/dist/store/postgres.js +93 -2
- package/dist/store/schema.d.ts +87 -0
- package/dist/store/schema.js +9 -0
- package/package.json +3 -3
- package/versions.digests.json +11 -0
- package/versions.json +3 -1
|
@@ -10,6 +10,10 @@ export interface AccessAppSummary {
|
|
|
10
10
|
id: string;
|
|
11
11
|
name: string;
|
|
12
12
|
domain: string;
|
|
13
|
+
/** The identity providers the app accepts; `[]` means Cloudflare lists none (any provider). */
|
|
14
|
+
allowedIdps: string[];
|
|
15
|
+
/** Whether a visitor goes straight to the one provider. */
|
|
16
|
+
autoRedirectToIdentity: boolean;
|
|
13
17
|
}
|
|
14
18
|
/**
|
|
15
19
|
* Foundry's Cloudflare Access calls. Every Access call in foundry goes through
|
|
@@ -19,13 +23,24 @@ export interface AccessAppSummary {
|
|
|
19
23
|
export interface AccessPort {
|
|
20
24
|
/** Every live Access app foundry may use: the platform's own, and ones nobody adopted yet. */
|
|
21
25
|
listAccessApps(): Promise<AccessAppSummary[]>;
|
|
26
|
+
/**
|
|
27
|
+
* Edge keeps `allowedIdps` of an app that already exists: a repeat create
|
|
28
|
+
* only reports the live value, so a caller compares it from
|
|
29
|
+
* {@link listAccessApps} and rebuilds the app on a difference.
|
|
30
|
+
*/
|
|
22
31
|
createAccessApp(app: {
|
|
23
32
|
name: string;
|
|
24
33
|
domain: string;
|
|
25
34
|
type: 'self_hosted';
|
|
35
|
+
allowedIdps?: string[];
|
|
36
|
+
autoRedirectToIdentity?: boolean;
|
|
26
37
|
}): Promise<{
|
|
27
38
|
id: string;
|
|
28
39
|
}>;
|
|
40
|
+
/** The account's one-time-PIN identity provider (edge 0.13.0 finds or makes it). Safe to repeat. */
|
|
41
|
+
ensureOneTimePinProvider(): Promise<{
|
|
42
|
+
id: string;
|
|
43
|
+
}>;
|
|
29
44
|
listAccessPolicies(appId: string): Promise<Array<{
|
|
30
45
|
id: string;
|
|
31
46
|
name: string;
|
package/dist/plan/edge-access.js
CHANGED
|
@@ -31,6 +31,7 @@ export function createEdgeAccess(client, options = {}) {
|
|
|
31
31
|
adopt: (input) => retrying(() => client.adopt(input)),
|
|
32
32
|
readiness: () => retrying(() => client.readiness()),
|
|
33
33
|
ensureProvider: (input) => retrying(() => client.ensureProvider(input)),
|
|
34
|
+
oneTimePinProvider: () => retrying(() => client.oneTimePinProvider()),
|
|
34
35
|
createGate: (input) => retrying(() => client.createGate(input)),
|
|
35
36
|
replaceGateHostnames: (id, input) => retrying(() => client.replaceGateHostnames(id, input)),
|
|
36
37
|
deleteGate: (id) => retrying(() => client.deleteGate(id)),
|
|
@@ -80,13 +81,27 @@ export function createEdgeAccess(client, options = {}) {
|
|
|
80
81
|
const listAccessApps = async () => {
|
|
81
82
|
const { apps } = await access.list();
|
|
82
83
|
remember(apps);
|
|
83
|
-
return apps.map((a) => ({
|
|
84
|
+
return apps.map((a) => ({
|
|
85
|
+
id: a.id,
|
|
86
|
+
name: a.name,
|
|
87
|
+
domain: a.domain,
|
|
88
|
+
allowedIdps: a.allowedIdps,
|
|
89
|
+
autoRedirectToIdentity: a.autoRedirectToIdentity,
|
|
90
|
+
}));
|
|
84
91
|
};
|
|
85
92
|
return {
|
|
86
93
|
listAccessApps,
|
|
87
94
|
async createAccessApp(app) {
|
|
88
95
|
try {
|
|
89
|
-
const created = await writeSlot(() => access.create({
|
|
96
|
+
const created = await writeSlot(() => access.create({
|
|
97
|
+
name: app.name,
|
|
98
|
+
domain: app.domain,
|
|
99
|
+
type: app.type,
|
|
100
|
+
...(app.allowedIdps === undefined ? {} : { allowedIdps: app.allowedIdps }),
|
|
101
|
+
...(app.autoRedirectToIdentity === undefined
|
|
102
|
+
? {}
|
|
103
|
+
: { autoRedirectToIdentity: app.autoRedirectToIdentity }),
|
|
104
|
+
}));
|
|
90
105
|
return { id: created.id };
|
|
91
106
|
}
|
|
92
107
|
catch (error) {
|
|
@@ -155,6 +170,10 @@ export function createEdgeAccess(client, options = {}) {
|
|
|
155
170
|
const { authDomain } = await access.readiness();
|
|
156
171
|
return { authDomain };
|
|
157
172
|
},
|
|
173
|
+
async ensureOneTimePinProvider() {
|
|
174
|
+
const { id } = await access.oneTimePinProvider();
|
|
175
|
+
return { id };
|
|
176
|
+
},
|
|
158
177
|
async ensureProvider(input) {
|
|
159
178
|
const { id } = await writeSlot(() => access.ensureProvider(input));
|
|
160
179
|
return { id };
|
package/dist/plan/executor.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { type EdgePlatformTunnels, type TunnelPort } from './edge-tunnels.js';
|
|
|
12
12
|
import { type EdgePlatformZones, type ZonePort } from './edge-zones.js';
|
|
13
13
|
import { type LoginHostBindingsPort } from './login-host.js';
|
|
14
14
|
import { type MailCopyPort } from './mail-copy.js';
|
|
15
|
+
import { type OwnServicesPort } from './own-services.js';
|
|
15
16
|
import type { DestroyTarget, StepDestroyer, StepExecutionContext, StepExecutor } from './plan-apply.js';
|
|
16
17
|
import type { EdgePortName } from './platform-org.js';
|
|
17
18
|
export declare function coolifyProjectMark(org: Pick<DeclaredOrg, 'id'>): string;
|
|
@@ -53,6 +54,8 @@ export interface CoolifyPort {
|
|
|
53
54
|
*/
|
|
54
55
|
listResources?(projectUuid: string): Promise<Array<{
|
|
55
56
|
kind: string;
|
|
57
|
+
key?: string;
|
|
58
|
+
config?: Record<string, unknown>;
|
|
56
59
|
}>>;
|
|
57
60
|
findOrCreateEnvironment(projectUuid: string, name: string): Promise<{
|
|
58
61
|
environment: {
|
|
@@ -107,10 +110,19 @@ export interface CoolifyPort {
|
|
|
107
110
|
custom_network_aliases?: string | null;
|
|
108
111
|
health_check_enabled?: boolean;
|
|
109
112
|
ports_exposes?: string;
|
|
113
|
+
/** Coolify's own record of the resource limits (`plan/own-limits.ts`). */
|
|
114
|
+
limits_memory?: string;
|
|
115
|
+
limits_cpus?: string;
|
|
116
|
+
git_repository?: string;
|
|
117
|
+
is_auto_deploy_enabled?: boolean;
|
|
118
|
+
/** Coolify's record of the run options, where the capability options live (`plan/run-options.ts`). */
|
|
119
|
+
custom_docker_run_options?: string | null;
|
|
110
120
|
}>;
|
|
111
121
|
createGithubAppApplication(input: {
|
|
112
122
|
project_uuid: string;
|
|
113
123
|
server_uuid: string;
|
|
124
|
+
/** The Docker network the app joins (`plan/destinations.ts`); absent: the server's default. */
|
|
125
|
+
destination_uuid?: string;
|
|
114
126
|
environment_name: string;
|
|
115
127
|
environment_uuid: string;
|
|
116
128
|
github_app_uuid: string;
|
|
@@ -121,6 +133,8 @@ export interface CoolifyPort {
|
|
|
121
133
|
domains?: string;
|
|
122
134
|
/** Selects which of two Dockerfiles in one repo to build (`cmsSite`'s own site/cms apps). */
|
|
123
135
|
dockerfile_location?: string;
|
|
136
|
+
/** Extra `docker run` options; the capability options are added by `plan/own-limits.ts`. */
|
|
137
|
+
custom_docker_run_options?: string;
|
|
124
138
|
/** The port the app listens on (`HostingEntry.port`). */
|
|
125
139
|
ports_exposes?: string;
|
|
126
140
|
/** `false` for an internal entry: Coolify makes no domain for it. */
|
|
@@ -138,6 +152,7 @@ export interface CoolifyPort {
|
|
|
138
152
|
createPostgresDatabase(input: {
|
|
139
153
|
project_uuid: string;
|
|
140
154
|
server_uuid: string;
|
|
155
|
+
destination_uuid?: string;
|
|
141
156
|
environment_name: string;
|
|
142
157
|
environment_uuid: string;
|
|
143
158
|
name: string;
|
|
@@ -162,6 +177,8 @@ export interface CoolifyPort {
|
|
|
162
177
|
uuid: string;
|
|
163
178
|
internal_db_url?: string;
|
|
164
179
|
status?: string;
|
|
180
|
+
limits_memory?: string;
|
|
181
|
+
limits_cpus?: string;
|
|
165
182
|
}>;
|
|
166
183
|
/**
|
|
167
184
|
* Every database Coolify holds. `coolifyDatabase` finds its own by name and
|
|
@@ -217,6 +234,16 @@ export interface CoolifyPort {
|
|
|
217
234
|
deleteEnv(applicationUuid: string, envUuid: string): Promise<unknown>;
|
|
218
235
|
/** Escape hatch (`CoolifyClient.raw`) — used for stop/start, which have no typed method yet. */
|
|
219
236
|
raw(method: string, path: string, body?: unknown): Promise<unknown>;
|
|
237
|
+
/**
|
|
238
|
+
* F6: the dumps one scheduled backup has run, newest first. A host may wrap
|
|
239
|
+
* `raw('GET', '/databases/{uuid}/backups/{backup}/executions')`; absent, the
|
|
240
|
+
* backup gate (`backup-gate.ts`) makes that call through `raw` itself.
|
|
241
|
+
*/
|
|
242
|
+
listDatabaseBackupExecutions?(databaseUuid: string, backupUuid: string): Promise<Array<{
|
|
243
|
+
uuid: string;
|
|
244
|
+
status: string;
|
|
245
|
+
created_at: string;
|
|
246
|
+
}>>;
|
|
220
247
|
/**
|
|
221
248
|
* Persistent (named volume) and file storages already attached to an
|
|
222
249
|
* application (#75) — `ensureLogVolume` reads this before creating one, so
|
|
@@ -257,6 +284,7 @@ export interface CoolifyPort {
|
|
|
257
284
|
createDockerImageApplication(input: {
|
|
258
285
|
project_uuid: string;
|
|
259
286
|
server_uuid: string;
|
|
287
|
+
destination_uuid?: string;
|
|
260
288
|
environment_name: string;
|
|
261
289
|
environment_uuid: string;
|
|
262
290
|
docker_registry_image_name: string;
|
|
@@ -288,12 +316,14 @@ export interface CoolifyPort {
|
|
|
288
316
|
createDockerfileApplication(input: {
|
|
289
317
|
project_uuid: string;
|
|
290
318
|
server_uuid: string;
|
|
319
|
+
destination_uuid?: string;
|
|
291
320
|
environment_name: string;
|
|
292
321
|
environment_uuid: string;
|
|
293
322
|
dockerfile: string;
|
|
294
323
|
name?: string;
|
|
295
324
|
description?: string;
|
|
296
325
|
instant_deploy?: boolean;
|
|
326
|
+
custom_docker_run_options?: string;
|
|
297
327
|
}): Promise<{
|
|
298
328
|
uuid: string;
|
|
299
329
|
}>;
|
|
@@ -425,6 +455,17 @@ export interface CoolifyPort {
|
|
|
425
455
|
getDeployment(deploymentUuid: string): Promise<{
|
|
426
456
|
status: string;
|
|
427
457
|
}>;
|
|
458
|
+
/**
|
|
459
|
+
* An application's deployment history, newest first -- matches
|
|
460
|
+
* `CoolifyClient.listApplicationDeployments`. `build-record.ts` finds the
|
|
461
|
+
* deployment a deploy started by its uuid and records its commit. Optional:
|
|
462
|
+
* a port without it records no built commit.
|
|
463
|
+
*/
|
|
464
|
+
listApplicationDeployments?(uuid: string, take?: number): Promise<Array<{
|
|
465
|
+
deployment_uuid: string;
|
|
466
|
+
commit?: string;
|
|
467
|
+
status: string;
|
|
468
|
+
}>>;
|
|
428
469
|
}
|
|
429
470
|
/**
|
|
430
471
|
* The Cloudflare zone calls the platform steps make directly, shaped to match
|
|
@@ -933,6 +974,21 @@ export interface ZitadelPort {
|
|
|
933
974
|
closeSelfRegistration(orgId: string): Promise<{
|
|
934
975
|
changed: boolean;
|
|
935
976
|
}>;
|
|
977
|
+
/**
|
|
978
|
+
* Makes the org require a second factor at sign-in: `forceMfa` on, no
|
|
979
|
+
* registration, no external identity provider, username and password kept
|
|
980
|
+
* (the invite and a password reset need them), passkeys allowed, and
|
|
981
|
+
* `mfaInitSkipLifetime` zero so enrolment cannot be skipped. The second
|
|
982
|
+
* factors are an authenticator app and a security key, the multi factor a
|
|
983
|
+
* user-verified passkey; every other factor (email or SMS code) is removed.
|
|
984
|
+
* An org on the instance default gets its own policy. Idempotent: a policy
|
|
985
|
+
* already so set is no write. A failure halfway is repaired by a repeat,
|
|
986
|
+
* which reads the policy again. `zitadel-login-policy.ts` is the real
|
|
987
|
+
* implementation.
|
|
988
|
+
*/
|
|
989
|
+
enforceSecondFactor(orgId: string): Promise<{
|
|
990
|
+
changed: boolean;
|
|
991
|
+
}>;
|
|
936
992
|
/** Grants another ZITADEL org access to a project (plan gap A9). Idempotent. */
|
|
937
993
|
findOrCreateProjectGrant(orgId: string, projectId: string, grantedOrgId: string): Promise<{
|
|
938
994
|
created: boolean;
|
|
@@ -1144,6 +1200,16 @@ export interface ZitadelListAppsRawApp {
|
|
|
1144
1200
|
* an operator's `MANAGEMENT_PUBLIC_KEYS`/`MANAGEMENT_ISSUER` pins.
|
|
1145
1201
|
*/
|
|
1146
1202
|
export interface FilesOrganisationPort {
|
|
1203
|
+
/**
|
|
1204
|
+
* An own files host's R2 storage key, sent to that host once (the files route
|
|
1205
|
+
* `PUT /v1/manage/r2-credentials`). Absent on a port that cannot: the own R2 key step is then
|
|
1206
|
+
* blocked by name. The same pair may be sent again after a failed send.
|
|
1207
|
+
*/
|
|
1208
|
+
storeR2Credentials?(input: {
|
|
1209
|
+
accessKeyId: string;
|
|
1210
|
+
secretAccessKey: string;
|
|
1211
|
+
tokenId: string;
|
|
1212
|
+
}): Promise<void>;
|
|
1147
1213
|
provisionOrganisation(input: {
|
|
1148
1214
|
organisationId: string;
|
|
1149
1215
|
ceilingBytes: string | null;
|
|
@@ -1447,6 +1513,12 @@ export interface PlatformOrgPortSource {
|
|
|
1447
1513
|
}
|
|
1448
1514
|
export interface FoundryStepExecutorPorts {
|
|
1449
1515
|
coolify: CoolifyPort;
|
|
1516
|
+
/**
|
|
1517
|
+
* Ports for the instances an own org (`services.own`) runs itself. Without
|
|
1518
|
+
* it, a step for an own org is refused rather than run against the shared
|
|
1519
|
+
* services, and the planner reports the org's own services blocked.
|
|
1520
|
+
*/
|
|
1521
|
+
ownServices?: OwnServicesPort;
|
|
1450
1522
|
zitadel: ZitadelPort;
|
|
1451
1523
|
mail: MailPort;
|
|
1452
1524
|
lettermint: LettermintPort;
|
|
@@ -1574,7 +1646,7 @@ export interface FoundryStepExecutorPorts {
|
|
|
1574
1646
|
deliverSecret(ref: string, secret: string): Promise<void>;
|
|
1575
1647
|
/**
|
|
1576
1648
|
* Archon's held keys, for an entry env from `sharedSecret` (read, else
|
|
1577
|
-
* generate and `put`)
|
|
1649
|
+
* generate and `put`). Optional: without
|
|
1578
1650
|
* it, or without `put`, such an env is omitted and the plan reports it
|
|
1579
1651
|
* blocked. A value goes to the entry's env and nowhere else.
|
|
1580
1652
|
*/
|
|
@@ -1590,6 +1662,27 @@ export interface FoundryStepExecutorPorts {
|
|
|
1590
1662
|
* passes the same store instance it constructs `FoundryPlanApply` with.
|
|
1591
1663
|
*/
|
|
1592
1664
|
declaredOrgStore: Pick<DeclaredOrgStore, 'findOrgIdForZone'>;
|
|
1665
|
+
/**
|
|
1666
|
+
* The `<host>:<port>` the shared mail relay dials for a company's mail server, given the Coolify
|
|
1667
|
+
* server's address and the host port the server publishes. Absent: `<server address>:<port>`. A
|
|
1668
|
+
* host whose containers cannot reach the server by its address (the local stack, where it is a
|
|
1669
|
+
* loopback address) supplies its own. The answer is checked like any relay target
|
|
1670
|
+
* (`normalizeMailTarget`): a host name or an IPv4 address and a port, nothing else.
|
|
1671
|
+
*/
|
|
1672
|
+
mailRelayTarget?(input: {
|
|
1673
|
+
serverAddress: string | undefined;
|
|
1674
|
+
port: number;
|
|
1675
|
+
}): string;
|
|
1676
|
+
/**
|
|
1677
|
+
* The address of the server's Docker bridge gateway (an IPv4 address, `172.17.0.1` unless the
|
|
1678
|
+
* daemon's `bip` says otherwise). With it a company's Stalwart publishes SMTP as
|
|
1679
|
+
* `<address>:<port>:25`, which Coolify 4.3.23 passes to Docker unchanged, so the port is reachable
|
|
1680
|
+
* from containers on the server but not on the server's public address; the relay is given
|
|
1681
|
+
* `<address>:<port>` unless `mailRelayTarget` says otherwise. Absent: `<port>:25` on every interface,
|
|
1682
|
+
* and the host firewall must close the range (`docs/runbooks/network-move.md`). The local stack
|
|
1683
|
+
* leaves it unset.
|
|
1684
|
+
*/
|
|
1685
|
+
mailBindAddress?: string;
|
|
1593
1686
|
/**
|
|
1594
1687
|
* #543: clock, sleep and `fetch` for the health-check wait every company
|
|
1595
1688
|
* app's own redeploy now goes through (`deploy-health.ts`'s
|
|
@@ -1616,9 +1709,8 @@ export declare class NotBuiltError extends Error {
|
|
|
1616
1709
|
* `cms.<domain>` -- the cms studio's own hostname (`cmsSite` below), apart
|
|
1617
1710
|
* from the public site itself at `org.domain` (used directly there). Called
|
|
1618
1711
|
* from `cmsSite` and from `zitadelApp` for the fixed `cms` client (#139),
|
|
1619
|
-
* both of which only run once `org.site.enabled` is true
|
|
1620
|
-
*
|
|
1621
|
-
* {@link tunnelIngressHostnames}'s own doc comment cites.
|
|
1712
|
+
* both of which only run once `org.site.enabled` is true. A company without
|
|
1713
|
+
* a domain has its studio at `<slug>-cms.oikos.host` (`cmsStudioHostnameFor`).
|
|
1622
1714
|
*/
|
|
1623
1715
|
export declare function cmsHostnameFor(org: DeclaredOrg): string;
|
|
1624
1716
|
/**
|
|
@@ -1641,8 +1733,24 @@ export declare function callsHostnameFor(org: DeclaredOrg): string;
|
|
|
1641
1733
|
* this file's hostname rules.
|
|
1642
1734
|
*/
|
|
1643
1735
|
export declare function healthUrls(org: DeclaredOrg): string[];
|
|
1736
|
+
/**
|
|
1737
|
+
* Every app on the Coolify box shares one Traefik reverse proxy, and Coolify
|
|
1738
|
+
* itself points that proxy's routing at whatever `domains` an application
|
|
1739
|
+
* declares (`coolifyApp`'s `appHostname(org)`) -- so a tunnel's ingress only
|
|
1740
|
+
* has to reach the proxy, never a specific app container.
|
|
1741
|
+
* `wtfalch/app-template`'s bootstrap (`bootstrap/steps/cf-tunnel.ts`, prior
|
|
1742
|
+
* art for the exact org this file's `cloudflareTunnel` implements, cited by
|
|
1743
|
+
* https://github.com/wtfalch/foundry/issues/71) names this same constant and
|
|
1744
|
+
* value.
|
|
1745
|
+
*/
|
|
1746
|
+
export declare const COOLIFY_PROXY_ORIGIN = "http://coolify-proxy:80";
|
|
1644
1747
|
/** The one Access policy foundry puts on a platform Access app (#84). */
|
|
1645
1748
|
export declare const ACCESS_POLICY_NAME = "foundry: allowed emails";
|
|
1749
|
+
export declare const LOG_DIR = "/app/logs";
|
|
1750
|
+
/** The company's Coolify destination (its Docker network `co-<slug>`), recorded when its first resource is created. */
|
|
1751
|
+
export declare const COOLIFY_DESTINATION_SCOPE_KEY = "coolify_destination";
|
|
1752
|
+
export declare const CHAT_REKEY_SYNAPSE_DB_SCOPE_KEY = "coolify_chat_rekey_synapse_database";
|
|
1753
|
+
export declare const CHAT_REKEY_MAS_DB_SCOPE_KEY = "coolify_chat_rekey_mas_database";
|
|
1646
1754
|
/**
|
|
1647
1755
|
* Whether an application's own Coolify `status` (`` `running:healthy`,
|
|
1648
1756
|
* `exited:unhealthy` `` -- `@wtfalch/providers`' own doc comment) means it is
|
|
@@ -1683,7 +1791,6 @@ export declare const databaseStartWait: {
|
|
|
1683
1791
|
graceMs: number;
|
|
1684
1792
|
};
|
|
1685
1793
|
export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer {
|
|
1686
|
-
private readonly ports;
|
|
1687
1794
|
private edgeDns;
|
|
1688
1795
|
private edgeTunnels;
|
|
1689
1796
|
private edgeAccess;
|
|
@@ -1692,13 +1799,50 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
|
|
|
1692
1799
|
private edgeWriteSlot;
|
|
1693
1800
|
/** Secrets this executor minted or was handed; see `StepExecutor.secretsHandled`. */
|
|
1694
1801
|
private readonly handledSecrets;
|
|
1802
|
+
private readonly ports;
|
|
1803
|
+
private get rawCoolify();
|
|
1695
1804
|
constructor(ports: FoundryStepExecutorPorts);
|
|
1696
1805
|
secretsHandled(): readonly string[];
|
|
1806
|
+
/**
|
|
1807
|
+
* An own org's step runs on a copy of this executor whose ZITADEL, mail,
|
|
1808
|
+
* broker, files, jobs and calls ports are the org's own. A port the org has
|
|
1809
|
+
* not got yet is absent or throws, never the shared one.
|
|
1810
|
+
*/
|
|
1811
|
+
private executeScoped;
|
|
1812
|
+
/** Whether `ensureOwnAuthMailToken` can mint: the same two inputs it blocks on. */
|
|
1813
|
+
ownMailKeyMintable(): boolean;
|
|
1814
|
+
/** What the executor's own files dependencies supply, for the planner's blocked lines. */
|
|
1815
|
+
ownFilesDeps(): {
|
|
1816
|
+
keys: boolean;
|
|
1817
|
+
bucket: boolean;
|
|
1818
|
+
};
|
|
1819
|
+
/** Which ports the own files storage key step needs this executor has, for the planner's blocked line. */
|
|
1820
|
+
ownFilesR2Ports(org: DeclaredOrg, recordedIds: Readonly<Record<string, string>>): Promise<{
|
|
1821
|
+
r2Keys: boolean;
|
|
1822
|
+
storeR2Credentials: boolean;
|
|
1823
|
+
}>;
|
|
1824
|
+
/** Whether the host's scoped ports for this own org include a files-organisation port. A held key still missing counts as no port. */
|
|
1825
|
+
ownFilesOrganisationPort(org: DeclaredOrg, recordedIds: Readonly<Record<string, string>>): Promise<boolean>;
|
|
1826
|
+
/** Whether the host's scoped ports for this own org include a broker port: the company's own broker, which its broker entries are written to. */
|
|
1827
|
+
ownBrokerPort(org: DeclaredOrg, recordedIds: Readonly<Record<string, string>>): Promise<boolean>;
|
|
1697
1828
|
/** #555: the edge-routed ports this executor was built with itself (they win over edge). */
|
|
1698
1829
|
ownEdgePorts(): Partial<Record<EdgePortName, boolean>>;
|
|
1699
1830
|
execute(action: StepAction, context: StepExecutionContext): Promise<{
|
|
1700
1831
|
resourceId?: string;
|
|
1701
1832
|
} | undefined>;
|
|
1833
|
+
private run;
|
|
1834
|
+
/**
|
|
1835
|
+
* The company's destination on `serverUuid`: its Docker network `co-<slug>`, made when absent and
|
|
1836
|
+
* recorded as {@link COOLIFY_DESTINATION_SCOPE_KEY}. Called before the first create of a step;
|
|
1837
|
+
* a server that already holds too many networks fails here (`DestinationCapacityError`), before
|
|
1838
|
+
* the destination or the resource exists. A crash after the destination is made and before it is
|
|
1839
|
+
* recorded leaves a destination the next run finds by its network name, so a re-run converges.
|
|
1840
|
+
* Each Coolify call has a 60 second timeout (`destinations.ts`).
|
|
1841
|
+
*/
|
|
1842
|
+
private companyDestination;
|
|
1843
|
+
/** `coolify` for an own-services step: its creates go to the company's destination. */
|
|
1844
|
+
private placedForCompany;
|
|
1845
|
+
private dispatch;
|
|
1702
1846
|
/**
|
|
1703
1847
|
* `plan/diff.ts`'s `planAccess` (#246): puts a `hosting.access` rule's
|
|
1704
1848
|
* hostname behind a Cloudflare Access app named
|
|
@@ -1922,6 +2066,17 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
|
|
|
1922
2066
|
private findRecreatedApp;
|
|
1923
2067
|
private ensureZitadelProject;
|
|
1924
2068
|
private ensureCoolifyProject;
|
|
2069
|
+
/**
|
|
2070
|
+
* `plan/network-move.ts`'s `planNetworkMove`: a company built on the shared network is moved to its
|
|
2071
|
+
* own. Coolify cannot move a resource in production, so every application and database in the
|
|
2072
|
+
* company's project that foundry did not adopt is deleted, applications first, and the ids recorded
|
|
2073
|
+
* for them are forgotten; the next apply builds them again on `co-<slug>`. Apply has already asked
|
|
2074
|
+
* for the typed company name and checked a fresh backup of every database. A failure part way
|
|
2075
|
+
* leaves what is left recorded and the marker unset, so the same step is planned again and goes on
|
|
2076
|
+
* from there (a 404 counts as deleted). The marker is recorded last. No new timeout: each delete
|
|
2077
|
+
* has the Coolify port's own.
|
|
2078
|
+
*/
|
|
2079
|
+
private coolifyNetwork;
|
|
1925
2080
|
/**
|
|
1926
2081
|
* #445: one company lands on one Coolify server. The first call for an
|
|
1927
2082
|
* org picks a usable, reachable server and records it
|
|
@@ -1960,6 +2115,15 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
|
|
|
1960
2115
|
* one).
|
|
1961
2116
|
*/
|
|
1962
2117
|
private ensureLogVolume;
|
|
2118
|
+
/**
|
|
2119
|
+
* F6: a nightly dump of one of the company's databases to the platform's
|
|
2120
|
+
* backup bucket. The schedule is found before it is created
|
|
2121
|
+
* (`provisionDatabaseBackup`), so a run that died after the create and
|
|
2122
|
+
* before `backup_<db>` was recorded reuses the schedule on the next apply
|
|
2123
|
+
* instead of adding a second. The error carries the database's name, never
|
|
2124
|
+
* the connection URL or a response body.
|
|
2125
|
+
*/
|
|
2126
|
+
private coolifyBackup;
|
|
1963
2127
|
private coolifyDatabase;
|
|
1964
2128
|
/**
|
|
1965
2129
|
* Pushes the database's connection string into the app's environment,
|
|
@@ -2012,6 +2176,19 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
|
|
|
2012
2176
|
* provisioned, where this one is unconditional (#239).
|
|
2013
2177
|
*/
|
|
2014
2178
|
private pushAppEnv;
|
|
2179
|
+
/**
|
|
2180
|
+
* The install's edge label token: read from the held keys, else minted and held first, so a crash
|
|
2181
|
+
* after the `put` hands the same value out on the next apply. Read once more after the `put`, so two
|
|
2182
|
+
* runs that mint at the same moment end on the value that was held last. An error names the ref,
|
|
2183
|
+
* never a value.
|
|
2184
|
+
*/
|
|
2185
|
+
private edgeLabelToken;
|
|
2186
|
+
/**
|
|
2187
|
+
* The `custom_labels` patch of an app foundry routes through Traefik at `hostname`: the same http
|
|
2188
|
+
* router a `domains` entry gets, plus the edge token. Coolify uses these instead of the labels it
|
|
2189
|
+
* generates from `domains`. The router names use the app's own name, which is unique per company.
|
|
2190
|
+
*/
|
|
2191
|
+
private routedLabels;
|
|
2015
2192
|
/**
|
|
2016
2193
|
* The org's shared secret `name`: read from the held keys, else generated
|
|
2017
2194
|
* and held first, so a crash after the `put` and before the env write
|
|
@@ -2189,6 +2366,22 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
|
|
|
2189
2366
|
* (which must never delete the platform's own project) change.
|
|
2190
2367
|
*/
|
|
2191
2368
|
private ensureOrgLettermintRelay;
|
|
2369
|
+
/**
|
|
2370
|
+
* The company's own sign-in mail token: a Lettermint project `<slug>-auth`
|
|
2371
|
+
* whose token goes to `orgTarget(org).refs.brokerMailKey`, where the broker
|
|
2372
|
+
* and ZITADEL's SMTP settings read it. The platform-wide Lettermint key
|
|
2373
|
+
* stays with the executor and never reaches a company. A held ref is left
|
|
2374
|
+
* alone. Otherwise the mint rule of {@link ensureOrgAppMailToken}: create
|
|
2375
|
+
* the project, restrict the domain, deliver its token, and only then
|
|
2376
|
+
* delete every other `<slug>-auth` project (a token cannot be read back,
|
|
2377
|
+
* so a project without a held token is a leftover). The restriction comes
|
|
2378
|
+
* before the delivery so a failure there leaves no held ref: the next run
|
|
2379
|
+
* starts over instead of skipping it. A failure before the delivery leaves
|
|
2380
|
+
* that project for the next run to delete. Every Lettermint call is
|
|
2381
|
+
* bounded; the token is registered for redaction before anything can throw
|
|
2382
|
+
* with it.
|
|
2383
|
+
*/
|
|
2384
|
+
private ensureOwnAuthMailToken;
|
|
2192
2385
|
/**
|
|
2193
2386
|
* Deletes a just-minted project from inside a `catch`. A failed delete is
|
|
2194
2387
|
* dropped so it never replaces the error being rethrown; the orphan is
|
|
@@ -2684,10 +2877,70 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
|
|
|
2684
2877
|
*/
|
|
2685
2878
|
private zonePort;
|
|
2686
2879
|
private jobsPort;
|
|
2880
|
+
/**
|
|
2881
|
+
* `plan/own-diff.ts`'s `planOwnJobs`: the org's own scheduler, in the org's
|
|
2882
|
+
* own project, with the names and the held-key ref of `orgTarget(org)`. The
|
|
2883
|
+
* admin token goes to `jobs:org:<slug>:adminToken` and nowhere else.
|
|
2884
|
+
*/
|
|
2885
|
+
private ownJobs;
|
|
2886
|
+
/**
|
|
2887
|
+
* `plan/own-diff.ts`'s `planOwnFiles`: the org's own files host in the
|
|
2888
|
+
* org's own project, trusting the company's issuer `https://auth.<domain>`.
|
|
2889
|
+
* Its ZITADEL project `<slug>-files` and introspection app are made in the
|
|
2890
|
+
* company's instance (`ownServices.zitadelFor`), never the shared one. The
|
|
2891
|
+
* bucket comes from the host's bucket port under the target's name. The
|
|
2892
|
+
* management key goes to `files:org:<slug>:managementKey` and the
|
|
2893
|
+
* introspection secret to `zitadel:org:<slug>:files:introspection`, both
|
|
2894
|
+
* once, and neither is recorded or returned.
|
|
2895
|
+
*/
|
|
2896
|
+
private ownFiles;
|
|
2897
|
+
/**
|
|
2898
|
+
* `plan/own-diff.ts`'s `planOwnFilesR2`: the storage key of the org's own files bucket. A token
|
|
2899
|
+
* recorded as undelivered is deleted first (its secret cannot be read back, so it can never be
|
|
2900
|
+
* finished), then one is minted, registered as a secret, recorded by id as undelivered and
|
|
2901
|
+
* persisted, and only then sent to the files host, the same pair on every retry. The secret is
|
|
2902
|
+
* never recorded and never written to a Coolify env. The id is recorded once the host has it.
|
|
2903
|
+
*/
|
|
2904
|
+
private ownFilesR2;
|
|
2905
|
+
/**
|
|
2906
|
+
* `plan/own-diff.ts`'s `planOwnAuth`: the org's own sign-in at
|
|
2907
|
+
* `auth.<domain>`, in the org's own project, through the org's own edge
|
|
2908
|
+
* routed DNS and tunnel ports. The instance owner token goes to
|
|
2909
|
+
* `zitadel:org:<slug>:instanceBearer` and is never recorded. A changed
|
|
2910
|
+
* declared auth ref patches both git apps' branch and redeploys them; the
|
|
2911
|
+
* applied marker comes last, so a run that died halfway is planned again.
|
|
2912
|
+
*/
|
|
2913
|
+
private ownAuth;
|
|
2914
|
+
/**
|
|
2915
|
+
* `plan/own-diff.ts`'s `planOwnMail`: the org's own Stalwart in the org's own
|
|
2916
|
+
* project, publishing its port 25 on one host port of the server (the shared
|
|
2917
|
+
* relay is on another network and dials the server's address) and a directory
|
|
2918
|
+
* that trusts the org's own sign-in. The admin key goes to
|
|
2919
|
+
* `orgTarget(org).refs.mailAdminKey` and nowhere else. The address the relay
|
|
2920
|
+
* routes to is recorded last, and the step's marker after it.
|
|
2921
|
+
*/
|
|
2922
|
+
private ownMail;
|
|
2923
|
+
/**
|
|
2924
|
+
* `plan/own-diff.ts`'s `planOwnSfu`: the org's own LiveKit server in the
|
|
2925
|
+
* org's project, on the ports of its slot. The company's key pair goes to
|
|
2926
|
+
* `livekit:org:<slug>:keys` and the app's env, nowhere else.
|
|
2927
|
+
*/
|
|
2928
|
+
private ownSfu;
|
|
2687
2929
|
private jobsRegistration;
|
|
2688
2930
|
/** `deliverSecret`, tried up to {@link JOBS_DELIVERY_ATTEMPTS} times; true once one attempt succeeds. */
|
|
2689
2931
|
private deliverWithRetry;
|
|
2690
2932
|
private cloudflareTunnel;
|
|
2933
|
+
/**
|
|
2934
|
+
* The entry names a company has in the platform's `oikos.host` zone (a company with no domain of its own):
|
|
2935
|
+
* one proxied CNAME each, to this company's tunnel, which routes them straight to the entry container. A
|
|
2936
|
+
* specific record beats the shared `*.oikos.host` wildcard, so the shared ingress stops carrying the name
|
|
2937
|
+
* without being edited. The company writes only its own `<slug>.oikos.host` ({@link ownsOikosName}); an
|
|
2938
|
+
* A or AAAA record, or a CNAME that points anywhere but this tunnel, an earlier id of it or the shared
|
|
2939
|
+
* wildcard's target, blocks the step. A name the company no longer declares has its record deleted when
|
|
2940
|
+
* it still points at this tunnel. The names are recorded only after every record is in place, so a failure
|
|
2941
|
+
* halfway is planned again.
|
|
2942
|
+
*/
|
|
2943
|
+
private publishOikosEntryRecords;
|
|
2691
2944
|
/**
|
|
2692
2945
|
* #236: when this tunnel's DNS was last published under a domain that
|
|
2693
2946
|
* differs from the one `org` now declares, deletes every CNAME in the
|
|
@@ -2900,6 +3153,19 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
|
|
|
2900
3153
|
* publishes it.
|
|
2901
3154
|
*/
|
|
2902
3155
|
private publishMailDnsRecords;
|
|
3156
|
+
/**
|
|
3157
|
+
* A company without a domain keeps its web address at `<slug>.oikos.host`,
|
|
3158
|
+
* which only the shared `*.oikos.host` wildcard answers. A wildcard applies
|
|
3159
|
+
* only to a name that holds no record of its own, so the MX and TXT records
|
|
3160
|
+
* of the company's mail domain on that same name would take the web address
|
|
3161
|
+
* away. This writes the name's own proxied CNAME, to wherever the wildcard
|
|
3162
|
+
* points, before any mail record. Found by name and type, so a rerun after a
|
|
3163
|
+
* half-finished apply never doubles it. Cloudflare lists only A, AAAA and
|
|
3164
|
+
* CNAME as clashing with a CNAME, so MX and TXT sit beside it.
|
|
3165
|
+
* https://developers.cloudflare.com/dns/manage-dns-records/reference/wildcard-dns-records/
|
|
3166
|
+
* https://developers.cloudflare.com/dns/manage-dns-records/troubleshooting/records-with-same-name/
|
|
3167
|
+
*/
|
|
3168
|
+
private ensureOwnWebAddressRecord;
|
|
2903
3169
|
/**
|
|
2904
3170
|
* Creates or updates one mail record (Stalwart's or Lettermint's).
|
|
2905
3171
|
*
|
|
@@ -2937,6 +3203,11 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
|
|
|
2937
3203
|
* #491: when `mailDomain` is the platform's own apex, this must never
|
|
2938
3204
|
* delete the platform's own project (the delete loop skips it by id) or
|
|
2939
3205
|
* the platform's own domain (returns before the domain lookup/delete).
|
|
3206
|
+
*
|
|
3207
|
+
* An org whose own sign-in is applied keeps its `<slug>-auth` project, and
|
|
3208
|
+
* keeps the Lettermint domain when that is the domain the sign-in sends
|
|
3209
|
+
* from. Every other org deletes `<slug>-auth` too, which only a failed
|
|
3210
|
+
* first sign-in run can have left behind.
|
|
2940
3211
|
*/
|
|
2941
3212
|
private parkLettermintMail;
|
|
2942
3213
|
/**
|