@wtfalch/foundry 0.32.0 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bootstrap/bootstrap.d.ts +18 -1
- package/dist/bootstrap/bootstrap.js +167 -28
- package/dist/bootstrap/declare.d.ts +5 -1
- package/dist/bootstrap/declare.js +3 -1
- package/dist/bootstrap/index.d.ts +1 -1
- package/dist/bootstrap/versions.d.ts +4 -0
- package/dist/calls-sfu.d.ts +10 -0
- package/dist/calls-sfu.js +41 -0
- package/dist/index.d.ts +18 -8
- package/dist/index.js +8 -3
- package/dist/migrations/0051_org_services.sql +13 -0
- package/dist/migrations/0052_platform_mail_relay.sql +8 -0
- package/dist/org.d.ts +24 -2
- package/dist/org.js +42 -0
- package/dist/plan/boule.d.ts +24 -1
- package/dist/plan/boule.js +29 -1
- package/dist/plan/calls-matrix.d.ts +9 -0
- package/dist/plan/calls-matrix.js +17 -0
- package/dist/plan/cloudflared-connector.d.ts +7 -1
- package/dist/plan/cloudflared-connector.js +14 -1
- package/dist/plan/diff.d.ts +32 -1
- package/dist/plan/diff.js +383 -26
- package/dist/plan/executor.d.ts +177 -7
- package/dist/plan/executor.js +738 -51
- 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.js +1 -1
- package/dist/plan/held-key-refs.d.ts +10 -0
- package/dist/plan/held-key-refs.js +10 -0
- package/dist/plan/index.d.ts +16 -6
- package/dist/plan/index.js +8 -3
- package/dist/plan/live.d.ts +5 -2
- package/dist/plan/live.js +15 -2
- package/dist/plan/locked-hostnames.d.ts +5 -2
- package/dist/plan/locked-hostnames.js +9 -1
- package/dist/plan/login-host.d.ts +62 -2
- package/dist/plan/login-host.js +104 -1
- package/dist/plan/mail-relay.d.ts +120 -0
- package/dist/plan/mail-relay.js +323 -0
- package/dist/plan/org-names.d.ts +24 -0
- package/dist/plan/org-names.js +52 -0
- package/dist/plan/own-diff.d.ts +169 -0
- package/dist/plan/own-diff.js +432 -0
- package/dist/plan/own-dns.d.ts +6 -0
- package/dist/plan/own-dns.js +15 -0
- package/dist/plan/own-limits.d.ts +22 -0
- package/dist/plan/own-limits.js +101 -0
- package/dist/plan/own-services.d.ts +193 -0
- package/dist/plan/own-services.js +167 -0
- package/dist/plan/own-sfu.d.ts +69 -0
- package/dist/plan/own-sfu.js +255 -0
- package/dist/plan/plan-apply.d.ts +101 -7
- package/dist/plan/plan-apply.js +216 -12
- package/dist/plan/platform-diff.d.ts +61 -12
- package/dist/plan/platform-diff.js +140 -34
- package/dist/plan/platform-org.d.ts +36 -0
- package/dist/plan/platform-org.js +96 -2
- package/dist/plan/platform-providers.d.ts +407 -12
- package/dist/plan/platform-providers.js +947 -422
- package/dist/plan/sign-in-org.d.ts +99 -0
- package/dist/plan/sign-in-org.js +151 -0
- package/dist/plan/zitadel-login-policy.js +1 -1
- package/dist/plan/zitadel-system-api.d.ts +22 -1
- package/dist/plan/zitadel-system-api.js +49 -8
- package/dist/plan.d.ts +18 -0
- package/dist/platform.d.ts +8 -0
- package/dist/slug.js +1 -0
- package/dist/store/postgres.js +51 -2
- package/dist/store/schema.d.ts +51 -0
- package/dist/store/schema.js +5 -0
- package/package.json +1 -1
- package/versions.json +3 -1
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { type DeclaredOrg } from '../org.js';
|
|
2
|
+
import type { DraftStep } from './diff.js';
|
|
3
|
+
import { type OrgPlatformReadiness } from './platform-org.js';
|
|
4
|
+
/** `unmanaged` key of the one line `planOwnJobs` reports. */
|
|
5
|
+
export declare const OWN_JOBS_BLOCKED_KEY = "own-jobs";
|
|
6
|
+
export interface PlanOwnJobsOptions {
|
|
7
|
+
org: DeclaredOrg;
|
|
8
|
+
recordedIds: Readonly<Record<string, string>>;
|
|
9
|
+
readiness: OrgPlatformReadiness;
|
|
10
|
+
isPlatformOrg: boolean;
|
|
11
|
+
/** The executor was built with an `ownServices` port. */
|
|
12
|
+
ownServices: boolean;
|
|
13
|
+
/** The admin token's held-key ref holds a value, so an undelivered marker is resumed, not blocked. */
|
|
14
|
+
tokenHeld?: boolean;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* An own org (`services.own`) runs its own jobs scheduler: a database, an app
|
|
18
|
+
* and an admin token in the org's own Coolify project. Applied once, and
|
|
19
|
+
* again when the platform's declared jobs ref changed, an earlier run died
|
|
20
|
+
* before the deploy finished, or the address was never recorded. Every
|
|
21
|
+
* blocked line is fixed text: it is part of the stale-plan comparison.
|
|
22
|
+
*/
|
|
23
|
+
export declare function planOwnJobs(options: PlanOwnJobsOptions): {
|
|
24
|
+
step: DraftStep | null;
|
|
25
|
+
blocked: string | null;
|
|
26
|
+
};
|
|
27
|
+
/** `unmanaged` key of the one line `planOwnAuth` reports. */
|
|
28
|
+
export declare const OWN_AUTH_BLOCKED_KEY = "own-auth";
|
|
29
|
+
/** The hostname an own org's sign-in last applied at, so a changed domain plans the step again. */
|
|
30
|
+
export declare const OWN_AUTH_APPLIED_HOSTNAME_SCOPE_KEY = "own_auth_applied_hostname";
|
|
31
|
+
/** The auth git ref an own org's sign-in last deployed, so a changed declared ref plans the step again. */
|
|
32
|
+
export declare const OWN_AUTH_APPLIED_GIT_REF_SCOPE_KEY = "own_auth_applied_git_ref";
|
|
33
|
+
export interface PlanOwnAuthOptions {
|
|
34
|
+
org: DeclaredOrg;
|
|
35
|
+
recordedIds: Readonly<Record<string, string>>;
|
|
36
|
+
readiness: OrgPlatformReadiness;
|
|
37
|
+
isPlatformOrg: boolean;
|
|
38
|
+
/** The executor was built with an `ownServices` port. */
|
|
39
|
+
ownServices: boolean;
|
|
40
|
+
/** The org's Cloudflare zone as the live read shows it, or null when none exists yet. */
|
|
41
|
+
zone: {
|
|
42
|
+
active: boolean;
|
|
43
|
+
} | null;
|
|
44
|
+
/** The org's own Lettermint key (`orgTarget(org).refs.brokerMailKey`) is held. */
|
|
45
|
+
mailKeyHeld: boolean;
|
|
46
|
+
/** The host can mint that key itself (a Lettermint port and a held-key reader): the step plans without it. */
|
|
47
|
+
mailKeyMintable?: boolean;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* An own org (`services.own`) runs its own sign-in: ZITADEL, the login app
|
|
51
|
+
* and the broker in the org's own Coolify project, at `auth.<domain>`.
|
|
52
|
+
* Applied once, and again when the declared domain or the platform's auth ref
|
|
53
|
+
* changed or an earlier run died before it finished. A login client that is
|
|
54
|
+
* not minted yet plans the step again only once the broker holds an entry:
|
|
55
|
+
* until then it waits on `AUTH_APPS`, which no re-run of this step supplies. Every blocked line is fixed
|
|
56
|
+
* text: it is part of the stale-plan comparison.
|
|
57
|
+
*/
|
|
58
|
+
export declare function planOwnAuth(options: PlanOwnAuthOptions): {
|
|
59
|
+
step: DraftStep | null;
|
|
60
|
+
blocked: string | null;
|
|
61
|
+
};
|
|
62
|
+
/** `unmanaged` key of the one line `planOwnMail` reports. */
|
|
63
|
+
export declare const OWN_MAIL_BLOCKED_KEY = "own-mail";
|
|
64
|
+
export interface PlanOwnMailOptions {
|
|
65
|
+
org: DeclaredOrg;
|
|
66
|
+
recordedIds: Readonly<Record<string, string>>;
|
|
67
|
+
readiness: OrgPlatformReadiness;
|
|
68
|
+
isPlatformOrg: boolean;
|
|
69
|
+
/** The executor was built with an `ownServices` port. */
|
|
70
|
+
ownServices: boolean;
|
|
71
|
+
/** `platform.mail.relay` is declared and the relay recorded a route list as applied. */
|
|
72
|
+
mailRelayApplied: boolean;
|
|
73
|
+
/** The org's zone is live and active. */
|
|
74
|
+
zoneActive: boolean;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* An own org (`services.own`) with mail on gets its own Stalwart in its own
|
|
78
|
+
* Coolify project, behind the shared relay. It waits for the org's own
|
|
79
|
+
* sign-in (its directory trusts that issuer), the relay (port 25 has no other
|
|
80
|
+
* way in) and the zone. Applied once, and again when the hostname or the
|
|
81
|
+
* image tag changed. Every blocked line is fixed text: it is part of the
|
|
82
|
+
* stale-plan comparison.
|
|
83
|
+
*/
|
|
84
|
+
export declare function planOwnMail(options: PlanOwnMailOptions): {
|
|
85
|
+
step: DraftStep | null;
|
|
86
|
+
blocked: string | null;
|
|
87
|
+
};
|
|
88
|
+
/** `unmanaged` key of the one blocked line `planOwnSfu` reports. */
|
|
89
|
+
export declare const OWN_SFU_BLOCKED_KEY = "own-sfu";
|
|
90
|
+
/** `unmanaged` key of the firewall line: the server's ports foundry cannot open itself. */
|
|
91
|
+
export declare const OWN_SFU_FIREWALL_KEY = "own-sfu:firewall";
|
|
92
|
+
export interface PlanOwnSfuOptions {
|
|
93
|
+
org: DeclaredOrg;
|
|
94
|
+
recordedIds: Readonly<Record<string, string>>;
|
|
95
|
+
isPlatformOrg: boolean;
|
|
96
|
+
/** The executor was built with an `ownServices` port. */
|
|
97
|
+
ownServices: boolean;
|
|
98
|
+
/** The org's zone is live and active: none is planned and none is waiting. */
|
|
99
|
+
zoneActive: boolean;
|
|
100
|
+
readiness: OrgPlatformReadiness;
|
|
101
|
+
/** The host confirmed the slot's ports are open on the server. */
|
|
102
|
+
sfuPortsOpen: boolean;
|
|
103
|
+
/** The key pair's held-key ref holds a value, so an undelivered marker is resumed, not blocked. */
|
|
104
|
+
keysHeld?: boolean;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* An own org (`services.own`) with calls on runs its own LiveKit media server
|
|
108
|
+
* as a Coolify app. Applied once, and again when the pinned LiveKit version,
|
|
109
|
+
* the slot or the hostname changed, or an earlier run died before the deploy
|
|
110
|
+
* finished. `firewall` is the one line that says which ports the server must
|
|
111
|
+
* have open: foundry opens none, so it stays until the host confirms them.
|
|
112
|
+
* Every line is fixed text: it is part of the stale-plan comparison.
|
|
113
|
+
*/
|
|
114
|
+
export declare function planOwnSfu(options: PlanOwnSfuOptions): {
|
|
115
|
+
step: DraftStep | null;
|
|
116
|
+
blocked: string | null;
|
|
117
|
+
firewall: string | null;
|
|
118
|
+
};
|
|
119
|
+
/** `unmanaged` key of the one line `planOwnFiles` reports. */
|
|
120
|
+
export declare const OWN_FILES_BLOCKED_KEY = "own-files";
|
|
121
|
+
export interface PlanOwnFilesOptions {
|
|
122
|
+
org: DeclaredOrg;
|
|
123
|
+
recordedIds: Readonly<Record<string, string>>;
|
|
124
|
+
readiness: OrgPlatformReadiness;
|
|
125
|
+
isPlatformOrg: boolean;
|
|
126
|
+
/** The executor was built with an `ownServices` port. */
|
|
127
|
+
ownServices: boolean;
|
|
128
|
+
/** What the host's own files dependencies supply: valid keys worker settings, and a bucket port. */
|
|
129
|
+
filesDeps: {
|
|
130
|
+
keys: boolean;
|
|
131
|
+
bucket: boolean;
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* An own org (`services.own`) runs its own files host: a database, a migrate
|
|
136
|
+
* app, a runner app, a ZITADEL project in the company's own instance and a
|
|
137
|
+
* bucket. Applied once, and again when the declared domain, the platform's
|
|
138
|
+
* files ref or the org's share domains changed or an earlier run died before
|
|
139
|
+
* it finished. A secret minted but not confirmed delivered never plans the
|
|
140
|
+
* step again. Every blocked line is fixed text: it is part of the stale-plan
|
|
141
|
+
* comparison.
|
|
142
|
+
*/
|
|
143
|
+
export declare function planOwnFiles(options: PlanOwnFilesOptions): {
|
|
144
|
+
step: DraftStep | null;
|
|
145
|
+
blocked: string | null;
|
|
146
|
+
};
|
|
147
|
+
/** `unmanaged` key of the one line `planOwnFilesR2` reports. */
|
|
148
|
+
export declare const OWN_FILES_R2_BLOCKED_KEY = "own-files-r2";
|
|
149
|
+
export interface PlanOwnFilesR2Options {
|
|
150
|
+
org: DeclaredOrg;
|
|
151
|
+
recordedIds: Readonly<Record<string, string>>;
|
|
152
|
+
isPlatformOrg: boolean;
|
|
153
|
+
/** The executor was built with an `ownServices` port. */
|
|
154
|
+
ownServices: boolean;
|
|
155
|
+
/** Which ports the step needs the host supplied: the R2 key port, and the files-organisation `storeR2Credentials`. */
|
|
156
|
+
ports: {
|
|
157
|
+
r2Keys: boolean;
|
|
158
|
+
storeR2Credentials: boolean;
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* The storage key of an own org's files bucket: planned once its files host is applied (the host must be
|
|
163
|
+
* up to receive the key) and until `files_r2_id` is recorded. A token minted and not confirmed delivered
|
|
164
|
+
* does not block the step: the next run deletes it first. The blocked line is fixed text.
|
|
165
|
+
*/
|
|
166
|
+
export declare function planOwnFilesR2(options: PlanOwnFilesR2Options): {
|
|
167
|
+
step: DraftStep | null;
|
|
168
|
+
blocked: string | null;
|
|
169
|
+
};
|
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
import { VERSIONS, floatingTagBlocked } from '../bootstrap/versions.js';
|
|
2
|
+
import { sfuSlotPorts } from '../calls-sfu.js';
|
|
3
|
+
import { SFU_SLOT_MAX, ownsServices } from '../org.js';
|
|
4
|
+
import { ZITADEL_ORG_SCOPE_KEY } from './agora.js';
|
|
5
|
+
import { brokerEntryAppliedScopeKey } from './broker-entries.js';
|
|
6
|
+
import { ZITADEL_FILES_PROJECT_SCOPE_KEY } from './core-files.js';
|
|
7
|
+
import { ownAuthApplied } from './login-host.js';
|
|
8
|
+
import { OWN_MAIL_SMTP_INTERNAL_SCOPE_KEY } from './mail-relay.js';
|
|
9
|
+
import { OWN_FILES_R2_SCOPE_KEY, OWN_SERVICES_APPLIED_SCOPE_KEY, orgTarget, } from './own-services.js';
|
|
10
|
+
import { OWN_SFU_APPLIED_HOSTNAME_SCOPE_KEY, OWN_SFU_APPLIED_SLOT_SCOPE_KEY, OWN_SFU_APPLIED_VERSION_SCOPE_KEY, OWN_SFU_DEPLOY_PENDING_SCOPE_KEY, OWN_SFU_KEYS_UNDELIVERED_SCOPE_KEY, SFU_INTERNAL_URL_SCOPE_KEY, SFU_PUBLIC_URL_SCOPE_KEY, } from './own-sfu.js';
|
|
11
|
+
import { AUTH_ZITADEL_RUN_OPTIONS, JOBS_DEPLOY_PENDING_SCOPE_KEY, JOBS_INTERNAL_URL_SCOPE_KEY, PLATFORM_AUTH_ADMIN_UNDELIVERED_SCOPE_KEY, PLATFORM_AUTH_ADMIN_USER_SCOPE_KEY, PLATFORM_AUTH_BROKER_SCOPE_KEY, PLATFORM_AUTH_CONNECTOR_APP_SCOPE_KEY, PLATFORM_AUTH_INSTANCE_SCOPE_KEY, PLATFORM_AUTH_INSTANCE_UNDELIVERED_SCOPE_KEY, PLATFORM_AUTH_LOGIN_CLIENT_UNDELIVERED_SCOPE_KEY, PLATFORM_AUTH_LOGIN_CLIENT_USER_SCOPE_KEY, PLATFORM_AUTH_SMTP_SCOPE_KEY, PLATFORM_AUTH_ZITADEL_RUN_OPTIONS_SCOPE_KEY, PLATFORM_FILES_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_FILES_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, PLATFORM_FILES_INTROSPECTION_SCOPE_KEY, PLATFORM_FILES_INTROSPECTION_UNDELIVERED_SCOPE_KEY, PLATFORM_FILES_MANAGEMENT_KEY_SCOPE_KEY, PLATFORM_FILES_MANAGEMENT_UNDELIVERED_SCOPE_KEY, PLATFORM_MAIL_ADMIN_KEY_SCOPE_KEY, PLATFORM_MAIL_ADMIN_UNDELIVERED_SCOPE_KEY, PLATFORM_MAIL_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_MAIL_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_MAIL_INTERNAL_URL_SCOPE_KEY, platformMailMxHost, } from './platform-diff.js';
|
|
12
|
+
import { OWN_FILES_RUNNER_SCOPE_KEY } from './platform-org.js';
|
|
13
|
+
import { JOBS_ADMIN_TOKEN_UNDELIVERED_SCOPE_KEY, JOBS_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_AUTH_ZITADEL_SCOPE_KEY, PLATFORM_FILES_SHARE_DOMAINS_SCOPE_KEY, filesShareDomains, } from './platform-providers.js';
|
|
14
|
+
/** `unmanaged` key of the one line `planOwnJobs` reports. */
|
|
15
|
+
export const OWN_JOBS_BLOCKED_KEY = 'own-jobs';
|
|
16
|
+
/**
|
|
17
|
+
* An own org (`services.own`) runs its own jobs scheduler: a database, an app
|
|
18
|
+
* and an admin token in the org's own Coolify project. Applied once, and
|
|
19
|
+
* again when the platform's declared jobs ref changed, an earlier run died
|
|
20
|
+
* before the deploy finished, or the address was never recorded. Every
|
|
21
|
+
* blocked line is fixed text: it is part of the stale-plan comparison.
|
|
22
|
+
*/
|
|
23
|
+
export function planOwnJobs(options) {
|
|
24
|
+
const { org, recordedIds, readiness, isPlatformOrg, ownServices } = options;
|
|
25
|
+
if (!ownsServices(org))
|
|
26
|
+
return { step: null, blocked: null };
|
|
27
|
+
const blocked = (line) => ({ step: null, blocked: line });
|
|
28
|
+
if (isPlatformOrg) {
|
|
29
|
+
return blocked(`${org.slug} cannot run its own services: it is the platform's own org`);
|
|
30
|
+
}
|
|
31
|
+
const undelivered = recordedIds[JOBS_ADMIN_TOKEN_UNDELIVERED_SCOPE_KEY];
|
|
32
|
+
if (undelivered && !options.tokenHeld) {
|
|
33
|
+
return blocked(`${org.slug}'s own jobs scheduler admin token for app ${undelivered} was minted but not confirmed delivered; remove the recorded key ${JOBS_ADMIN_TOKEN_UNDELIVERED_SCOPE_KEY} to mint a new one`);
|
|
34
|
+
}
|
|
35
|
+
if (!ownServices) {
|
|
36
|
+
return blocked(`${org.slug}'s own jobs scheduler is blocked, not planned: the host supplies no ownServices port`);
|
|
37
|
+
}
|
|
38
|
+
const gitRef = readiness.serviceGitRefs?.jobs;
|
|
39
|
+
if (!gitRef) {
|
|
40
|
+
return blocked(`${org.slug}'s own jobs scheduler is blocked, not planned: the platform declares no jobs git ref`);
|
|
41
|
+
}
|
|
42
|
+
const applied = Boolean(recordedIds[`coolify:own-jobs:${org.id}`]);
|
|
43
|
+
if (applied &&
|
|
44
|
+
recordedIds[JOBS_APPLIED_GIT_REF_SCOPE_KEY] === gitRef &&
|
|
45
|
+
!recordedIds[JOBS_DEPLOY_PENDING_SCOPE_KEY] &&
|
|
46
|
+
recordedIds[JOBS_INTERNAL_URL_SCOPE_KEY]) {
|
|
47
|
+
return { step: null, blocked: null };
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
blocked: null,
|
|
51
|
+
step: {
|
|
52
|
+
action: {
|
|
53
|
+
system: 'coolify',
|
|
54
|
+
kind: 'own-jobs',
|
|
55
|
+
key: org.id,
|
|
56
|
+
desired: { gitRef },
|
|
57
|
+
},
|
|
58
|
+
step: {
|
|
59
|
+
description: `provision ${org.slug}'s own jobs scheduler`,
|
|
60
|
+
destroys: false,
|
|
61
|
+
migrates: false,
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
const BROKER_ENTRY_APPLIED_PREFIX = brokerEntryAppliedScopeKey('');
|
|
67
|
+
/** `unmanaged` key of the one line `planOwnAuth` reports. */
|
|
68
|
+
export const OWN_AUTH_BLOCKED_KEY = 'own-auth';
|
|
69
|
+
/** The hostname an own org's sign-in last applied at, so a changed domain plans the step again. */
|
|
70
|
+
export const OWN_AUTH_APPLIED_HOSTNAME_SCOPE_KEY = 'own_auth_applied_hostname';
|
|
71
|
+
/** The auth git ref an own org's sign-in last deployed, so a changed declared ref plans the step again. */
|
|
72
|
+
export const OWN_AUTH_APPLIED_GIT_REF_SCOPE_KEY = 'own_auth_applied_git_ref';
|
|
73
|
+
/**
|
|
74
|
+
* An own org (`services.own`) runs its own sign-in: ZITADEL, the login app
|
|
75
|
+
* and the broker in the org's own Coolify project, at `auth.<domain>`.
|
|
76
|
+
* Applied once, and again when the declared domain or the platform's auth ref
|
|
77
|
+
* changed or an earlier run died before it finished. A login client that is
|
|
78
|
+
* not minted yet plans the step again only once the broker holds an entry:
|
|
79
|
+
* until then it waits on `AUTH_APPS`, which no re-run of this step supplies. Every blocked line is fixed
|
|
80
|
+
* text: it is part of the stale-plan comparison.
|
|
81
|
+
*/
|
|
82
|
+
export function planOwnAuth(options) {
|
|
83
|
+
const { org, recordedIds, readiness, isPlatformOrg, ownServices } = options;
|
|
84
|
+
// `planOwnJobs` reports the platform's own org once.
|
|
85
|
+
if (!ownsServices(org) || isPlatformOrg || !org.domain)
|
|
86
|
+
return { step: null, blocked: null };
|
|
87
|
+
const blocked = (reason) => ({
|
|
88
|
+
step: null,
|
|
89
|
+
blocked: `${org.slug}'s own sign-in is blocked, not planned: ${reason}`,
|
|
90
|
+
});
|
|
91
|
+
if (recordedIds.zitadel_org && !recordedIds[OWN_SERVICES_APPLIED_SCOPE_KEY]) {
|
|
92
|
+
return {
|
|
93
|
+
step: null,
|
|
94
|
+
blocked: `${org.slug} already signs in on the shared service; moving it is a later phase`,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
const instanceLost = recordedIds[PLATFORM_AUTH_INSTANCE_UNDELIVERED_SCOPE_KEY];
|
|
98
|
+
if (instanceLost &&
|
|
99
|
+
!recordedIds[PLATFORM_AUTH_INSTANCE_SCOPE_KEY] &&
|
|
100
|
+
!recordedIds[PLATFORM_AUTH_ADMIN_USER_SCOPE_KEY]) {
|
|
101
|
+
return blocked(`a ZITADEL instance (${instanceLost}) may exist without a delivered owner token; delete it with the system API or redeploy its database, then remove the recorded key ${PLATFORM_AUTH_INSTANCE_UNDELIVERED_SCOPE_KEY}`);
|
|
102
|
+
}
|
|
103
|
+
const adminLost = recordedIds[PLATFORM_AUTH_ADMIN_UNDELIVERED_SCOPE_KEY];
|
|
104
|
+
if (adminLost && !recordedIds[PLATFORM_AUTH_ADMIN_USER_SCOPE_KEY]) {
|
|
105
|
+
return blocked(`the admin key of ZITADEL machine user ${adminLost} was minted but not confirmed delivered; delete that user and remove the recorded key ${PLATFORM_AUTH_ADMIN_UNDELIVERED_SCOPE_KEY} to mint a new one`);
|
|
106
|
+
}
|
|
107
|
+
const loginClientLost = recordedIds[PLATFORM_AUTH_LOGIN_CLIENT_UNDELIVERED_SCOPE_KEY];
|
|
108
|
+
if (loginClientLost && !recordedIds[PLATFORM_AUTH_LOGIN_CLIENT_USER_SCOPE_KEY]) {
|
|
109
|
+
return blocked(`the login-client token of ZITADEL machine user ${loginClientLost} was minted but not confirmed delivered; remove the recorded key ${PLATFORM_AUTH_LOGIN_CLIENT_UNDELIVERED_SCOPE_KEY} to mint a new one`);
|
|
110
|
+
}
|
|
111
|
+
if (!ownServices)
|
|
112
|
+
return blocked('the host supplies no ownServices port');
|
|
113
|
+
const gitRef = readiness.serviceGitRefs?.auth;
|
|
114
|
+
if (!gitRef)
|
|
115
|
+
return blocked('the platform declares no auth git ref');
|
|
116
|
+
if (!options.zone?.active)
|
|
117
|
+
return blocked(`the zone for ${org.domain} is not active yet`);
|
|
118
|
+
const ownEdge = readiness.ownEdgePorts;
|
|
119
|
+
if (readiness.dnsBlocked && !(ownEdge?.dns && ownEdge.tunnels)) {
|
|
120
|
+
return blocked(readiness.dnsBlocked);
|
|
121
|
+
}
|
|
122
|
+
if (!ownEdge?.tunnels && !readiness.edgeReady) {
|
|
123
|
+
return blocked('the host supplies no tunnel port and edge is not ready');
|
|
124
|
+
}
|
|
125
|
+
if (!options.mailKeyHeld && !options.mailKeyMintable) {
|
|
126
|
+
return blocked(`the held key ${orgTarget(org).refs.brokerMailKey} is not held yet`);
|
|
127
|
+
}
|
|
128
|
+
const hostname = `auth.${org.domain}`;
|
|
129
|
+
// The broker starts only on a valid `AUTH_APPS`, and the login client is minted only once it does:
|
|
130
|
+
// the first broker entry is what lets this step finish, as the platform's first registration does.
|
|
131
|
+
const brokerHoldsAnEntry = Object.keys(recordedIds).some((key) => key.startsWith(BROKER_ENTRY_APPLIED_PREFIX));
|
|
132
|
+
const applied = Boolean(recordedIds[`coolify:own-auth:${org.id}`]) &&
|
|
133
|
+
Boolean(recordedIds[OWN_SERVICES_APPLIED_SCOPE_KEY]);
|
|
134
|
+
if (applied &&
|
|
135
|
+
recordedIds[OWN_AUTH_APPLIED_HOSTNAME_SCOPE_KEY] === hostname &&
|
|
136
|
+
recordedIds[OWN_AUTH_APPLIED_GIT_REF_SCOPE_KEY] === gitRef &&
|
|
137
|
+
recordedIds[PLATFORM_AUTH_ZITADEL_SCOPE_KEY] &&
|
|
138
|
+
recordedIds[PLATFORM_AUTH_CONNECTOR_APP_SCOPE_KEY] &&
|
|
139
|
+
recordedIds[PLATFORM_AUTH_BROKER_SCOPE_KEY] &&
|
|
140
|
+
recordedIds[PLATFORM_AUTH_ADMIN_USER_SCOPE_KEY] &&
|
|
141
|
+
recordedIds[PLATFORM_AUTH_SMTP_SCOPE_KEY] &&
|
|
142
|
+
recordedIds[PLATFORM_AUTH_ZITADEL_RUN_OPTIONS_SCOPE_KEY] === AUTH_ZITADEL_RUN_OPTIONS &&
|
|
143
|
+
(Boolean(recordedIds[PLATFORM_AUTH_LOGIN_CLIENT_USER_SCOPE_KEY]) || !brokerHoldsAnEntry)) {
|
|
144
|
+
return { step: null, blocked: null };
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
blocked: null,
|
|
148
|
+
step: {
|
|
149
|
+
action: {
|
|
150
|
+
system: 'coolify',
|
|
151
|
+
kind: 'own-auth',
|
|
152
|
+
key: org.id,
|
|
153
|
+
desired: { hostname, gitRef },
|
|
154
|
+
},
|
|
155
|
+
step: {
|
|
156
|
+
description: `provision ${org.slug}'s own sign-in at ${hostname}`,
|
|
157
|
+
destroys: false,
|
|
158
|
+
migrates: false,
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
/** `unmanaged` key of the one line `planOwnMail` reports. */
|
|
164
|
+
export const OWN_MAIL_BLOCKED_KEY = 'own-mail';
|
|
165
|
+
/**
|
|
166
|
+
* An own org (`services.own`) with mail on gets its own Stalwart in its own
|
|
167
|
+
* Coolify project, behind the shared relay. It waits for the org's own
|
|
168
|
+
* sign-in (its directory trusts that issuer), the relay (port 25 has no other
|
|
169
|
+
* way in) and the zone. Applied once, and again when the hostname or the
|
|
170
|
+
* image tag changed. Every blocked line is fixed text: it is part of the
|
|
171
|
+
* stale-plan comparison.
|
|
172
|
+
*/
|
|
173
|
+
export function planOwnMail(options) {
|
|
174
|
+
const { org, recordedIds, readiness, isPlatformOrg, ownServices, mailRelayApplied, zoneActive } = options;
|
|
175
|
+
if (!ownsServices(org) || org.mail.mode === 'off' || isPlatformOrg) {
|
|
176
|
+
return { step: null, blocked: null };
|
|
177
|
+
}
|
|
178
|
+
const blocked = (reason) => ({
|
|
179
|
+
step: null,
|
|
180
|
+
blocked: `${org.slug}'s own mail server is blocked, not planned: ${reason}`,
|
|
181
|
+
});
|
|
182
|
+
const undelivered = recordedIds[PLATFORM_MAIL_ADMIN_UNDELIVERED_SCOPE_KEY];
|
|
183
|
+
if (undelivered && !recordedIds[PLATFORM_MAIL_ADMIN_KEY_SCOPE_KEY]) {
|
|
184
|
+
return blocked(`its admin API key ("${undelivered}") was minted but not confirmed delivered; revoke it in Stalwart and remove the recorded key ${PLATFORM_MAIL_ADMIN_UNDELIVERED_SCOPE_KEY} to mint a new one`);
|
|
185
|
+
}
|
|
186
|
+
if (!ownServices)
|
|
187
|
+
return blocked('the host supplies no ownServices port');
|
|
188
|
+
const gitRef = readiness.serviceGitRefs?.mail;
|
|
189
|
+
if (!gitRef)
|
|
190
|
+
return blocked('the platform declares no mail git ref');
|
|
191
|
+
const floating = floatingTagBlocked('Stalwart', gitRef, 'v0.16.24');
|
|
192
|
+
if (floating)
|
|
193
|
+
return blocked(`${floating} as the mail gitRef`);
|
|
194
|
+
if (!org.domain)
|
|
195
|
+
return blocked('the org declares no domain');
|
|
196
|
+
if (!ownAuthApplied(org, recordedIds)) {
|
|
197
|
+
return blocked('its own sign-in is not applied yet');
|
|
198
|
+
}
|
|
199
|
+
if (!mailRelayApplied || !readiness.platformApex) {
|
|
200
|
+
return blocked('the shared mail relay is not applied yet');
|
|
201
|
+
}
|
|
202
|
+
if (!zoneActive)
|
|
203
|
+
return blocked(`the zone for ${org.domain} is not active yet`);
|
|
204
|
+
const platformApex = readiness.platformApex;
|
|
205
|
+
const hostname = `mail.${org.domain}`;
|
|
206
|
+
if (recordedIds[`coolify:own-mail:${org.id}`] &&
|
|
207
|
+
recordedIds[PLATFORM_MAIL_APPLIED_HOSTNAME_SCOPE_KEY] === hostname &&
|
|
208
|
+
recordedIds[PLATFORM_MAIL_APPLIED_GIT_REF_SCOPE_KEY] === gitRef &&
|
|
209
|
+
recordedIds[PLATFORM_MAIL_INTERNAL_URL_SCOPE_KEY] &&
|
|
210
|
+
recordedIds[OWN_MAIL_SMTP_INTERNAL_SCOPE_KEY]) {
|
|
211
|
+
return { step: null, blocked: null };
|
|
212
|
+
}
|
|
213
|
+
return {
|
|
214
|
+
blocked: null,
|
|
215
|
+
step: {
|
|
216
|
+
action: {
|
|
217
|
+
system: 'coolify',
|
|
218
|
+
kind: 'own-mail',
|
|
219
|
+
key: org.id,
|
|
220
|
+
desired: { hostname, gitRef, mailHost: platformMailMxHost(platformApex) },
|
|
221
|
+
},
|
|
222
|
+
step: {
|
|
223
|
+
description: `provision ${org.slug}'s own mail server`,
|
|
224
|
+
destroys: false,
|
|
225
|
+
migrates: false,
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
/** `unmanaged` key of the one blocked line `planOwnSfu` reports. */
|
|
231
|
+
export const OWN_SFU_BLOCKED_KEY = 'own-sfu';
|
|
232
|
+
/** `unmanaged` key of the firewall line: the server's ports foundry cannot open itself. */
|
|
233
|
+
export const OWN_SFU_FIREWALL_KEY = 'own-sfu:firewall';
|
|
234
|
+
/**
|
|
235
|
+
* An own org (`services.own`) with calls on runs its own LiveKit media server
|
|
236
|
+
* as a Coolify app. Applied once, and again when the pinned LiveKit version,
|
|
237
|
+
* the slot or the hostname changed, or an earlier run died before the deploy
|
|
238
|
+
* finished. `firewall` is the one line that says which ports the server must
|
|
239
|
+
* have open: foundry opens none, so it stays until the host confirms them.
|
|
240
|
+
* Every line is fixed text: it is part of the stale-plan comparison.
|
|
241
|
+
*/
|
|
242
|
+
export function planOwnSfu(options) {
|
|
243
|
+
const { org, recordedIds, isPlatformOrg, ownServices, zoneActive, sfuPortsOpen, readiness } = options;
|
|
244
|
+
const none = { step: null, blocked: null, firewall: null };
|
|
245
|
+
// The platform's own org is reported once, by `planOwnJobs`.
|
|
246
|
+
if (!ownsServices(org) || !org.calls.enabled || isPlatformOrg || !org.domain)
|
|
247
|
+
return none;
|
|
248
|
+
const blocked = (line) => ({ ...none, blocked: line });
|
|
249
|
+
const slot = org.services?.sfuSlot;
|
|
250
|
+
if (typeof slot !== 'number' || !Number.isInteger(slot) || slot < 1 || slot > SFU_SLOT_MAX) {
|
|
251
|
+
return blocked(`${org.slug}'s own media server is blocked, not planned: no sfuSlot is declared`);
|
|
252
|
+
}
|
|
253
|
+
const ports = sfuSlotPorts(slot);
|
|
254
|
+
const firewall = sfuPortsOpen
|
|
255
|
+
? null
|
|
256
|
+
: `${org.slug}'s own media server needs UDP ${ports.udpStart}-${ports.udpEnd} and TCP ${ports.tcp} open on the Coolify server's firewall; foundry does not open them`;
|
|
257
|
+
const result = (step, line = null) => ({
|
|
258
|
+
step,
|
|
259
|
+
blocked: line,
|
|
260
|
+
firewall,
|
|
261
|
+
});
|
|
262
|
+
const undelivered = recordedIds[OWN_SFU_KEYS_UNDELIVERED_SCOPE_KEY];
|
|
263
|
+
if (undelivered && !options.keysHeld) {
|
|
264
|
+
return result(null, `${org.slug}'s own media server LiveKit key for app ${undelivered} was minted but not confirmed delivered; remove the recorded key ${OWN_SFU_KEYS_UNDELIVERED_SCOPE_KEY} to mint a new one`);
|
|
265
|
+
}
|
|
266
|
+
if (!ownServices) {
|
|
267
|
+
return result(null, `${org.slug}'s own media server is blocked, not planned: the host supplies no ownServices port`);
|
|
268
|
+
}
|
|
269
|
+
const waiting = (reason) => result(null, `${org.slug}'s own media server is blocked, not planned: ${reason}`);
|
|
270
|
+
if (!ownAuthApplied(org, recordedIds))
|
|
271
|
+
return waiting('its own sign-in is not applied yet');
|
|
272
|
+
if (!zoneActive)
|
|
273
|
+
return waiting(`the zone for ${org.domain} is not active yet`);
|
|
274
|
+
const ownEdge = readiness.ownEdgePorts;
|
|
275
|
+
if (readiness.dnsBlocked && !(ownEdge?.dns && ownEdge.tunnels)) {
|
|
276
|
+
return waiting(readiness.dnsBlocked);
|
|
277
|
+
}
|
|
278
|
+
if (!ownEdge?.tunnels && !readiness.edgeReady) {
|
|
279
|
+
return waiting('the host supplies no tunnel port and edge is not ready');
|
|
280
|
+
}
|
|
281
|
+
const hostname = `sfu.${org.domain}`;
|
|
282
|
+
const version = VERSIONS.livekit;
|
|
283
|
+
const applied = Boolean(recordedIds[`coolify:own-sfu:${org.id}`]) &&
|
|
284
|
+
recordedIds[OWN_SFU_APPLIED_VERSION_SCOPE_KEY] === version &&
|
|
285
|
+
recordedIds[OWN_SFU_APPLIED_SLOT_SCOPE_KEY] === String(slot) &&
|
|
286
|
+
recordedIds[OWN_SFU_APPLIED_HOSTNAME_SCOPE_KEY] === hostname &&
|
|
287
|
+
!recordedIds[OWN_SFU_DEPLOY_PENDING_SCOPE_KEY] &&
|
|
288
|
+
Boolean(recordedIds[SFU_INTERNAL_URL_SCOPE_KEY]) &&
|
|
289
|
+
Boolean(recordedIds[SFU_PUBLIC_URL_SCOPE_KEY]);
|
|
290
|
+
if (applied)
|
|
291
|
+
return result(null);
|
|
292
|
+
return result({
|
|
293
|
+
action: {
|
|
294
|
+
system: 'coolify',
|
|
295
|
+
kind: 'own-sfu',
|
|
296
|
+
key: org.id,
|
|
297
|
+
desired: { slot, hostname, version },
|
|
298
|
+
},
|
|
299
|
+
step: {
|
|
300
|
+
description: `provision ${org.slug}'s own media server`,
|
|
301
|
+
destroys: false,
|
|
302
|
+
migrates: false,
|
|
303
|
+
},
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
/** `unmanaged` key of the one line `planOwnFiles` reports. */
|
|
307
|
+
export const OWN_FILES_BLOCKED_KEY = 'own-files';
|
|
308
|
+
/**
|
|
309
|
+
* An own org (`services.own`) runs its own files host: a database, a migrate
|
|
310
|
+
* app, a runner app, a ZITADEL project in the company's own instance and a
|
|
311
|
+
* bucket. Applied once, and again when the declared domain, the platform's
|
|
312
|
+
* files ref or the org's share domains changed or an earlier run died before
|
|
313
|
+
* it finished. A secret minted but not confirmed delivered never plans the
|
|
314
|
+
* step again. Every blocked line is fixed text: it is part of the stale-plan
|
|
315
|
+
* comparison.
|
|
316
|
+
*/
|
|
317
|
+
export function planOwnFiles(options) {
|
|
318
|
+
const { org, recordedIds, readiness, isPlatformOrg, ownServices, filesDeps } = options;
|
|
319
|
+
// `planOwnJobs` reports the platform's own org once.
|
|
320
|
+
if (!ownsServices(org) || isPlatformOrg || !org.domain)
|
|
321
|
+
return { step: null, blocked: null };
|
|
322
|
+
const blocked = (reason) => ({
|
|
323
|
+
step: null,
|
|
324
|
+
blocked: `${org.slug}'s own files host is blocked, not planned: ${reason}`,
|
|
325
|
+
});
|
|
326
|
+
const managementLost = recordedIds[PLATFORM_FILES_MANAGEMENT_UNDELIVERED_SCOPE_KEY];
|
|
327
|
+
if (managementLost && !recordedIds[PLATFORM_FILES_MANAGEMENT_KEY_SCOPE_KEY]) {
|
|
328
|
+
return blocked(`the files management key ${managementLost} was minted but not confirmed delivered; remove the recorded key ${PLATFORM_FILES_MANAGEMENT_UNDELIVERED_SCOPE_KEY} to mint a new one`);
|
|
329
|
+
}
|
|
330
|
+
const introspectionLost = recordedIds[PLATFORM_FILES_INTROSPECTION_UNDELIVERED_SCOPE_KEY];
|
|
331
|
+
if (introspectionLost && !recordedIds[PLATFORM_FILES_INTROSPECTION_SCOPE_KEY]) {
|
|
332
|
+
return blocked(`the introspection client secret of ZITADEL app ${introspectionLost} was minted but not confirmed delivered; remove the recorded key ${PLATFORM_FILES_INTROSPECTION_UNDELIVERED_SCOPE_KEY} to mint a new one`);
|
|
333
|
+
}
|
|
334
|
+
if (!ownServices)
|
|
335
|
+
return blocked('the host supplies no ownServices port');
|
|
336
|
+
const gitRef = readiness.serviceGitRefs?.files;
|
|
337
|
+
if (!gitRef)
|
|
338
|
+
return blocked('the platform declares no files git ref');
|
|
339
|
+
if (!ownAuthApplied(org, recordedIds))
|
|
340
|
+
return blocked('its own sign-in service is not applied yet');
|
|
341
|
+
if (!recordedIds[ZITADEL_ORG_SCOPE_KEY])
|
|
342
|
+
return blocked('its ZITADEL organisation is not recorded yet');
|
|
343
|
+
if (!filesDeps.keys) {
|
|
344
|
+
return blocked('the host supplies no keys mint port (keysMint) for the files host keyring');
|
|
345
|
+
}
|
|
346
|
+
if (!filesDeps.bucket)
|
|
347
|
+
return blocked('the host supplies no bucket port');
|
|
348
|
+
const hostname = `files.${org.domain}`;
|
|
349
|
+
const applied = Boolean(recordedIds[`coolify:own-files:${org.id}`]);
|
|
350
|
+
if (applied &&
|
|
351
|
+
recordedIds[PLATFORM_FILES_APPLIED_HOSTNAME_SCOPE_KEY] === hostname &&
|
|
352
|
+
recordedIds[PLATFORM_FILES_APPLIED_GIT_REF_SCOPE_KEY] === gitRef &&
|
|
353
|
+
(recordedIds[PLATFORM_FILES_SHARE_DOMAINS_SCOPE_KEY] ?? '') ===
|
|
354
|
+
filesShareDomains([org]).join(',') &&
|
|
355
|
+
recordedIds[PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY] &&
|
|
356
|
+
recordedIds[OWN_FILES_RUNNER_SCOPE_KEY] &&
|
|
357
|
+
recordedIds[ZITADEL_FILES_PROJECT_SCOPE_KEY] &&
|
|
358
|
+
recordedIds[PLATFORM_FILES_INTROSPECTION_SCOPE_KEY] &&
|
|
359
|
+
recordedIds[PLATFORM_FILES_MANAGEMENT_KEY_SCOPE_KEY]) {
|
|
360
|
+
return { step: null, blocked: null };
|
|
361
|
+
}
|
|
362
|
+
// The platform administers a company's files host: its operator is the platform's owner, never the company's.
|
|
363
|
+
const platformOwner = readiness.platformOwnerUserId;
|
|
364
|
+
if (!platformOwner) {
|
|
365
|
+
return blocked("the platform has not recorded its owner's sign-in id yet");
|
|
366
|
+
}
|
|
367
|
+
return {
|
|
368
|
+
blocked: null,
|
|
369
|
+
step: {
|
|
370
|
+
action: {
|
|
371
|
+
system: 'coolify',
|
|
372
|
+
kind: 'own-files',
|
|
373
|
+
key: org.id,
|
|
374
|
+
desired: {
|
|
375
|
+
hostname,
|
|
376
|
+
gitRef,
|
|
377
|
+
...(platformOwner === 'none' ? {} : { bootstrapOperator: platformOwner }),
|
|
378
|
+
},
|
|
379
|
+
},
|
|
380
|
+
step: {
|
|
381
|
+
description: `provision ${org.slug}'s own files host at ${hostname}`,
|
|
382
|
+
destroys: false,
|
|
383
|
+
migrates: false,
|
|
384
|
+
},
|
|
385
|
+
},
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
/** `unmanaged` key of the one line `planOwnFilesR2` reports. */
|
|
389
|
+
export const OWN_FILES_R2_BLOCKED_KEY = 'own-files-r2';
|
|
390
|
+
/**
|
|
391
|
+
* The storage key of an own org's files bucket: planned once its files host is applied (the host must be
|
|
392
|
+
* up to receive the key) and until `files_r2_id` is recorded. A token minted and not confirmed delivered
|
|
393
|
+
* does not block the step: the next run deletes it first. The blocked line is fixed text.
|
|
394
|
+
*/
|
|
395
|
+
export function planOwnFilesR2(options) {
|
|
396
|
+
const { org, recordedIds, isPlatformOrg, ownServices, ports } = options;
|
|
397
|
+
if (!ownsServices(org) || isPlatformOrg || !org.domain || !ownServices) {
|
|
398
|
+
return { step: null, blocked: null };
|
|
399
|
+
}
|
|
400
|
+
if (!recordedIds[`coolify:own-files:${org.id}`])
|
|
401
|
+
return { step: null, blocked: null };
|
|
402
|
+
if (recordedIds[OWN_FILES_R2_SCOPE_KEY])
|
|
403
|
+
return { step: null, blocked: null };
|
|
404
|
+
const missing = [
|
|
405
|
+
...(ports.r2Keys ? [] : ['R2 key port (r2Keys)']),
|
|
406
|
+
...(ports.storeR2Credentials
|
|
407
|
+
? []
|
|
408
|
+
: ["storeR2Credentials on the company's files-organisation port"]),
|
|
409
|
+
];
|
|
410
|
+
if (missing.length > 0) {
|
|
411
|
+
return {
|
|
412
|
+
step: null,
|
|
413
|
+
blocked: `${org.slug}'s own files storage key is blocked, not planned: the host supplies no ${missing.join(' and no ')}`,
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
return {
|
|
417
|
+
blocked: null,
|
|
418
|
+
step: {
|
|
419
|
+
action: {
|
|
420
|
+
system: 'coolify',
|
|
421
|
+
kind: 'own-files-r2',
|
|
422
|
+
key: org.id,
|
|
423
|
+
desired: { bucket: orgTarget(org).names.filesBucket },
|
|
424
|
+
},
|
|
425
|
+
step: {
|
|
426
|
+
description: `make and deliver ${org.slug}'s storage key for bucket ${orgTarget(org).names.filesBucket}`,
|
|
427
|
+
destroys: false,
|
|
428
|
+
migrates: false,
|
|
429
|
+
},
|
|
430
|
+
},
|
|
431
|
+
};
|
|
432
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { StepScope } from './own-services.js';
|
|
2
|
+
/** Comma-separated hostnames of the tunnel CNAMEs an own org's service steps wrote. */
|
|
3
|
+
export declare const OWN_SERVICES_DNS_HOSTNAMES_SCOPE_KEY = "own_services_dns_hostnames";
|
|
4
|
+
/** Records a hostname once, and writes the record out now: called after the DNS write, so a crash between the two is repaired by the rerun. */
|
|
5
|
+
export declare function recordOwnDnsHostname(scope: StepScope, hostname: string): Promise<void>;
|
|
6
|
+
export declare function ownDnsHostnames(recordedIds: Readonly<Record<string, string>>): string[];
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Comma-separated hostnames of the tunnel CNAMEs an own org's service steps wrote. */
|
|
2
|
+
export const OWN_SERVICES_DNS_HOSTNAMES_SCOPE_KEY = 'own_services_dns_hostnames';
|
|
3
|
+
/** Records a hostname once, and writes the record out now: called after the DNS write, so a crash between the two is repaired by the rerun. */
|
|
4
|
+
export async function recordOwnDnsHostname(scope, hostname) {
|
|
5
|
+
const known = (scope.recordedIds()[OWN_SERVICES_DNS_HOSTNAMES_SCOPE_KEY] ?? '')
|
|
6
|
+
.split(',')
|
|
7
|
+
.filter(Boolean);
|
|
8
|
+
if (known.includes(hostname))
|
|
9
|
+
return;
|
|
10
|
+
scope.recordScopeId(OWN_SERVICES_DNS_HOSTNAMES_SCOPE_KEY, [...known, hostname].join(','));
|
|
11
|
+
await scope.persist();
|
|
12
|
+
}
|
|
13
|
+
export function ownDnsHostnames(recordedIds) {
|
|
14
|
+
return (recordedIds[OWN_SERVICES_DNS_HOSTNAMES_SCOPE_KEY] ?? '').toLowerCase().split(',');
|
|
15
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { CoolifyPort } from './executor.js';
|
|
2
|
+
/**
|
|
3
|
+
* The hard limits every container of a company that runs its own services
|
|
4
|
+
* gets: one flat default. 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
|
+
/** The same bound the neighbouring Coolify calls in this package use for a call that may hang. */
|
|
12
|
+
export declare const OWN_LIMITS_TIMEOUT_MS = 60000;
|
|
13
|
+
type LimitsPort = Partial<Pick<CoolifyPort, 'createGithubAppApplication' | 'createDockerImageApplication' | 'createDockerfileApplication' | 'createPostgresDatabase' | 'findApplicationByName' | 'getApplication' | 'getDatabase' | 'updateApplication' | 'raw'>>;
|
|
14
|
+
/**
|
|
15
|
+
* The Coolify port an own-services step uses, with the limits set on every
|
|
16
|
+
* application and database it creates, and set again on one it finds without
|
|
17
|
+
* them, so a company provisioned before the limits existed converges on its
|
|
18
|
+
* next apply. A failed limits call fails the calling step before it records
|
|
19
|
+
* its applied marker; the next run finds the resource and sets them.
|
|
20
|
+
*/
|
|
21
|
+
export declare function limited<C extends LimitsPort>(coolify: C): C;
|
|
22
|
+
export {};
|