@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.
Files changed (106) hide show
  1. package/dist/bootstrap/bootstrap.d.ts +1 -1
  2. package/dist/bootstrap/bootstrap.js +49 -11
  3. package/dist/bootstrap/declare.d.ts +12 -5
  4. package/dist/bootstrap/declare.js +33 -18
  5. package/dist/bootstrap/versions.d.ts +24 -0
  6. package/dist/bootstrap/versions.js +46 -0
  7. package/dist/calls-sfu.d.ts +10 -0
  8. package/dist/calls-sfu.js +44 -3
  9. package/dist/index.d.ts +21 -8
  10. package/dist/index.js +11 -4
  11. package/dist/migrations/0051_org_services.sql +13 -0
  12. package/dist/migrations/0052_platform_mail_relay.sql +8 -0
  13. package/dist/migrations/0053_heartbeats_provider.sql +9 -0
  14. package/dist/migrations/0053_org_version_pin.sql +6 -0
  15. package/dist/org.d.ts +76 -12
  16. package/dist/org.js +136 -20
  17. package/dist/plan/backup-gate.d.ts +93 -0
  18. package/dist/plan/backup-gate.js +261 -0
  19. package/dist/plan/broker-entries.d.ts +9 -1
  20. package/dist/plan/broker-entries.js +20 -3
  21. package/dist/plan/build-record.d.ts +56 -0
  22. package/dist/plan/build-record.js +109 -0
  23. package/dist/plan/calls-matrix.d.ts +9 -0
  24. package/dist/plan/calls-matrix.js +18 -1
  25. package/dist/plan/cloudflared-connector.d.ts +7 -1
  26. package/dist/plan/cloudflared-connector.js +15 -2
  27. package/dist/plan/company-backups.d.ts +31 -0
  28. package/dist/plan/company-backups.js +101 -0
  29. package/dist/plan/deploy-health.d.ts +12 -2
  30. package/dist/plan/deploy-health.js +3 -1
  31. package/dist/plan/destinations.d.ts +73 -0
  32. package/dist/plan/destinations.js +217 -0
  33. package/dist/plan/diff.d.ts +46 -8
  34. package/dist/plan/diff.js +367 -126
  35. package/dist/plan/edge-access.d.ts +15 -0
  36. package/dist/plan/edge-access.js +21 -2
  37. package/dist/plan/executor.d.ts +276 -5
  38. package/dist/plan/executor.js +1111 -109
  39. package/dist/plan/health-check-settings.d.ts +28 -0
  40. package/dist/plan/health-check-settings.js +36 -0
  41. package/dist/plan/health-gaps.d.ts +33 -0
  42. package/dist/plan/health-gaps.js +89 -0
  43. package/dist/plan/health-targets.d.ts +1 -3
  44. package/dist/plan/health-targets.js +36 -36
  45. package/dist/plan/held-key-refs.d.ts +15 -0
  46. package/dist/plan/held-key-refs.js +15 -0
  47. package/dist/plan/hostnames.d.ts +10 -0
  48. package/dist/plan/hostnames.js +14 -0
  49. package/dist/plan/index.d.ts +19 -6
  50. package/dist/plan/index.js +10 -3
  51. package/dist/plan/live.d.ts +11 -2
  52. package/dist/plan/live.js +16 -2
  53. package/dist/plan/locked-hostnames.d.ts +7 -3
  54. package/dist/plan/locked-hostnames.js +25 -4
  55. package/dist/plan/login-host.d.ts +61 -12
  56. package/dist/plan/login-host.js +99 -11
  57. package/dist/plan/mail-relay.d.ts +139 -0
  58. package/dist/plan/mail-relay.js +400 -0
  59. package/dist/plan/network-move.d.ts +20 -0
  60. package/dist/plan/network-move.js +41 -0
  61. package/dist/plan/oikos-ingress.d.ts +111 -0
  62. package/dist/plan/oikos-ingress.js +231 -0
  63. package/dist/plan/org-names.d.ts +30 -0
  64. package/dist/plan/org-names.js +108 -0
  65. package/dist/plan/own-diff.d.ts +170 -0
  66. package/dist/plan/own-diff.js +440 -0
  67. package/dist/plan/own-dns.d.ts +6 -0
  68. package/dist/plan/own-dns.js +15 -0
  69. package/dist/plan/own-hostnames.d.ts +27 -0
  70. package/dist/plan/own-hostnames.js +24 -0
  71. package/dist/plan/own-limits.d.ts +213 -0
  72. package/dist/plan/own-limits.js +232 -0
  73. package/dist/plan/own-services.d.ts +193 -0
  74. package/dist/plan/own-services.js +169 -0
  75. package/dist/plan/own-sfu.d.ts +69 -0
  76. package/dist/plan/own-sfu.js +256 -0
  77. package/dist/plan/plan-apply.d.ts +121 -10
  78. package/dist/plan/plan-apply.js +357 -18
  79. package/dist/plan/platform-diff.d.ts +155 -11
  80. package/dist/plan/platform-diff.js +271 -66
  81. package/dist/plan/platform-org.d.ts +107 -4
  82. package/dist/plan/platform-org.js +175 -3
  83. package/dist/plan/platform-providers.d.ts +474 -81
  84. package/dist/plan/platform-providers.js +1407 -560
  85. package/dist/plan/proxy-labels.d.ts +57 -11
  86. package/dist/plan/proxy-labels.js +94 -14
  87. package/dist/plan/run-options.d.ts +19 -0
  88. package/dist/plan/run-options.js +46 -0
  89. package/dist/plan/sign-in-org.d.ts +1 -1
  90. package/dist/plan/sign-in-org.js +20 -2
  91. package/dist/plan/zitadel-login-policy.d.ts +1 -1
  92. package/dist/plan/zitadel-login-policy.js +77 -9
  93. package/dist/plan/zitadel-system-api.d.ts +22 -1
  94. package/dist/plan/zitadel-system-api.js +49 -8
  95. package/dist/platform.d.ts +40 -0
  96. package/dist/platform.js +16 -0
  97. package/dist/provision.d.ts +2 -0
  98. package/dist/provision.js +3 -1
  99. package/dist/slug.d.ts +3 -1
  100. package/dist/slug.js +26 -3
  101. package/dist/store/postgres.js +93 -2
  102. package/dist/store/schema.d.ts +87 -0
  103. package/dist/store/schema.js +9 -0
  104. package/package.json +3 -3
  105. package/versions.digests.json +11 -0
  106. package/versions.json +3 -1
@@ -0,0 +1,213 @@
1
+ import type { CoolifyPort } from './executor.js';
2
+ import { type CapabilityTier } from './run-options.js';
3
+ /**
4
+ * The hard limits of every container foundry creates, per step (`LIMITS`). Field names are Coolify's (`PATCH /applications/{uuid}`
5
+ * and `PATCH /databases/{uuid}`: `limits_memory`, `limits_cpus`). Coolify has
6
+ * no field for a process limit, and its `custom_docker_run_options` parser drops
7
+ * `--pids-limit`, so 512 processes cannot be set through Coolify.
8
+ */
9
+ export declare const OWN_LIMITS_MEMORY = "1g";
10
+ export declare const OWN_LIMITS_CPUS = "1";
11
+ /**
12
+ * What a step's containers get. `memory` is for applications, `dbMemory` for
13
+ * databases. `autoDeploy: false` makes every git application the step creates
14
+ * or updates deploy only when foundry deploys it; `'declared'` leaves it to
15
+ * the company's own hosting entry (`HostingEntry.autoDeploy`).
16
+ */
17
+ export interface LimitProfile {
18
+ memory: string;
19
+ dbMemory: string;
20
+ autoDeploy: false | 'declared';
21
+ /** The capability options sent in `custom_docker_run_options` (`plan/run-options.ts`). */
22
+ capabilities: CapabilityTier;
23
+ }
24
+ /**
25
+ * The one table of memory limits, per step. Every container foundry creates
26
+ * is in a step named here. The five `own*` rows are the flat 1g the own
27
+ * services already ran with. A Node or Next app gets 1g, a small Postgres
28
+ * 512m, the Go-sized helpers (relay, boule, edge, hc) less.
29
+ */
30
+ export declare const LIMITS: {
31
+ readonly ownAuth: {
32
+ memory: string;
33
+ dbMemory: string;
34
+ autoDeploy: false | "declared";
35
+ capabilities: CapabilityTier;
36
+ };
37
+ readonly ownJobs: {
38
+ memory: string;
39
+ dbMemory: string;
40
+ autoDeploy: false | "declared";
41
+ capabilities: CapabilityTier;
42
+ };
43
+ readonly ownFiles: {
44
+ memory: string;
45
+ dbMemory: string;
46
+ autoDeploy: false | "declared";
47
+ capabilities: CapabilityTier;
48
+ };
49
+ readonly ownMail: {
50
+ memory: string;
51
+ dbMemory: string;
52
+ autoDeploy: false | "declared";
53
+ capabilities: CapabilityTier;
54
+ };
55
+ readonly ownSfu: {
56
+ memory: string;
57
+ dbMemory: string;
58
+ autoDeploy: false | "declared";
59
+ capabilities: CapabilityTier;
60
+ };
61
+ /** A customer's hosting entry: capability tier B, unless the entry declares `capabilities: 'standard'`. */
62
+ readonly companyApp: {
63
+ memory: string;
64
+ dbMemory: string;
65
+ autoDeploy: false | "declared";
66
+ capabilities: CapabilityTier;
67
+ };
68
+ readonly companyAppStandard: {
69
+ memory: string;
70
+ dbMemory: string;
71
+ autoDeploy: false | "declared";
72
+ capabilities: CapabilityTier;
73
+ };
74
+ readonly brokerEntry: {
75
+ memory: string;
76
+ dbMemory: string;
77
+ autoDeploy: false | "declared";
78
+ capabilities: CapabilityTier;
79
+ };
80
+ readonly cms: {
81
+ memory: string;
82
+ dbMemory: string;
83
+ autoDeploy: false | "declared";
84
+ capabilities: CapabilityTier;
85
+ };
86
+ readonly chat: {
87
+ memory: string;
88
+ dbMemory: string;
89
+ autoDeploy: false | "declared";
90
+ capabilities: CapabilityTier;
91
+ };
92
+ readonly calls: {
93
+ memory: string;
94
+ dbMemory: string;
95
+ autoDeploy: false | "declared";
96
+ capabilities: CapabilityTier;
97
+ };
98
+ readonly agora: {
99
+ memory: string;
100
+ dbMemory: string;
101
+ autoDeploy: false | "declared";
102
+ capabilities: CapabilityTier;
103
+ };
104
+ readonly boule: {
105
+ memory: string;
106
+ dbMemory: string;
107
+ autoDeploy: false | "declared";
108
+ capabilities: CapabilityTier;
109
+ };
110
+ readonly companyOther: {
111
+ memory: string;
112
+ dbMemory: string;
113
+ autoDeploy: false | "declared";
114
+ capabilities: CapabilityTier;
115
+ };
116
+ readonly platformArchon: {
117
+ memory: string;
118
+ dbMemory: string;
119
+ autoDeploy: false | "declared";
120
+ capabilities: CapabilityTier;
121
+ };
122
+ readonly platformAi: {
123
+ memory: string;
124
+ dbMemory: string;
125
+ autoDeploy: false | "declared";
126
+ capabilities: CapabilityTier;
127
+ };
128
+ readonly platformIntegrations: {
129
+ memory: string;
130
+ dbMemory: string;
131
+ autoDeploy: false | "declared";
132
+ capabilities: CapabilityTier;
133
+ };
134
+ readonly platformMailRelay: {
135
+ memory: string;
136
+ dbMemory: string;
137
+ autoDeploy: false | "declared";
138
+ capabilities: CapabilityTier;
139
+ };
140
+ readonly platformIngress: {
141
+ memory: string;
142
+ dbMemory: string;
143
+ autoDeploy: false | "declared";
144
+ capabilities: CapabilityTier;
145
+ };
146
+ readonly connector: {
147
+ memory: string;
148
+ dbMemory: string;
149
+ autoDeploy: false | "declared";
150
+ capabilities: CapabilityTier;
151
+ };
152
+ readonly platformFiles: {
153
+ memory: string;
154
+ dbMemory: string;
155
+ autoDeploy: false | "declared";
156
+ capabilities: CapabilityTier;
157
+ };
158
+ readonly platformEdge: {
159
+ memory: string;
160
+ dbMemory: string;
161
+ autoDeploy: false | "declared";
162
+ capabilities: CapabilityTier;
163
+ };
164
+ readonly platformHc: {
165
+ memory: string;
166
+ dbMemory: string;
167
+ autoDeploy: false | "declared";
168
+ capabilities: CapabilityTier;
169
+ };
170
+ readonly platformMail: {
171
+ memory: string;
172
+ dbMemory: string;
173
+ autoDeploy: false | "declared";
174
+ capabilities: CapabilityTier;
175
+ };
176
+ readonly platformAuth: {
177
+ memory: string;
178
+ dbMemory: string;
179
+ autoDeploy: false | "declared";
180
+ capabilities: CapabilityTier;
181
+ };
182
+ readonly platformJobs: {
183
+ memory: string;
184
+ dbMemory: string;
185
+ autoDeploy: false | "declared";
186
+ capabilities: CapabilityTier;
187
+ };
188
+ };
189
+ /** The same bound the neighbouring Coolify calls in this package use for a call that may hang. */
190
+ export declare const OWN_LIMITS_TIMEOUT_MS = 60000;
191
+ type LimitsPort = Partial<Pick<CoolifyPort, 'createGithubAppApplication' | 'createDockerImageApplication' | 'createDockerfileApplication' | 'createPostgresDatabase' | 'findApplicationByName' | 'getApplication' | 'getDatabase' | 'updateApplication' | 'raw'>>;
192
+ /**
193
+ * The Coolify port a step uses, with the profile's memory limit set on every
194
+ * application and database it creates, and set again on one it finds without
195
+ * it, so a company provisioned before the limits existed converges on its
196
+ * next apply. With `autoDeploy: false` a git application is created with
197
+ * auto-deploy off, and one found or updated with it on is switched off. A
198
+ * failed limits call fails the calling step before it records its applied
199
+ * marker; the next run finds the resource and sets them.
200
+ *
201
+ * `scope` is a profile, or a function that gives the profile of the step now
202
+ * running (none: the port is used as it is).
203
+ *
204
+ * Every limits call has a 60 second timeout. A step that creates a resource
205
+ * and then fails on the limits call leaves a resource without limits; the
206
+ * next run finds it by name and sets them.
207
+ */
208
+ export declare function limited<C extends LimitsPort>(coolify: C, scope?: LimitProfile | (() => LimitProfile | undefined)): C;
209
+ /** The deps of a step with their Coolify port limited by `profile`. */
210
+ export declare function limitedDeps<D extends {
211
+ coolify: LimitsPort;
212
+ }>(deps: D, profile: LimitProfile): D;
213
+ export {};
@@ -0,0 +1,232 @@
1
+ import { hasCapabilities, withCapabilities } from './run-options.js';
2
+ /**
3
+ * The hard limits of every container foundry creates, per step (`LIMITS`). Field names are Coolify's (`PATCH /applications/{uuid}`
4
+ * and `PATCH /databases/{uuid}`: `limits_memory`, `limits_cpus`). Coolify has
5
+ * no field for a process limit, and its `custom_docker_run_options` parser drops
6
+ * `--pids-limit`, so 512 processes cannot be set through Coolify.
7
+ */
8
+ export const OWN_LIMITS_MEMORY = '1g';
9
+ export const OWN_LIMITS_CPUS = '1';
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
+ };
46
+ /** The same bound the neighbouring Coolify calls in this package use for a call that may hang. */
47
+ export const OWN_LIMITS_TIMEOUT_MS = 60_000;
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);
53
+ async function bounded(work, what) {
54
+ let timer;
55
+ try {
56
+ return await Promise.race([
57
+ work,
58
+ new Promise((_, reject) => {
59
+ timer = setTimeout(() => reject(new Error(`${what}: setting the resource limits timed out`)), OWN_LIMITS_TIMEOUT_MS);
60
+ }),
61
+ ]);
62
+ }
63
+ finally {
64
+ clearTimeout(timer);
65
+ }
66
+ }
67
+ const wrapped = new WeakSet();
68
+ /**
69
+ * The Coolify port a step uses, with the profile's memory limit set on every
70
+ * application and database it creates, and set again on one it finds without
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.
83
+ */
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) => {
91
+ if (!coolify.updateApplication)
92
+ throw new Error('own limits: the Coolify port has no updateApplication');
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}`);
109
+ };
110
+ const patchDb = (uuid, p) => {
111
+ if (!coolify.raw)
112
+ throw new Error('own limits: the Coolify port has no raw');
113
+ return bounded(coolify.raw('PATCH', `/databases/${uuid}`, dbLimits(p)), `database ${uuid}`);
114
+ };
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
+ }
137
+ return created;
138
+ };
139
+ const over = {};
140
+ if (coolify.createGithubAppApplication)
141
+ over.createGithubAppApplication = afterCreate('createGithubAppApplication', true);
142
+ if (coolify.createDockerImageApplication)
143
+ over.createDockerImageApplication = afterCreate('createDockerImageApplication', false);
144
+ if (coolify.createDockerfileApplication)
145
+ over.createDockerfileApplication = afterCreate('createDockerfileApplication', false);
146
+ if (coolify.createPostgresDatabase) {
147
+ over.createPostgresDatabase = async (input) => {
148
+ const created = await coolify.createPostgresDatabase.call(coolify, input);
149
+ const p = current();
150
+ if (p)
151
+ await patchDb(created.uuid, p);
152
+ return created;
153
+ };
154
+ }
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
+ }
186
+ if (getApplication) {
187
+ over.getApplication = async (uuid) => {
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));
192
+ return app;
193
+ };
194
+ }
195
+ const find = coolify.findApplicationByName;
196
+ if (find && getApplication) {
197
+ over.findApplicationByName = async (...args) => {
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
+ }
205
+ return found;
206
+ };
207
+ }
208
+ const getDatabase = coolify.getDatabase;
209
+ if (getDatabase) {
210
+ over.getDatabase = async (uuid) => {
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);
215
+ return db;
216
+ };
217
+ }
218
+ const proxy = new Proxy(coolify, {
219
+ get(target, prop) {
220
+ if (typeof prop === 'string' && prop in over)
221
+ return over[prop];
222
+ const value = Reflect.get(target, prop, target);
223
+ return typeof value === 'function' ? value.bind(target) : value;
224
+ },
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) };
232
+ }
@@ -0,0 +1,193 @@
1
+ import type { LivekitCredentials } from '../calls-sfu-keys.js';
2
+ import type { CallsSfuUrls } from '../calls-sfu.js';
3
+ import type { DeclaredOrg } from '../org.js';
4
+ import type { BrokerPort } from './broker-entries.js';
5
+ import type { DnsPort } from './edge-dns.js';
6
+ import type { ZonePort } from './edge-zones.js';
7
+ import type { CallsSfuPort, CloudflarePort, CoolifyPort, FilesOrganisationPort, FoundryStepExecutorPorts, JobsSchedulerPort, MailPort, ZitadelPort } from './executor.js';
8
+ import type { PlatformStepExecutionContext, StepExecutionContext } from './plan-apply.js';
9
+ import type { FoundryPlatformStepExecutorOptions, PlatformAuthDeps, PlatformDnsFactory, PlatformFilesDeps, PlatformMailDeps, PlatformTunnelPort, PlatformTunnelsFactory } from './platform-providers.js';
10
+ /** Applied-state key under which an own org's provisioned-services marker is recorded. */
11
+ export declare const OWN_SERVICES_APPLIED_SCOPE_KEY = "own_services_applied";
12
+ /**
13
+ * Every name a service step creates, so the platform's steps and an own
14
+ * org's steps take them from one place. Names are unique on the server: each
15
+ * Coolify app name, each `custom_internal_name` on the shared Docker network
16
+ * and each held-key ref carries the owner (`platform` or the org slug).
17
+ */
18
+ export interface ServiceTarget {
19
+ readonly id: string;
20
+ readonly kind: 'platform' | 'org';
21
+ readonly names: {
22
+ authDb: string;
23
+ authZitadel: string;
24
+ authLogin: string;
25
+ authBroker: string;
26
+ authTunnel: string;
27
+ authCloudflared: string;
28
+ mailApp: string;
29
+ mailTunnel: string;
30
+ mailCloudflared: string;
31
+ filesDb: string;
32
+ filesMigrate: string;
33
+ filesRunner: string;
34
+ /** An own org's R2 bucket. The platform's is host-supplied (`PlatformFilesDeps.r2`), so its value here is never read. */
35
+ filesBucket: string;
36
+ jobsApp: string;
37
+ jobsDb: string;
38
+ sfuApp: string;
39
+ sfuTunnel: string;
40
+ sfuCloudflared: string;
41
+ };
42
+ readonly internal: {
43
+ authZitadel: string;
44
+ authLogin: string;
45
+ authBroker: string;
46
+ mail: string;
47
+ files: string;
48
+ jobs: string;
49
+ sfu: string;
50
+ };
51
+ readonly refs: {
52
+ instanceBearer: string;
53
+ authAdmin: string;
54
+ brokerMailKey: string;
55
+ mailAdminKey: string;
56
+ filesManagement: string;
57
+ filesIntrospection: string;
58
+ jobsAdminToken: string;
59
+ livekitKeys: string;
60
+ /** An own files host's derived keys credential, held after the mint. The platform's value is never read. */
61
+ keysCredential: string;
62
+ /** The public half the host verifies with (`KEYS_VERIFY_KEYS`). */
63
+ keysVerifyKeys: string;
64
+ };
65
+ }
66
+ /** The platform's shared services, with the literals the platform steps use today. */
67
+ export declare function platformTarget(): ServiceTarget;
68
+ /** One company's own services: every name and ref carries the org's slug, so two orgs never clash. */
69
+ export declare function orgTarget(org: Pick<DeclaredOrg, 'slug'>): ServiceTarget;
70
+ /** Where a step reads and records its ids: the platform's store or one org's. */
71
+ export interface StepScope {
72
+ /** This scope's own ids (platform store or org store). */
73
+ recordedIds(): Readonly<Record<string, string>>;
74
+ recordScopeId(key: string, id: string): void;
75
+ persist(): Promise<void>;
76
+ /** Read only. For a platform scope this is `recordedIds()`. */
77
+ platformIds(): Readonly<Record<string, string>>;
78
+ }
79
+ export declare function scopeFromPlatform(context: PlatformStepExecutionContext, _platformIds?: never): StepScope;
80
+ export declare function scopeFromOrg(context: StepExecutionContext, platformIds: Readonly<Record<string, string>>): StepScope;
81
+ export interface ServiceNetwork {
82
+ dns(): Promise<DnsPort>;
83
+ tunnels(): Promise<PlatformTunnelPort>;
84
+ zoneId(apex: string): Promise<string | undefined>;
85
+ }
86
+ export declare function platformNetwork(deps: {
87
+ dns: PlatformDnsFactory;
88
+ tunnels: PlatformTunnelsFactory;
89
+ cloudflare: Pick<CloudflarePort, 'findZoneByApex'>;
90
+ }, scope: StepScope, step: string): ServiceNetwork;
91
+ /**
92
+ * An own org's DNS and tunnels: the org's edge-routed ports, never the
93
+ * platform's scope, and the zone edge recorded for the org's domain. A port
94
+ * the host did not supply is named in the error, never reached around.
95
+ */
96
+ export declare function orgNetwork(ports: {
97
+ dns?: DnsPort;
98
+ tunnels?: PlatformTunnelPort;
99
+ zones?: Pick<ZonePort, 'findZoneByApex'>;
100
+ }, scope: StepScope, step: string): ServiceNetwork;
101
+ /** Where the project comes from: the platform's 'platform' project, or the org's own. */
102
+ export interface ProjectSource {
103
+ ensure(coolify: Pick<CoolifyPort, 'findProjectByName' | 'createProject'>, scope: StepScope): Promise<string>;
104
+ }
105
+ export interface OwnAuthInstance {
106
+ issuerUrl: string;
107
+ bearerRef: string;
108
+ }
109
+ /** Every field optional: absent means "not ready", and the executor throws {@link OwnServicesBlockedError} naming the ref. */
110
+ export interface OwnServicePorts {
111
+ zitadel?: ZitadelPort;
112
+ mail?: MailPort;
113
+ broker?: BrokerPort;
114
+ filesOrganisation?: FilesOrganisationPort;
115
+ jobsScheduler?: JobsSchedulerPort;
116
+ callsSfu?: CallsSfuPort;
117
+ callsSfuUrls?: CallsSfuUrls;
118
+ }
119
+ /** Dependencies of the `coolify:own-*` steps themselves. */
120
+ export interface OwnServicesProvision {
121
+ auth: PlatformAuthDeps;
122
+ mail: PlatformMailDeps;
123
+ files: OwnFilesDeps;
124
+ jobs: NonNullable<FoundryPlatformStepExecutorOptions['coolify']>;
125
+ sfu: OwnSfuDeps;
126
+ }
127
+ /** What the keys worker answers for one company's derived host: the credential and the public keys, plus ids for the record. */
128
+ export interface FilesKeysMinted {
129
+ credential: string;
130
+ verifyKeys: string;
131
+ credentialId: string;
132
+ epoch: number;
133
+ expiresAt: number;
134
+ }
135
+ /** Mints a company's own keys credential on the keys worker. Called with the host id `files-<slug>`. */
136
+ export interface FilesKeysMint {
137
+ workerUrl: string;
138
+ mint(hostId: string, requestId: string): Promise<FilesKeysMinted>;
139
+ }
140
+ /** One S3 pair for one bucket, and the id of the API token it came from. The secret is shown once. */
141
+ export interface OwnFilesR2Key {
142
+ accessKeyId: string;
143
+ secretAccessKey: string;
144
+ tokenId: string;
145
+ }
146
+ /**
147
+ * Mints and removes the storage key of one company bucket. Each call is given 30 seconds. A delete
148
+ * of a token that is already gone (404) is a success.
149
+ */
150
+ export interface OwnFilesR2Keys {
151
+ mintBucketKey(bucket: string): Promise<OwnFilesR2Key>;
152
+ deleteBucketKey(tokenId: string): Promise<void>;
153
+ }
154
+ /** The token id of the storage key delivered to an own files host. Its presence means the step is done. */
155
+ export declare const OWN_FILES_R2_SCOPE_KEY = "files_r2_id";
156
+ /** The token id minted and not yet confirmed delivered: deleted before any new mint. */
157
+ export declare const OWN_FILES_R2_UNDELIVERED_SCOPE_KEY = "files_r2_undelivered";
158
+ /**
159
+ * An own files host takes no `keys.credential`, `keys.auditToken` or `jobsSigningSecrets` from here:
160
+ * those are the platform's and never reach a company's containers. Its keyring comes from `keysMint`.
161
+ */
162
+ export interface OwnFilesDeps extends Omit<PlatformFilesDeps, 'r2'> {
163
+ r2Buckets: {
164
+ ensureBucket(name: string): Promise<{
165
+ bucket: string;
166
+ endpoint: string;
167
+ }>;
168
+ };
169
+ /** Absent: the own files R2 key step is blocked, naming this port. */
170
+ r2Keys?: OwnFilesR2Keys;
171
+ /** Absent: the own files step is blocked, naming this port. */
172
+ keysMint?: FilesKeysMint;
173
+ /** Reads a delivered secret back from the host, so a rerun after a failed run resumes instead of minting. */
174
+ readSecret?: (ref: string) => Promise<string | null>;
175
+ }
176
+ export interface OwnSfuDeps {
177
+ coolify: Pick<CoolifyPort, 'findProjectByName' | 'createProject' | 'findOrCreateEnvironment' | 'listServers' | 'findApplicationByName' | 'createDockerImageApplication' | 'updateApplication' | 'bulkUpsertEnvs' | 'deploy' | 'getApplication' | 'raw' | 'listEnvs' | 'createDockerfileApplication' | 'deployAndWait'>;
178
+ mintLivekitCredentials?: () => LivekitCredentials;
179
+ deliverSecret: FoundryStepExecutorPorts['deliverSecret'];
180
+ }
181
+ /** A held key an own service needs is missing. Names the service and the ref, never a value. */
182
+ export declare class OwnServicesBlockedError extends Error {
183
+ readonly service: string;
184
+ readonly ref: string;
185
+ constructor(service: string, ref: string);
186
+ }
187
+ export interface OwnServicesPort {
188
+ /** Ports scoped to this org's own instances, or null when the org is not own. Throws {@link OwnServicesBlockedError} when a held key is missing. */
189
+ portsFor(org: DeclaredOrg, recordedIds: Readonly<Record<string, string>>): Promise<OwnServicePorts | null>;
190
+ /** Builds the ZITADEL port for one company instance. */
191
+ zitadelFor(instance: OwnAuthInstance): ZitadelPort;
192
+ provision: OwnServicesProvision;
193
+ }