@wtfalch/foundry 0.34.0 → 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 +20 -0
- package/dist/bootstrap/versions.js +46 -0
- package/dist/calls-sfu.js +3 -3
- package/dist/index.d.ts +9 -4
- package/dist/index.js +5 -2
- package/dist/migrations/0053_heartbeats_provider.sql +9 -0
- package/dist/migrations/0053_org_version_pin.sql +6 -0
- package/dist/org.d.ts +60 -14
- package/dist/org.js +118 -28
- 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.js +3 -3
- package/dist/plan/cloudflared-connector.js +2 -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 +17 -8
- package/dist/plan/diff.js +131 -131
- package/dist/plan/edge-access.d.ts +15 -0
- package/dist/plan/edge-access.js +21 -2
- package/dist/plan/executor.d.ts +166 -7
- package/dist/plan/executor.js +547 -106
- 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 +5 -0
- package/dist/plan/held-key-refs.js +5 -0
- package/dist/plan/hostnames.d.ts +10 -0
- package/dist/plan/hostnames.js +14 -0
- package/dist/plan/index.d.ts +5 -0
- package/dist/plan/index.js +3 -0
- package/dist/plan/live.d.ts +6 -0
- package/dist/plan/live.js +3 -2
- package/dist/plan/locked-hostnames.d.ts +2 -1
- package/dist/plan/locked-hostnames.js +16 -3
- package/dist/plan/login-host.d.ts +9 -20
- package/dist/plan/login-host.js +10 -25
- package/dist/plan/mail-relay.d.ts +48 -29
- package/dist/plan/mail-relay.js +145 -68
- 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 +8 -2
- package/dist/plan/org-names.js +57 -1
- package/dist/plan/own-diff.d.ts +9 -8
- package/dist/plan/own-diff.js +42 -34
- package/dist/plan/own-hostnames.d.ts +27 -0
- package/dist/plan/own-hostnames.js +24 -0
- package/dist/plan/own-limits.d.ts +198 -7
- package/dist/plan/own-limits.js +163 -32
- package/dist/plan/own-services.js +3 -1
- package/dist/plan/own-sfu.js +3 -2
- package/dist/plan/plan-apply.d.ts +47 -7
- package/dist/plan/plan-apply.js +184 -38
- package/dist/plan/platform-diff.d.ts +133 -17
- package/dist/plan/platform-diff.js +231 -68
- package/dist/plan/platform-org.d.ts +79 -10
- package/dist/plan/platform-org.js +93 -13
- package/dist/plan/platform-providers.d.ts +118 -73
- package/dist/plan/platform-providers.js +770 -231
- 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/zitadel-login-policy.d.ts +1 -1
- package/dist/plan/zitadel-login-policy.js +76 -8
- package/dist/platform.d.ts +32 -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 +25 -3
- package/dist/store/postgres.js +43 -1
- package/dist/store/schema.d.ts +36 -0
- package/dist/store/schema.js +4 -0
- package/package.json +3 -3
- package/versions.digests.json +11 -0
package/dist/plan/own-limits.js
CHANGED
|
@@ -1,16 +1,55 @@
|
|
|
1
|
+
import { hasCapabilities, withCapabilities } from './run-options.js';
|
|
1
2
|
/**
|
|
2
|
-
* The hard limits every container
|
|
3
|
-
* gets: one flat default. Field names are Coolify's (`PATCH /applications/{uuid}`
|
|
3
|
+
* The hard limits of every container foundry creates, per step (`LIMITS`). Field names are Coolify's (`PATCH /applications/{uuid}`
|
|
4
4
|
* and `PATCH /databases/{uuid}`: `limits_memory`, `limits_cpus`). Coolify has
|
|
5
5
|
* no field for a process limit, and its `custom_docker_run_options` parser drops
|
|
6
6
|
* `--pids-limit`, so 512 processes cannot be set through Coolify.
|
|
7
7
|
*/
|
|
8
8
|
export const OWN_LIMITS_MEMORY = '1g';
|
|
9
9
|
export const OWN_LIMITS_CPUS = '1';
|
|
10
|
-
const
|
|
10
|
+
const profile = (memory, dbMemory = memory, autoDeploy = false, capabilities = 'A') => ({ memory, dbMemory, autoDeploy, capabilities });
|
|
11
|
+
/**
|
|
12
|
+
* The one table of memory limits, per step. Every container foundry creates
|
|
13
|
+
* is in a step named here. The five `own*` rows are the flat 1g the own
|
|
14
|
+
* services already ran with. A Node or Next app gets 1g, a small Postgres
|
|
15
|
+
* 512m, the Go-sized helpers (relay, boule, edge, hc) less.
|
|
16
|
+
*/
|
|
17
|
+
export const LIMITS = {
|
|
18
|
+
ownAuth: profile('1g'),
|
|
19
|
+
ownJobs: profile('1g'),
|
|
20
|
+
ownFiles: profile('1g'),
|
|
21
|
+
ownMail: profile('1g'),
|
|
22
|
+
ownSfu: profile('1g'),
|
|
23
|
+
/** A customer's hosting entry: capability tier B, unless the entry declares `capabilities: 'standard'`. */
|
|
24
|
+
companyApp: profile('1g', '1g', 'declared', 'B'),
|
|
25
|
+
companyAppStandard: profile('1g', '1g', 'declared'),
|
|
26
|
+
brokerEntry: profile('1g', '1g', 'declared'),
|
|
27
|
+
cms: profile('1g', '512m'),
|
|
28
|
+
chat: profile('1g', '512m'),
|
|
29
|
+
calls: profile('1g', '512m'),
|
|
30
|
+
agora: profile('1g', '512m'),
|
|
31
|
+
boule: profile('512m'),
|
|
32
|
+
companyOther: profile('1g', '512m'),
|
|
33
|
+
platformArchon: profile('1g'),
|
|
34
|
+
platformAi: profile('1g'),
|
|
35
|
+
platformIntegrations: profile('1g', '512m'),
|
|
36
|
+
platformMailRelay: profile('256m'),
|
|
37
|
+
platformIngress: profile('256m'),
|
|
38
|
+
connector: profile('256m'),
|
|
39
|
+
platformFiles: profile('1g'),
|
|
40
|
+
platformEdge: profile('512m'),
|
|
41
|
+
platformHc: profile('512m'),
|
|
42
|
+
platformMail: profile('1g'),
|
|
43
|
+
platformAuth: profile('1g'),
|
|
44
|
+
platformJobs: profile('1g'),
|
|
45
|
+
};
|
|
11
46
|
/** The same bound the neighbouring Coolify calls in this package use for a call that may hang. */
|
|
12
47
|
export const OWN_LIMITS_TIMEOUT_MS = 60_000;
|
|
13
|
-
const
|
|
48
|
+
const appLimits = (p) => ({ limits_memory: p.memory, limits_cpus: OWN_LIMITS_CPUS });
|
|
49
|
+
const dbLimits = (p) => ({ limits_memory: p.dbMemory, limits_cpus: OWN_LIMITS_CPUS });
|
|
50
|
+
const lacks = (r, want) => Boolean(r) && (r?.limits_memory !== want.limits_memory || r?.limits_cpus !== OWN_LIMITS_CPUS);
|
|
51
|
+
const capsLack = (r, p) => Boolean(r) &&
|
|
52
|
+
!hasCapabilities(typeof r?.custom_docker_run_options === 'string' ? r.custom_docker_run_options : null, p.capabilities);
|
|
14
53
|
async function bounded(work, what) {
|
|
15
54
|
let timer;
|
|
16
55
|
try {
|
|
@@ -25,72 +64,158 @@ async function bounded(work, what) {
|
|
|
25
64
|
clearTimeout(timer);
|
|
26
65
|
}
|
|
27
66
|
}
|
|
67
|
+
const wrapped = new WeakSet();
|
|
28
68
|
/**
|
|
29
|
-
* The Coolify port
|
|
69
|
+
* The Coolify port a step uses, with the profile's memory limit set on every
|
|
30
70
|
* application and database it creates, and set again on one it finds without
|
|
31
|
-
*
|
|
32
|
-
* next apply.
|
|
33
|
-
*
|
|
71
|
+
* it, so a company provisioned before the limits existed converges on its
|
|
72
|
+
* next apply. With `autoDeploy: false` a git application is created with
|
|
73
|
+
* auto-deploy off, and one found or updated with it on is switched off. A
|
|
74
|
+
* failed limits call fails the calling step before it records its applied
|
|
75
|
+
* marker; the next run finds the resource and sets them.
|
|
76
|
+
*
|
|
77
|
+
* `scope` is a profile, or a function that gives the profile of the step now
|
|
78
|
+
* running (none: the port is used as it is).
|
|
79
|
+
*
|
|
80
|
+
* Every limits call has a 60 second timeout. A step that creates a resource
|
|
81
|
+
* and then fails on the limits call leaves a resource without limits; the
|
|
82
|
+
* next run finds it by name and sets them.
|
|
34
83
|
*/
|
|
35
|
-
export function limited(coolify) {
|
|
36
|
-
|
|
84
|
+
export function limited(coolify, scope = LIMITS.ownAuth) {
|
|
85
|
+
if (wrapped.has(coolify))
|
|
86
|
+
return coolify;
|
|
87
|
+
const current = () => (typeof scope === 'function' ? scope() : scope);
|
|
88
|
+
// `existing`: the application as just read, or the options just sent on its create. Absent, it is read
|
|
89
|
+
// here, because the capability options are written together with the `--entrypoint` already on the app.
|
|
90
|
+
const patchApp = async (uuid, p, autoDeploy, existing) => {
|
|
37
91
|
if (!coolify.updateApplication)
|
|
38
92
|
throw new Error('own limits: the Coolify port has no updateApplication');
|
|
39
|
-
|
|
93
|
+
const current = existing ??
|
|
94
|
+
(coolify.getApplication
|
|
95
|
+
? await bounded(coolify.getApplication(uuid), `application ${uuid}`)
|
|
96
|
+
: undefined);
|
|
97
|
+
const options = current === undefined
|
|
98
|
+
? {}
|
|
99
|
+
: {
|
|
100
|
+
custom_docker_run_options: withCapabilities(typeof current?.custom_docker_run_options === 'string'
|
|
101
|
+
? current.custom_docker_run_options
|
|
102
|
+
: null, p.capabilities),
|
|
103
|
+
};
|
|
104
|
+
return bounded(coolify.updateApplication(uuid, {
|
|
105
|
+
...appLimits(p),
|
|
106
|
+
...options,
|
|
107
|
+
...(autoDeploy ? { is_auto_deploy_enabled: false } : {}),
|
|
108
|
+
}), `application ${uuid}`);
|
|
40
109
|
};
|
|
41
|
-
const patchDb = (uuid) => {
|
|
110
|
+
const patchDb = (uuid, p) => {
|
|
42
111
|
if (!coolify.raw)
|
|
43
112
|
throw new Error('own limits: the Coolify port has no raw');
|
|
44
|
-
return bounded(coolify.raw('PATCH', `/databases/${uuid}`,
|
|
113
|
+
return bounded(coolify.raw('PATCH', `/databases/${uuid}`, dbLimits(p)), `database ${uuid}`);
|
|
45
114
|
};
|
|
46
|
-
const afterCreate = (
|
|
47
|
-
const
|
|
48
|
-
|
|
115
|
+
const afterCreate = (method, git) => async (input) => {
|
|
116
|
+
const p = current();
|
|
117
|
+
const noDeploy = git && p?.autoDeploy === false && input.is_auto_deploy_enabled === undefined
|
|
118
|
+
? { ...input, is_auto_deploy_enabled: false }
|
|
119
|
+
: input;
|
|
120
|
+
const sent = p
|
|
121
|
+
? {
|
|
122
|
+
...noDeploy,
|
|
123
|
+
custom_docker_run_options: withCapabilities(typeof input.custom_docker_run_options === 'string'
|
|
124
|
+
? input.custom_docker_run_options
|
|
125
|
+
: null, p.capabilities),
|
|
126
|
+
}
|
|
127
|
+
: noDeploy;
|
|
128
|
+
const create = coolify[method];
|
|
129
|
+
const created = await create.call(coolify, sent);
|
|
130
|
+
if (p) {
|
|
131
|
+
await patchApp(created.uuid, p, false, {
|
|
132
|
+
custom_docker_run_options: sent
|
|
133
|
+
.custom_docker_run_options,
|
|
134
|
+
});
|
|
135
|
+
settled.add(created.uuid);
|
|
136
|
+
}
|
|
49
137
|
return created;
|
|
50
138
|
};
|
|
51
139
|
const over = {};
|
|
52
140
|
if (coolify.createGithubAppApplication)
|
|
53
|
-
over.createGithubAppApplication = afterCreate(
|
|
141
|
+
over.createGithubAppApplication = afterCreate('createGithubAppApplication', true);
|
|
54
142
|
if (coolify.createDockerImageApplication)
|
|
55
|
-
over.createDockerImageApplication = afterCreate(
|
|
143
|
+
over.createDockerImageApplication = afterCreate('createDockerImageApplication', false);
|
|
56
144
|
if (coolify.createDockerfileApplication)
|
|
57
|
-
over.createDockerfileApplication = afterCreate(
|
|
145
|
+
over.createDockerfileApplication = afterCreate('createDockerfileApplication', false);
|
|
58
146
|
if (coolify.createPostgresDatabase) {
|
|
59
|
-
const create = coolify.createPostgresDatabase;
|
|
60
147
|
over.createPostgresDatabase = async (input) => {
|
|
61
|
-
const created = await
|
|
62
|
-
|
|
148
|
+
const created = await coolify.createPostgresDatabase.call(coolify, input);
|
|
149
|
+
const p = current();
|
|
150
|
+
if (p)
|
|
151
|
+
await patchDb(created.uuid, p);
|
|
63
152
|
return created;
|
|
64
153
|
};
|
|
65
154
|
}
|
|
66
155
|
const getApplication = coolify.getApplication;
|
|
156
|
+
const update = coolify.updateApplication;
|
|
157
|
+
// Applications already brought to the profile by this port: no second patch for them.
|
|
158
|
+
const settled = new Set();
|
|
159
|
+
const settle = async (uuid, p, app, git) => {
|
|
160
|
+
if (settled.has(uuid))
|
|
161
|
+
return;
|
|
162
|
+
const deploys = git && p.autoDeploy === false && app?.is_auto_deploy_enabled !== false;
|
|
163
|
+
if (!app || lacks(app, appLimits(p)) || capsLack(app, p) || deploys) {
|
|
164
|
+
await patchApp(uuid, p, deploys, app);
|
|
165
|
+
}
|
|
166
|
+
settled.add(uuid);
|
|
167
|
+
};
|
|
168
|
+
if (update) {
|
|
169
|
+
over.updateApplication = async (uuid, patch) => {
|
|
170
|
+
const p = current();
|
|
171
|
+
// A step that sends its own run options (the entrypoint) must not take the capability options off.
|
|
172
|
+
const sent = p && typeof patch.custom_docker_run_options === 'string'
|
|
173
|
+
? {
|
|
174
|
+
...patch,
|
|
175
|
+
custom_docker_run_options: withCapabilities(patch.custom_docker_run_options, p.capabilities),
|
|
176
|
+
}
|
|
177
|
+
: patch;
|
|
178
|
+
const out = await coolify.updateApplication?.call(coolify, uuid, sent);
|
|
179
|
+
if (p) {
|
|
180
|
+
const git = 'git_branch' in patch || 'is_auto_deploy_enabled' in patch;
|
|
181
|
+
await settle(uuid, p, null, git);
|
|
182
|
+
}
|
|
183
|
+
return out;
|
|
184
|
+
};
|
|
185
|
+
}
|
|
67
186
|
if (getApplication) {
|
|
68
187
|
over.getApplication = async (uuid) => {
|
|
69
|
-
const app = await getApplication
|
|
70
|
-
|
|
71
|
-
|
|
188
|
+
const app = await coolify.getApplication?.call(coolify, uuid);
|
|
189
|
+
const p = current();
|
|
190
|
+
if (p && app)
|
|
191
|
+
await settle(uuid, p, app, Boolean(app.git_repository));
|
|
72
192
|
return app;
|
|
73
193
|
};
|
|
74
194
|
}
|
|
75
195
|
const find = coolify.findApplicationByName;
|
|
76
196
|
if (find && getApplication) {
|
|
77
197
|
over.findApplicationByName = async (...args) => {
|
|
78
|
-
const found = await
|
|
79
|
-
|
|
80
|
-
|
|
198
|
+
const found = await coolify.findApplicationByName?.apply(coolify, args);
|
|
199
|
+
const p = current();
|
|
200
|
+
if (found && p) {
|
|
201
|
+
const app = await coolify.getApplication?.call(coolify, found.uuid);
|
|
202
|
+
if (app)
|
|
203
|
+
await settle(found.uuid, p, app, Boolean(app.git_repository));
|
|
204
|
+
}
|
|
81
205
|
return found;
|
|
82
206
|
};
|
|
83
207
|
}
|
|
84
208
|
const getDatabase = coolify.getDatabase;
|
|
85
209
|
if (getDatabase) {
|
|
86
210
|
over.getDatabase = async (uuid) => {
|
|
87
|
-
const db = await getDatabase
|
|
88
|
-
|
|
89
|
-
|
|
211
|
+
const db = await coolify.getDatabase?.call(coolify, uuid);
|
|
212
|
+
const p = current();
|
|
213
|
+
if (p && lacks(db, dbLimits(p)))
|
|
214
|
+
await patchDb(uuid, p);
|
|
90
215
|
return db;
|
|
91
216
|
};
|
|
92
217
|
}
|
|
93
|
-
|
|
218
|
+
const proxy = new Proxy(coolify, {
|
|
94
219
|
get(target, prop) {
|
|
95
220
|
if (typeof prop === 'string' && prop in over)
|
|
96
221
|
return over[prop];
|
|
@@ -98,4 +223,10 @@ export function limited(coolify) {
|
|
|
98
223
|
return typeof value === 'function' ? value.bind(target) : value;
|
|
99
224
|
},
|
|
100
225
|
});
|
|
226
|
+
wrapped.add(proxy);
|
|
227
|
+
return proxy;
|
|
228
|
+
}
|
|
229
|
+
/** The deps of a step with their Coolify port limited by `profile`. */
|
|
230
|
+
export function limitedDeps(deps, profile) {
|
|
231
|
+
return { ...deps, coolify: limited(deps.coolify, profile) };
|
|
101
232
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { NO_DOMAIN_ZONE } from './own-hostnames.js';
|
|
1
2
|
import { apexZoneKey, exactZone, platformDns, platformTunnels } from './platform-providers.js';
|
|
2
3
|
/** The key the org's zone step records its Cloudflare zone id under. */
|
|
3
4
|
const ORG_ZONE_SCOPE_KEY = 'cloudflare_zone';
|
|
@@ -143,7 +144,8 @@ export function orgNetwork(ports, scope, step) {
|
|
|
143
144
|
return ports.tunnels;
|
|
144
145
|
},
|
|
145
146
|
zoneId: async (apex) => {
|
|
146
|
-
|
|
147
|
+
// The recorded zone is the company's own; `oikos.host` is never it.
|
|
148
|
+
const recorded = apex === NO_DOMAIN_ZONE ? undefined : scope.recordedIds()[ORG_ZONE_SCOPE_KEY];
|
|
147
149
|
if (recorded)
|
|
148
150
|
return recorded;
|
|
149
151
|
return ports.zones ? (await exactZone(ports.zones, apex))?.id : undefined;
|
package/dist/plan/own-sfu.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { pinnedTagFor } from '../bootstrap/versions.js';
|
|
1
2
|
import { mintLivekitCredentials } from '../calls-sfu-keys.js';
|
|
2
3
|
import { LIVEKIT_SIGNALLING_PORT, sfuSlotPorts } from '../calls-sfu.js';
|
|
3
4
|
import { ensureCloudflaredConnector } from './cloudflared-connector.js';
|
|
@@ -105,7 +106,7 @@ export async function provisionSfuApp(scope, deps, target, input) {
|
|
|
105
106
|
environment_name: environment.name,
|
|
106
107
|
environment_uuid: environment.uuid,
|
|
107
108
|
docker_registry_image_name: OWN_SFU_IMAGE,
|
|
108
|
-
docker_registry_image_tag: input.version,
|
|
109
|
+
docker_registry_image_tag: pinnedTagFor('livekit', input.version),
|
|
109
110
|
ports_exposes: String(LIVEKIT_SIGNALLING_PORT),
|
|
110
111
|
ports_mappings: sfuPortsMappings(input.slot),
|
|
111
112
|
name: target.names.sfuApp,
|
|
@@ -121,7 +122,7 @@ export async function provisionSfuApp(scope, deps, target, input) {
|
|
|
121
122
|
// Set on every run: a declared version or slot reaches an existing app here.
|
|
122
123
|
await coolify.updateApplication(appId, {
|
|
123
124
|
custom_internal_name: target.internal.sfu,
|
|
124
|
-
docker_registry_image_tag: input.version,
|
|
125
|
+
docker_registry_image_tag: pinnedTagFor('livekit', input.version),
|
|
125
126
|
ports_exposes: String(LIVEKIT_SIGNALLING_PORT),
|
|
126
127
|
ports_mappings: sfuPortsMappings(input.slot),
|
|
127
128
|
...healthCheckSettings('/', LIVEKIT_SIGNALLING_PORT),
|
|
@@ -7,11 +7,12 @@ import type { AppliedState } from '../state.js';
|
|
|
7
7
|
import { type AppliedVersionsStore, type DeclaredOrgStore, type PlatformStore } from '../store.js';
|
|
8
8
|
import { type AdoptCandidate, type AdoptInput, type AdoptPlan } from './adopt.js';
|
|
9
9
|
import { type AgoraHostPort } from './agora.js';
|
|
10
|
+
import { type MoveGatePorts } from './backup-gate.js';
|
|
10
11
|
import { type UnconfirmedUpgrade } from './confirm-upgrade.js';
|
|
11
12
|
import { type StepAction } from './diff.js';
|
|
12
13
|
import { type LiveReaders, type LiveResource, type PlatformLiveReaders } from './live.js';
|
|
13
14
|
import { type LoginHostDiscoveryPort } from './login-host.js';
|
|
14
|
-
import { type EdgePortName, type HeldKeyResolver } from './platform-org.js';
|
|
15
|
+
import { type EdgePortName, type HeldKeyResolver, type PlatformOwnerInput } from './platform-org.js';
|
|
15
16
|
import type { CallsSfuConnectionPort } from './platform-sfu.js';
|
|
16
17
|
/** Who did it, for `@wtfalch/audit`'s "who, when, the plan, the result" (#9, #19). */
|
|
17
18
|
export interface AuditActor {
|
|
@@ -124,6 +125,11 @@ export interface AuthzGate {
|
|
|
124
125
|
*/
|
|
125
126
|
export interface StepExecutionContext {
|
|
126
127
|
readonly org: DeclaredOrg;
|
|
128
|
+
/**
|
|
129
|
+
* True when `org` is the platform's own company (`DeclaredPlatform.ownerOrgId`). It stays on the
|
|
130
|
+
* platform's Docker network, not on `co-<slug>`, so Archon reaches its database. Absent: false.
|
|
131
|
+
*/
|
|
132
|
+
readonly ownerCompany?: boolean;
|
|
127
133
|
/**
|
|
128
134
|
* Every id foundry has recorded for this org so far — both `system:kind:key`
|
|
129
135
|
* resource ids (`runSteps` records these itself from `execute`'s own return
|
|
@@ -199,6 +205,13 @@ export interface StepExecutor {
|
|
|
199
205
|
* this says false. Optional: an executor without it is taken to have the port.
|
|
200
206
|
*/
|
|
201
207
|
ownFilesOrganisationPort?(org: DeclaredOrg, recordedIds: Readonly<Record<string, string>>): Promise<boolean>;
|
|
208
|
+
/**
|
|
209
|
+
* Whether the host's scoped ports for this own org include a broker port, the company's own
|
|
210
|
+
* broker. An own org's broker entries are planned on this answer, not on
|
|
211
|
+
* {@link FoundryPlanApplyOptions.broker}, which names the shared broker. Optional: an executor
|
|
212
|
+
* without it leaves the option as the answer.
|
|
213
|
+
*/
|
|
214
|
+
ownBrokerPort?(org: DeclaredOrg, recordedIds: Readonly<Record<string, string>>): Promise<boolean>;
|
|
202
215
|
/**
|
|
203
216
|
* Every secret this executor minted or read so far (peer password, bridge
|
|
204
217
|
* secret, invite code and link). The host's run adds them to its redactor
|
|
@@ -219,6 +232,8 @@ export interface StepExecutor {
|
|
|
219
232
|
*/
|
|
220
233
|
export interface PlatformStepExecutionContext {
|
|
221
234
|
readonly platform: DeclaredPlatform;
|
|
235
|
+
/** The owner company's own sign-in and files host, when applied. Archon and the Access provider read them. */
|
|
236
|
+
readonly owner?: PlatformOwnerInput;
|
|
222
237
|
/** {@link StepExecutionContext.recordedIds}, for the platform's own run. */
|
|
223
238
|
recordedIds(): Readonly<Record<string, string>>;
|
|
224
239
|
/** {@link StepExecutionContext.recordScopeId}, for the platform's own run. */
|
|
@@ -254,7 +269,7 @@ export interface PlatformStepExecutor {
|
|
|
254
269
|
* unmanaged item and plans no step for it, the way a blocked Agora is
|
|
255
270
|
* reported, instead of planning a step that throws on every apply.
|
|
256
271
|
*/
|
|
257
|
-
blockedSteps?(platform: DeclaredPlatform, recordedIds: Readonly<Record<string, string
|
|
272
|
+
blockedSteps?(platform: DeclaredPlatform, recordedIds: Readonly<Record<string, string>>, owner?: PlatformOwnerInput): Promise<{
|
|
258
273
|
auth?: string;
|
|
259
274
|
/** #211 round 6: see `PlanPlatformOptions.blocked.authExecutorMissing`. */
|
|
260
275
|
authExecutorMissing?: boolean;
|
|
@@ -269,6 +284,8 @@ export interface PlatformStepExecutor {
|
|
|
269
284
|
relay?: string;
|
|
270
285
|
/** #604: see `PlanPlatformOptions.blocked.accessProvider` (`platform-diff.ts`). */
|
|
271
286
|
accessProvider?: string;
|
|
287
|
+
/** See `PlanPlatformOptions.blocked.oikosIngress` (`platform-diff.ts`). */
|
|
288
|
+
oikosIngress?: string;
|
|
272
289
|
/** #257: see `PlanPlatformOptions.blocked.backupExecutorMissing` (`platform-diff.ts`). */
|
|
273
290
|
backupExecutorMissing?: boolean;
|
|
274
291
|
/** #540: see `PlanPlatformOptions.blocked.hc` (`platform-diff.ts`). */
|
|
@@ -291,7 +308,7 @@ export interface PlatformStepExecutor {
|
|
|
291
308
|
* the token and the edge step does not clear the flag, so the host must stop
|
|
292
309
|
* reporting it once edge holds the new token and the old one is gone.
|
|
293
310
|
*/
|
|
294
|
-
driftedSteps?(platform: DeclaredPlatform, recordedIds: Readonly<Record<string, string
|
|
311
|
+
driftedSteps?(platform: DeclaredPlatform, recordedIds: Readonly<Record<string, string>>, owner?: PlatformOwnerInput): Promise<PlatformDriftedSteps>;
|
|
295
312
|
/**
|
|
296
313
|
* Gives the platform files runner a changed share-domain set and touches
|
|
297
314
|
* nothing else. Returns the scope ids to record (only the share-domains
|
|
@@ -513,6 +530,15 @@ export interface FoundryPlanApplyOptions {
|
|
|
513
530
|
* every existing caller of this constructor is unaffected.
|
|
514
531
|
*/
|
|
515
532
|
appliedVersions?: AppliedVersionsStore;
|
|
533
|
+
/**
|
|
534
|
+
* F6: the Coolify slice that reads a company database's backups. With it, an
|
|
535
|
+
* org's databases get backup steps and a confirmed stateful upgrade is
|
|
536
|
+
* refused without a fresh finished dump (`backup-gate.ts`). Without it
|
|
537
|
+
* neither happens, so an existing caller is unaffected.
|
|
538
|
+
*/
|
|
539
|
+
backupGate?: {
|
|
540
|
+
coolify: MoveGatePorts['coolify'];
|
|
541
|
+
};
|
|
516
542
|
/**
|
|
517
543
|
* #167: dependencies for planning/applying the shared platform (#166)
|
|
518
544
|
* alongside an org. Optional so an org-only caller — every #19-era caller,
|
|
@@ -566,6 +592,7 @@ export declare class FoundryPlanApply implements PlanApply {
|
|
|
566
592
|
private readonly now;
|
|
567
593
|
private readonly leaseTtlMs;
|
|
568
594
|
private readonly appliedVersions;
|
|
595
|
+
private readonly backupGate;
|
|
569
596
|
private readonly platformDeps;
|
|
570
597
|
private readonly runner;
|
|
571
598
|
constructor(options: FoundryPlanApplyOptions);
|
|
@@ -726,8 +753,19 @@ export declare class FoundryPlanApply implements PlanApply {
|
|
|
726
753
|
private ownMailKeyHeld;
|
|
727
754
|
/** Which of an own org's undelivered-secret refs hold a value now, so a rerun resumes instead of blocking. */
|
|
728
755
|
private ownResumableRefs;
|
|
756
|
+
/**
|
|
757
|
+
* The host port an own org's Stalwart publishes for the relay: the one it already has recorded, else
|
|
758
|
+
* the lowest port in the range no other org has recorded. Two plans made at the same moment could pick
|
|
759
|
+
* the same port; the second Stalwart then fails to bind it and the step fails, with nothing recorded.
|
|
760
|
+
*/
|
|
761
|
+
private ownMailSmtpPort;
|
|
729
762
|
/** The relay is declared and has recorded the routes it last applied: port 25 reaches the mail servers. */
|
|
730
763
|
private mailRelayApplied;
|
|
764
|
+
/**
|
|
765
|
+
* The relay's last applied routes (a fingerprint, recorded when it deploys) are the routes the
|
|
766
|
+
* orgs' recorded SMTP addresses give now, and one of them is this org's mail domain.
|
|
767
|
+
*/
|
|
768
|
+
private mailRelayHoldsRoute;
|
|
731
769
|
private sharedPlatformReadiness;
|
|
732
770
|
/**
|
|
733
771
|
* #177: `AgoraHostValues` from the applied platform, the same
|
|
@@ -751,14 +789,16 @@ export declare class FoundryPlanApply implements PlanApply {
|
|
|
751
789
|
/** #167: `FoundryPlanApplyOptions.platform`, or a plain `Error` naming what's missing. */
|
|
752
790
|
private requirePlatformDeps;
|
|
753
791
|
private computeFreshPlatformPlan;
|
|
792
|
+
/** The owner company's own sign-in and files host, read from its applied ids; `null` members until applied. */
|
|
793
|
+
private platformOwnerInput;
|
|
754
794
|
/**
|
|
755
795
|
* What the shared mail relay needs from the orgs. A route exists for an org
|
|
756
796
|
* that owns its services, serves mail on its own domain and has recorded its
|
|
757
797
|
* mail server's SMTP address (`mail_smtp_internal`, written by the company
|
|
758
|
-
* mail step)
|
|
759
|
-
*
|
|
760
|
-
* that owns its services but has no mail server yet
|
|
761
|
-
*
|
|
798
|
+
* mail step), on its mail domain: its own domain, or `<slug>.oikos.host`.
|
|
799
|
+
* There is no platform mail server: a domain with no route is refused. An org
|
|
800
|
+
* that owns its services but has no mail server yet has no route, so the
|
|
801
|
+
* relay refuses its mail until the server exists.
|
|
762
802
|
*/
|
|
763
803
|
private mailRelayInputs;
|
|
764
804
|
private sfuBlockedReason;
|