@wtfalch/foundry 0.37.0 → 0.39.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/versions.d.ts +3 -2
- package/dist/index.d.ts +1 -1
- package/dist/plan/backup-gate.d.ts +19 -3
- package/dist/plan/backup-gate.js +49 -4
- package/dist/plan/company-backups.d.ts +7 -2
- package/dist/plan/company-backups.js +25 -6
- package/dist/plan/diff.d.ts +4 -0
- package/dist/plan/diff.js +19 -1
- package/dist/plan/executor.d.ts +13 -0
- package/dist/plan/executor.js +56 -13
- package/dist/plan/index.d.ts +2 -1
- package/dist/plan/index.js +1 -0
- package/dist/plan/mail-store.d.ts +34 -0
- package/dist/plan/mail-store.js +49 -0
- package/dist/plan/own-diff.d.ts +26 -0
- package/dist/plan/own-diff.js +74 -6
- package/dist/plan/own-services.d.ts +11 -0
- package/dist/plan/own-services.js +12 -0
- package/dist/plan/plan-apply.d.ts +4 -0
- package/dist/plan/plan-apply.js +30 -4
- package/dist/plan/platform-diff.d.ts +30 -0
- package/dist/plan/platform-diff.js +62 -7
- package/dist/plan/platform-org.d.ts +15 -0
- package/dist/plan/platform-org.js +17 -1
- package/dist/plan/platform-providers.d.ts +69 -2
- package/dist/plan/platform-providers.js +255 -41
- package/dist/plan/signin-db-backup.d.ts +105 -0
- package/dist/plan/signin-db-backup.js +193 -0
- package/dist/plan/signin-db-move.d.ts +68 -0
- package/dist/plan/signin-db-move.js +338 -0
- package/dist/plan/signin-db-tls.d.ts +69 -0
- package/dist/plan/signin-db-tls.js +255 -0
- package/dist/plan/signin-db.d.ts +114 -0
- package/dist/plan/signin-db.js +255 -0
- package/dist/plan/uptime-worker.d.ts +20 -1
- package/dist/plan/uptime-worker.js +121 -3
- package/package.json +1 -1
- package/versions.digests.json +2 -1
- package/versions.json +2 -1
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
* The estate's pinned third-party image/release refs for the platform
|
|
3
3
|
* services foundry provisions (#247). One JSON file, so Renovate can read
|
|
4
4
|
* and later propose bumps against it, instead of four separate string
|
|
5
|
-
* constants it cannot see.
|
|
6
|
-
* pins a postgres image tag today, so there is no ref to record yet.
|
|
5
|
+
* constants it cannot see.
|
|
7
6
|
*/
|
|
8
7
|
export interface PinnedVersions {
|
|
9
8
|
zitadel: string;
|
|
@@ -17,6 +16,8 @@ export interface PinnedVersions {
|
|
|
17
16
|
alpine: string;
|
|
18
17
|
/** A static busybox, copied into the MAS image so its health check has an HTTP client. */
|
|
19
18
|
busybox: string;
|
|
19
|
+
/** The sign-in database's image (#746). Its major version is the data directory's format: a bump past 16 needs a dump and restore, not an image change. */
|
|
20
|
+
postgres: string;
|
|
20
21
|
}
|
|
21
22
|
export declare function loadVersions(): PinnedVersions;
|
|
22
23
|
export declare const VERSIONS: PinnedVersions;
|
package/dist/index.d.ts
CHANGED
|
@@ -54,7 +54,7 @@ export { checkMailIdentityAudience } from './plan/index.js';
|
|
|
54
54
|
export type { MailAllowlistApp } from './plan/index.js';
|
|
55
55
|
export { checkMailAppsAllowlist } from './plan/index.js';
|
|
56
56
|
export { FoundryPlatformStepExecutor, ProvidersPlatformCloudflareReader, ProvidersPlatformCoolifyReader, createZitadelAdminMinter, createZitadelLoginPolicy, createZitadelProjectRoles, PLATFORM_AUTH_KEY, PLATFORM_MAIL_KEY, PLATFORM_MAIL_RELAY_KEY, PLATFORM_MAIL_RELAY_ROUTES_SCOPE_KEY, PLATFORM_MAIL_ADMIN_KEY_DESCRIPTION, } from './plan/index.js';
|
|
57
|
-
export type { PlatformAuthDeps, PlatformAuthSmtp, PlatformAuthRuntime, PlatformFilesDeps, PlatformFilesRuntime, PlatformEdgeDeps, PlatformDnsFactory, PlatformAccessFactory, PlatformUptimeAccessFactory, PlatformUptimeAccessPort, PlatformUptimeDeps, PlatformWorkersFactory, PlatformWorkersPort, PlatformAccessPort, PlatformTunnelsFactory, PlatformEdgeRuntime, } from './plan/index.js';
|
|
57
|
+
export type { PlatformAuthDeps, PlatformAuthSmtp, PlatformAuthRuntime, PlatformFilesDeps, PlatformFilesRuntime, PlatformEdgeDeps, PlatformDnsFactory, PlatformAccessFactory, PlatformUptimeAccessFactory, PlatformUptimeAccessPort, PlatformUptimeChatDeps, PlatformUptimeDeps, PlatformWorkersFactory, PlatformWorkersPort, PlatformAccessPort, PlatformTunnelsFactory, PlatformEdgeRuntime, } from './plan/index.js';
|
|
58
58
|
export type { PlatformMailDeps, PlatformMailAdminPort, PlatformMailRuntime, } from './plan/index.js';
|
|
59
59
|
export { FILES_INTROSPECTION_SECRET_REF, FILES_MANAGEMENT_ISSUER, FILES_MANAGEMENT_KEY_ID, FILES_MANAGEMENT_KEY_REF, PLATFORM_AUTH_INSTANCE_BEARER_REF, } from './plan/index.js';
|
|
60
60
|
export { AI_MANAGEMENT_ISSUER, AI_MANAGEMENT_KEY_ID, AI_SIGNING_KEY_REF, AiManagementKeyExistsError, createAiManagementKeyMinter, PLATFORM_AI_MANAGEMENT_KEY_REF, PLATFORM_AI_MANAGEMENT_PERMISSIONS, } from './plan/index.js';
|
|
@@ -28,6 +28,15 @@ export declare class NetworkMoveBackupMissingError extends Error {
|
|
|
28
28
|
readonly reason: 'no-gate' | 'no-backup' | 'by-hand';
|
|
29
29
|
constructor(reason: 'no-gate' | 'no-backup' | 'by-hand');
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* The move of a company's sign-in database to the TLS image (#746) is refused before any step runs when
|
|
33
|
+
* the old database has no finished backup from the last {@link BACKUP_MAX_AGE_HOURS} hours, or the host
|
|
34
|
+
* has no backup gate. The message names no database, uuid or URL. There is no override.
|
|
35
|
+
*/
|
|
36
|
+
export declare class SigninDbMoveBackupMissingError extends Error {
|
|
37
|
+
readonly reason: 'no-gate' | 'no-backup';
|
|
38
|
+
constructor(reason: 'no-gate' | 'no-backup');
|
|
39
|
+
}
|
|
31
40
|
export interface BackupGatePorts {
|
|
32
41
|
/** The company's recorded ids (`AppliedState.serviceIds`): database uuids and `backup_<db>` schedule uuids. */
|
|
33
42
|
recordedIds: Readonly<Record<string, string>>;
|
|
@@ -52,8 +61,8 @@ export interface BackupGatePorts {
|
|
|
52
61
|
* `unconfirmedUpgrades` and before any step. A service whose database the
|
|
53
62
|
* company does not have passes (there is nothing to migrate); a database that
|
|
54
63
|
* exists with no recorded schedule, a disabled or non-S3 schedule, a failed or
|
|
55
|
-
* old dump, or an unreadable list all refuse. `mail`
|
|
56
|
-
*
|
|
64
|
+
* old dump, or an unreadable list all refuse. `mail` is covered like the rest:
|
|
65
|
+
* Stalwart's data store is a Postgres database.
|
|
57
66
|
*
|
|
58
67
|
* Where this Coolify has no executions route, the fallback is that the
|
|
59
68
|
* schedule exists, is enabled and saves to S3; that proves a dump is
|
|
@@ -67,6 +76,13 @@ export declare function requireFreshBackupsForUpgrades(plan: {
|
|
|
67
76
|
}, confirmed: readonly string[] | undefined, ports: BackupGatePorts): Promise<{
|
|
68
77
|
backupId: string | null;
|
|
69
78
|
}>;
|
|
79
|
+
/**
|
|
80
|
+
* The sign-in database move's gate (#746): the company's recorded `auth` database needs a schedule and a
|
|
81
|
+
* `success` execution not older than {@link BACKUP_MAX_AGE_HOURS} hours, read from Coolify's executions.
|
|
82
|
+
* A schedule alone proves no dump ran, so a Coolify with no executions route refuses too. Returns the
|
|
83
|
+
* uuid of the execution that passed.
|
|
84
|
+
*/
|
|
85
|
+
export declare function requireFreshBackupForSigninMove(ports: BackupGatePorts): Promise<string>;
|
|
70
86
|
export interface MoveGatePorts {
|
|
71
87
|
/** The company's Coolify project (`coolify_project`). */
|
|
72
88
|
projectUuid: string | undefined;
|
|
@@ -88,6 +104,6 @@ export declare function requireFreshBackupsForMove(ports: MoveGatePorts): Promis
|
|
|
88
104
|
/**
|
|
89
105
|
* The platform databases an upgrade of `service` can migrate. Postgres is all three. archon, ai and
|
|
90
106
|
* integrations each migrate their own database when the new container starts. The owner company runs its own
|
|
91
|
-
* sign-in, files and jobs, so the platform has no database for them;
|
|
107
|
+
* sign-in, files and jobs, so the platform has no database for them; edge and hc have no Postgres.
|
|
92
108
|
*/
|
|
93
109
|
export declare function platformDatabasesForService(service: string): readonly string[] | 'all';
|
package/dist/plan/backup-gate.js
CHANGED
|
@@ -44,6 +44,21 @@ export class NetworkMoveBackupMissingError extends Error {
|
|
|
44
44
|
this.name = 'NetworkMoveBackupMissingError';
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* The move of a company's sign-in database to the TLS image (#746) is refused before any step runs when
|
|
49
|
+
* the old database has no finished backup from the last {@link BACKUP_MAX_AGE_HOURS} hours, or the host
|
|
50
|
+
* has no backup gate. The message names no database, uuid or URL. There is no override.
|
|
51
|
+
*/
|
|
52
|
+
export class SigninDbMoveBackupMissingError extends Error {
|
|
53
|
+
reason;
|
|
54
|
+
constructor(reason) {
|
|
55
|
+
super(reason === 'no-gate'
|
|
56
|
+
? 'sign-in database move refused: this host has no backup gate, so a fresh backup cannot be checked'
|
|
57
|
+
: `sign-in database move refused: the old sign-in database has no finished backup from the last ${BACKUP_MAX_AGE_HOURS} hours, checked in Coolify; take one now and apply again`);
|
|
58
|
+
this.reason = reason;
|
|
59
|
+
this.name = 'SigninDbMoveBackupMissingError';
|
|
60
|
+
}
|
|
61
|
+
}
|
|
47
62
|
async function withinTimeout(call) {
|
|
48
63
|
let timer;
|
|
49
64
|
try {
|
|
@@ -123,8 +138,8 @@ function freshSuccess(executions, now, maxAgeMs = BACKUP_MAX_AGE_HOURS * 3_600_0
|
|
|
123
138
|
* `unconfirmedUpgrades` and before any step. A service whose database the
|
|
124
139
|
* company does not have passes (there is nothing to migrate); a database that
|
|
125
140
|
* exists with no recorded schedule, a disabled or non-S3 schedule, a failed or
|
|
126
|
-
* old dump, or an unreadable list all refuse. `mail`
|
|
127
|
-
*
|
|
141
|
+
* old dump, or an unreadable list all refuse. `mail` is covered like the rest:
|
|
142
|
+
* Stalwart's data store is a Postgres database.
|
|
128
143
|
*
|
|
129
144
|
* Where this Coolify has no executions route, the fallback is that the
|
|
130
145
|
* schedule exists, is enabled and saves to S3; that proves a dump is
|
|
@@ -184,6 +199,31 @@ export async function requireFreshBackupsForUpgrades(plan, confirmed, ports) {
|
|
|
184
199
|
}
|
|
185
200
|
return { backupId };
|
|
186
201
|
}
|
|
202
|
+
/**
|
|
203
|
+
* The sign-in database move's gate (#746): the company's recorded `auth` database needs a schedule and a
|
|
204
|
+
* `success` execution not older than {@link BACKUP_MAX_AGE_HOURS} hours, read from Coolify's executions.
|
|
205
|
+
* A schedule alone proves no dump ran, so a Coolify with no executions route refuses too. Returns the
|
|
206
|
+
* uuid of the execution that passed.
|
|
207
|
+
*/
|
|
208
|
+
export async function requireFreshBackupForSigninMove(ports) {
|
|
209
|
+
const now = (ports.now ?? Date.now)();
|
|
210
|
+
const databaseUuid = ports.recordedIds[companyDatabaseKeys({ id: ports.orgId }).auth];
|
|
211
|
+
const backupUuid = ports.recordedIds[backupScopeKey('auth')];
|
|
212
|
+
if (!databaseUuid || !backupUuid)
|
|
213
|
+
throw new SigninDbMoveBackupMissingError('no-backup');
|
|
214
|
+
let executions;
|
|
215
|
+
try {
|
|
216
|
+
executions = await readExecutions(ports.coolify, databaseUuid, backupUuid);
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
// An unreadable list is not a fresh backup. The cause stays out: it can carry a response body.
|
|
220
|
+
throw new SigninDbMoveBackupMissingError('no-backup');
|
|
221
|
+
}
|
|
222
|
+
const fresh = executions ? freshSuccess(executions, now) : null;
|
|
223
|
+
if (!fresh)
|
|
224
|
+
throw new SigninDbMoveBackupMissingError('no-backup');
|
|
225
|
+
return fresh.uuid;
|
|
226
|
+
}
|
|
187
227
|
/**
|
|
188
228
|
* The gate for a move to the company's own network, which deletes every database of the project. It
|
|
189
229
|
* fails closed and is stricter than the upgrade gate: it reads the project itself, not foundry's records.
|
|
@@ -252,10 +292,15 @@ export async function requireFreshBackupsForMove(ports) {
|
|
|
252
292
|
/**
|
|
253
293
|
* The platform databases an upgrade of `service` can migrate. Postgres is all three. archon, ai and
|
|
254
294
|
* integrations each migrate their own database when the new container starts. The owner company runs its own
|
|
255
|
-
* sign-in, files and jobs, so the platform has no database for them;
|
|
295
|
+
* sign-in, files and jobs, so the platform has no database for them; edge and hc have no Postgres.
|
|
256
296
|
*/
|
|
257
297
|
export function platformDatabasesForService(service) {
|
|
258
298
|
if (service === 'postgres')
|
|
259
299
|
return 'all';
|
|
260
|
-
return service === 'archon' ||
|
|
300
|
+
return service === 'archon' ||
|
|
301
|
+
service === 'ai' ||
|
|
302
|
+
service === 'integrations' ||
|
|
303
|
+
service === 'mail'
|
|
304
|
+
? [service]
|
|
305
|
+
: [];
|
|
261
306
|
}
|
|
@@ -11,6 +11,11 @@ import type { DraftStep } from './diff.js';
|
|
|
11
11
|
* `app` is the hosting entry's database, recorded as a resource id.
|
|
12
12
|
*/
|
|
13
13
|
export declare function companyDatabaseKeys(org: Pick<DeclaredOrg, 'id'>): Readonly<Record<string, string>>;
|
|
14
|
+
/**
|
|
15
|
+
* The database a company backup schedule must name when the Coolify resource
|
|
16
|
+
* is not left to pick its first database. Absent: the resource's first database.
|
|
17
|
+
*/
|
|
18
|
+
export declare const COMPANY_BACKUP_DATABASE_NAMES: Readonly<Record<string, string>>;
|
|
14
19
|
/** What every recorded-id key of a company database's backup schedule starts with. */
|
|
15
20
|
export declare const BACKUP_SCOPE_PREFIX = "backup_";
|
|
16
21
|
/** The recorded-id key a company database's backup schedule uuid is stored under. */
|
|
@@ -22,8 +27,8 @@ export declare function databasesForService(service: string): readonly string[]
|
|
|
22
27
|
* recorded uuid and no recorded backup yet. `storageUuid` undefined means the
|
|
23
28
|
* host did not wire backups, so nothing is planned and nothing is said; null
|
|
24
29
|
* means the platform's backup storage is not registered, so each database is
|
|
25
|
-
* reported as blocked instead (rule 2). A company
|
|
26
|
-
*
|
|
30
|
+
* reported as blocked instead (rule 2). A company's mail server is one of
|
|
31
|
+
* these databases (Stalwart's data store), dumped like the rest.
|
|
27
32
|
*/
|
|
28
33
|
export declare function planCompanyBackups(org: DeclaredOrg, recordedIds: Readonly<Record<string, string>>, storageUuid: string | null | undefined): {
|
|
29
34
|
steps: DraftStep[];
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { ownsServices } from '../org.js';
|
|
2
1
|
import { BOULE_DATABASE_SCOPE_KEY } from './boule.js';
|
|
3
2
|
import { CHAT_REKEY_MAS_DB_SCOPE_KEY, CHAT_REKEY_SYNAPSE_DB_SCOPE_KEY } from './executor.js';
|
|
3
|
+
import { MAIL_DATABASE_NAME, MAIL_DATABASE_SCOPE_KEY } from './mail-store.js';
|
|
4
|
+
import { signinDbBackupMissing } from './signin-db-backup.js';
|
|
5
|
+
import { SIGNIN_DB_TLS_SCOPE_KEY } from './signin-db-tls.js';
|
|
4
6
|
/**
|
|
5
7
|
* F6: the Postgres databases of a company that get a nightly backup, and the
|
|
6
8
|
* recorded id each one's uuid sits under. The keys are second, documented
|
|
@@ -14,6 +16,7 @@ export function companyDatabaseKeys(org) {
|
|
|
14
16
|
return {
|
|
15
17
|
app: `coolify:database:${org.id}`,
|
|
16
18
|
cms: 'coolify_cms_database',
|
|
19
|
+
mail: MAIL_DATABASE_SCOPE_KEY,
|
|
17
20
|
auth: 'coolify_platform_auth_database',
|
|
18
21
|
files: 'coolify_platform_files_database',
|
|
19
22
|
jobs: 'coolify_jobs_database',
|
|
@@ -28,6 +31,13 @@ export function companyDatabaseKeys(org) {
|
|
|
28
31
|
chat_rekey_mas: CHAT_REKEY_MAS_DB_SCOPE_KEY,
|
|
29
32
|
};
|
|
30
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* The database a company backup schedule must name when the Coolify resource
|
|
36
|
+
* is not left to pick its first database. Absent: the resource's first database.
|
|
37
|
+
*/
|
|
38
|
+
export const COMPANY_BACKUP_DATABASE_NAMES = {
|
|
39
|
+
mail: MAIL_DATABASE_NAME,
|
|
40
|
+
};
|
|
31
41
|
/** What every recorded-id key of a company database's backup schedule starts with. */
|
|
32
42
|
export const BACKUP_SCOPE_PREFIX = 'backup_';
|
|
33
43
|
/** The recorded-id key a company database's backup schedule uuid is stored under. */
|
|
@@ -43,6 +53,8 @@ export function databasesForService(service) {
|
|
|
43
53
|
return ['files'];
|
|
44
54
|
case 'jobs':
|
|
45
55
|
return ['jobs'];
|
|
56
|
+
case 'mail':
|
|
57
|
+
return ['mail'];
|
|
46
58
|
case 'synapse':
|
|
47
59
|
return ['synapse', 'chat_synapse'];
|
|
48
60
|
case 'mas':
|
|
@@ -58,8 +70,8 @@ export function databasesForService(service) {
|
|
|
58
70
|
* recorded uuid and no recorded backup yet. `storageUuid` undefined means the
|
|
59
71
|
* host did not wire backups, so nothing is planned and nothing is said; null
|
|
60
72
|
* means the platform's backup storage is not registered, so each database is
|
|
61
|
-
* reported as blocked instead (rule 2). A company
|
|
62
|
-
*
|
|
73
|
+
* reported as blocked instead (rule 2). A company's mail server is one of
|
|
74
|
+
* these databases (Stalwart's data store), dumped like the rest.
|
|
63
75
|
*/
|
|
64
76
|
export function planCompanyBackups(org, recordedIds, storageUuid) {
|
|
65
77
|
const steps = [];
|
|
@@ -91,10 +103,17 @@ export function planCompanyBackups(org, recordedIds, storageUuid) {
|
|
|
91
103
|
},
|
|
92
104
|
});
|
|
93
105
|
}
|
|
94
|
-
|
|
106
|
+
const oldDatabase = recordedIds[companyDatabaseKeys(org).auth];
|
|
107
|
+
if (recordedIds[SIGNIN_DB_TLS_SCOPE_KEY] && oldDatabase) {
|
|
108
|
+
blocked.push({
|
|
109
|
+
description: `${org.slug}'s old Coolify sign-in database (${oldDatabase}) is no longer used; delete it by hand in Coolify when you no longer want it as a fallback, foundry never deletes it`,
|
|
110
|
+
key: 'signin-db-old-database',
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
if (signinDbBackupMissing(recordedIds)) {
|
|
95
114
|
blocked.push({
|
|
96
|
-
description: `${org.slug}'s
|
|
97
|
-
key: 'backup-not-covered:
|
|
115
|
+
description: `${org.slug}'s sign-in database runs as foundry's own image, which Coolify's database backups do not cover: its nightly dump is not scheduled yet, so no backup covers it`,
|
|
116
|
+
key: 'backup-not-covered:signin-db',
|
|
98
117
|
});
|
|
99
118
|
}
|
|
100
119
|
return { steps, blocked };
|
package/dist/plan/diff.d.ts
CHANGED
|
@@ -69,6 +69,10 @@ export interface PlanOrgOptions {
|
|
|
69
69
|
ownMailKeyHeld?: boolean;
|
|
70
70
|
/** The executor can mint an own org's Lettermint key (`FoundryStepExecutor.ownMailKeyMintable`). */
|
|
71
71
|
ownMailKeyMintable?: boolean;
|
|
72
|
+
/** The executor can store the sign-in database's CA and certificate (`FoundryStepExecutor.ownSigninDbKeysPuttable`). Absent: true. */
|
|
73
|
+
ownSigninDbKeysPuttable?: boolean;
|
|
74
|
+
/** The host supplies the backup bucket the sign-in database dumps into (`FoundryStepExecutor.ownSigninDbBackupConfigured`). Absent: true. */
|
|
75
|
+
ownSigninDbBackupConfigured?: boolean;
|
|
72
76
|
/** The own jobs admin token's held-key ref holds a value. */
|
|
73
77
|
ownJobsTokenHeld?: boolean;
|
|
74
78
|
/** The own media server's key pair ref holds a value. */
|
package/dist/plan/diff.js
CHANGED
|
@@ -14,8 +14,9 @@ import { appHostname, tunnelDnsHostnames } from './hostnames.js';
|
|
|
14
14
|
import { lockedHostnames } from './locked-hostnames.js';
|
|
15
15
|
import { LOGIN_BINDING_SCOPE_KEY, agoraIssuerHostFor, companyLoginTunnelHost, loginBindingState, loginHostClientIds, loginHostServesAgora, loginHostnameFor, ownAuthApplied, pendingRemovalHosts, } from './login-host.js';
|
|
16
16
|
import { MAIL_COPY_COMPLETED_SCOPE_KEY, MAIL_COPY_DONE_SCOPE_KEY, parseMailCopyCompleted, } from './mail-copy.js';
|
|
17
|
+
import { mailStoreSwitchChange } from './mail-store.js';
|
|
17
18
|
import { hasAdoptedResources, networkMoveNeeded, planNetworkMove } from './network-move.js';
|
|
18
|
-
import { OWN_AUTH_BLOCKED_KEY, OWN_FILES_BLOCKED_KEY, OWN_FILES_R2_BLOCKED_KEY, OWN_JOBS_BLOCKED_KEY, OWN_MAIL_BLOCKED_KEY, OWN_SFU_BLOCKED_KEY, OWN_SFU_FIREWALL_KEY, planOwnAuth, planOwnFiles, planOwnFilesR2, planOwnJobs, planOwnMail, planOwnSfu, } from './own-diff.js';
|
|
19
|
+
import { OWN_AUTH_BLOCKED_KEY, OWN_FILES_BLOCKED_KEY, OWN_FILES_R2_BLOCKED_KEY, OWN_JOBS_BLOCKED_KEY, OWN_MAIL_BLOCKED_KEY, OWN_SFU_BLOCKED_KEY, OWN_SFU_FIREWALL_KEY, OWN_SIGNIN_DB_MOVE_BLOCKED_KEY, planOwnAuth, planOwnFiles, planOwnFilesR2, planOwnJobs, planOwnMail, planOwnSfu, planSigninDbMove, } from './own-diff.js';
|
|
19
20
|
import { ownDnsHostnames } from './own-dns.js';
|
|
20
21
|
import { ownHostnames } from './own-hostnames.js';
|
|
21
22
|
import { CALLS_MAS_APPLIED_IMAGE_TAG_SCOPE_KEY, CALLS_SYNAPSE_APPLIED_IMAGE_TAG_SCOPE_KEY, ORG_MAIL_APPLIED_APEX_SCOPE_KEY, ORG_MAIL_APPLIED_HOST_SCOPE_KEY, ORG_MAIL_APPLIED_MODE_SCOPE_KEY, ORG_MAIL_APPLIED_USES_DOMAIN_SCOPE_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, appEnvAppliedScopeKey, appFolderDiffers, appSettingsOf, appStepKey, appliedAppDomains, appliedAppSettings, dbEnvAppliedScopeKey, resolveAppEnv, serviceDbEnvAppliedScopeKey, versionNotAppliedLine, zitadelAppIdTokenUserinfoAppliedScopeKey, } from './platform-diff.js';
|
|
@@ -196,9 +197,20 @@ export function computePlanDetail(org, live, recordedIds = {}, agoraHost = NO_AG
|
|
|
196
197
|
oikosZoneReady: options.oikosZoneReady ?? false,
|
|
197
198
|
mailKeyHeld: options.ownMailKeyHeld ?? false,
|
|
198
199
|
mailKeyMintable: options.ownMailKeyMintable ?? false,
|
|
200
|
+
now: (options.now ?? Date.now)(),
|
|
201
|
+
signinDbKeysPuttable: options.ownSigninDbKeysPuttable ?? true,
|
|
202
|
+
signinDbBackupConfigured: options.ownSigninDbBackupConfigured ?? true,
|
|
199
203
|
});
|
|
200
204
|
if (ownAuth.step)
|
|
201
205
|
plannedSteps.push(ownAuth.step);
|
|
206
|
+
const signinDbMove = planSigninDbMove({
|
|
207
|
+
org,
|
|
208
|
+
recordedIds,
|
|
209
|
+
signinDbKeysPuttable: options.ownSigninDbKeysPuttable ?? true,
|
|
210
|
+
signinDbBackupConfigured: options.ownSigninDbBackupConfigured ?? true,
|
|
211
|
+
});
|
|
212
|
+
if (signinDbMove.step)
|
|
213
|
+
plannedSteps.push(signinDbMove.step);
|
|
202
214
|
// An own org's ZITADEL is the company's own instance: nothing that needs it plans before it exists.
|
|
203
215
|
const ownSignInPending = ownsServices(org) && !ownAuthApplied(org, recordedIds);
|
|
204
216
|
const issuer = planIssuer(org, byKey, claimed, platformReadiness.authReady);
|
|
@@ -319,6 +331,9 @@ export function computePlanDetail(org, live, recordedIds = {}, agoraHost = NO_AG
|
|
|
319
331
|
// uses, never `versionChangeLine`'s "X -> Y (redeploy)" -- nothing here
|
|
320
332
|
// ever performs that redeploy.
|
|
321
333
|
const versionChanges = [];
|
|
334
|
+
if (ownMail.step?.action.desired.switchMailStore) {
|
|
335
|
+
versionChanges.push(mailStoreSwitchChange(org.slug));
|
|
336
|
+
}
|
|
322
337
|
if (org.calls.enabled && !calls.blocked) {
|
|
323
338
|
const callsAppLive = Boolean(byKey.get(resourceKey('coolify', 'calls', org.id)));
|
|
324
339
|
const synapseChange = versionNotAppliedLine('synapse', callsAppLive, recordedIds[CALLS_SYNAPSE_APPLIED_IMAGE_TAG_SCOPE_KEY], versionPin?.calls ?? VERSIONS.synapse);
|
|
@@ -606,6 +621,9 @@ export function computePlanDetail(org, live, recordedIds = {}, agoraHost = NO_AG
|
|
|
606
621
|
unmanaged.push(...edgeReservedLabels.blocked);
|
|
607
622
|
if (ownAuth.blocked)
|
|
608
623
|
unmanaged.push({ description: ownAuth.blocked, key: OWN_AUTH_BLOCKED_KEY });
|
|
624
|
+
if (signinDbMove.blocked) {
|
|
625
|
+
unmanaged.push({ description: signinDbMove.blocked, key: OWN_SIGNIN_DB_MOVE_BLOCKED_KEY });
|
|
626
|
+
}
|
|
609
627
|
if (ownJobs.blocked)
|
|
610
628
|
unmanaged.push({ description: ownJobs.blocked, key: OWN_JOBS_BLOCKED_KEY });
|
|
611
629
|
if (ownMail.blocked)
|
package/dist/plan/executor.d.ts
CHANGED
|
@@ -325,6 +325,8 @@ export interface CoolifyPort {
|
|
|
325
325
|
description?: string;
|
|
326
326
|
instant_deploy?: boolean;
|
|
327
327
|
custom_docker_run_options?: string;
|
|
328
|
+
/** `false`: no domain is made for the app. Coolify makes one by default, which would route to the app's port. */
|
|
329
|
+
autogenerate_domain?: boolean;
|
|
328
330
|
}): Promise<{
|
|
329
331
|
uuid: string;
|
|
330
332
|
}>;
|
|
@@ -1828,6 +1830,10 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
|
|
|
1828
1830
|
private executeScoped;
|
|
1829
1831
|
/** Whether `ensureOwnAuthMailToken` can mint: the same two inputs it blocks on. */
|
|
1830
1832
|
ownMailKeyMintable(): boolean;
|
|
1833
|
+
/** Whether the held keys can store a new company's sign-in database CA and certificate (#746). */
|
|
1834
|
+
ownSigninDbKeysPuttable(): boolean;
|
|
1835
|
+
/** Whether the host supplies the backup bucket the sign-in database dumps into (#746). */
|
|
1836
|
+
ownSigninDbBackupConfigured(): boolean;
|
|
1831
1837
|
/** What the executor's own files dependencies supply, for the planner's blocked lines. */
|
|
1832
1838
|
ownFilesDeps(): {
|
|
1833
1839
|
keys: boolean;
|
|
@@ -2927,6 +2933,13 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
|
|
|
2927
2933
|
* declared auth ref patches both git apps' branch and redeploys them; the
|
|
2928
2934
|
* applied marker comes last, so a run that died halfway is planned again.
|
|
2929
2935
|
*/
|
|
2936
|
+
/**
|
|
2937
|
+
* The sign-in ports of one company's steps: its own network and limits, a SMTP target without the
|
|
2938
|
+
* platform's password, and every secret the step reads registered for redaction.
|
|
2939
|
+
*/
|
|
2940
|
+
private ownAuthDeps;
|
|
2941
|
+
/** `plan/signin-db-move.ts`: copies a company's sign-in data from its Coolify database to the TLS database. */
|
|
2942
|
+
private ownAuthDbMove;
|
|
2930
2943
|
private ownAuth;
|
|
2931
2944
|
/**
|
|
2932
2945
|
* `plan/own-diff.ts`'s `planOwnMail`: the org's own Stalwart in the org's own
|
package/dist/plan/executor.js
CHANGED
|
@@ -13,7 +13,7 @@ import { recordBuiltCommit } from './build-record.js';
|
|
|
13
13
|
import { MAS_TAG, SYNAPSE_IMAGE, SYNAPSE_TAG, callsMasConfigYaml, callsServerName, callsSynapseHomeserverYaml, masDockerfile, mintCallsMatrixSecrets, parseCallsDbUrl, } from './calls-matrix.js';
|
|
14
14
|
import { CHAT_MAS_PORT, CHAT_REPO, CHAT_SERVICE_DOCKERFILE, CHAT_SERVICE_PORT, CHAT_SYNAPSE_DOCKERFILE, CHAT_SYNAPSE_PORT, assertChatServerName, chatInternalNames, chatMasConfigYaml, chatRekeyedInternalNames, chatServerName, chatServiceEnvs, chatSynapseHomeserverYaml, chatUpstreams, } from './chat-matrix.js';
|
|
15
15
|
import { ensureCloudflaredConnector } from './cloudflared-connector.js';
|
|
16
|
-
import { BACKUP_SCOPE_PREFIX, backupScopeKey } from './company-backups.js';
|
|
16
|
+
import { BACKUP_SCOPE_PREFIX, COMPANY_BACKUP_DATABASE_NAMES, backupScopeKey, } from './company-backups.js';
|
|
17
17
|
import { CORE_FILES_COLLECTION_NAME, FILES_CORE_COLLECTION_SCOPE_KEY, ZITADEL_FILES_PROJECT_SCOPE_KEY, } from './core-files.js';
|
|
18
18
|
import { waitForNewDeployment, } from './deploy-health.js';
|
|
19
19
|
import { PLATFORM_NETWORK, companyNetworkName, ensureDestination, findDestination, onPlatformNetwork, placed, placedByScope, } from './destinations.js';
|
|
@@ -44,6 +44,7 @@ import { CALLS_MAS_APPLIED_IMAGE_TAG_SCOPE_KEY, CALLS_SYNAPSE_APPLIED_IMAGE_TAG_
|
|
|
44
44
|
import { AUTH_LOGIN_PORT, PLATFORM_AUTH_BROKER_MAIL_KEY_REF, PLATFORM_AUTH_LOGIN_INTERNAL_NAME, PLATFORM_AUTH_LOGIN_SCOPE_KEY, PLATFORM_MAIL_SERVER_SCOPE_KEY, defaultStalwartForInstance, filesDepsInvalid, filesShareDomains, provisionAuthStack, provisionDatabaseBackup, provisionFilesHost, provisionJobsHost, provisionMailServer, resolveOwnFilesKeys, } from './platform-providers.js';
|
|
45
45
|
import { CLOUDFLARE_TUNNEL_ENTRY_ROUTES_SCOPE_KEY, CLOUDFLARE_TUNNEL_OIKOS_HOSTNAMES_SCOPE_KEY, ENTRY_CONTAINER_LABELS, appLabelsAppliedScopeKey, entryLabelHash, entryLabelSpec, entryOikosHostnames, entryProxyLabels, entryRoutes, entryRoutesMarker, ownsOikosName, routesWithoutToken, sameLabelSets, } from './proxy-labels.js';
|
|
46
46
|
import { ZITADEL_ORG_DOMAIN_SCOPE_KEY, ZITADEL_ORG_REGISTRATION_CLOSED_SCOPE_KEY, closeSignInOrgRegistration, ensureSignInOrg, inviteOwnerWithLink, registerSignInDomainOrBlock, signInOrgDomain, } from './sign-in-org.js';
|
|
47
|
+
import { moveSigninDatabase } from './signin-db-move.js';
|
|
47
48
|
import { entryWorkerAppliedScopeKey, entryWorkerExclusionsScopeKey, entryWorkerRetiredScopeKey, entryWorkerRouteIdScopeKey, entryWorkerRoutePatternScopeKey, entryWorkerRoutePendingScopeKey, entryWorkerRouteZoneScopeKey, entryWorkerUploadedScopeKey, } from './worker.js';
|
|
48
49
|
/**
|
|
49
50
|
* The real {@link StepExecutor} (#20 "provision Company Factory end to
|
|
@@ -940,6 +941,15 @@ export class FoundryStepExecutor {
|
|
|
940
941
|
ownMailKeyMintable() {
|
|
941
942
|
return Boolean(this.ports.lettermint && this.ports.ownServices?.provision.auth.heldKeys);
|
|
942
943
|
}
|
|
944
|
+
/** Whether the held keys can store a new company's sign-in database CA and certificate (#746). */
|
|
945
|
+
ownSigninDbKeysPuttable() {
|
|
946
|
+
return Boolean(this.ports.ownServices?.provision.auth.heldKeys?.put);
|
|
947
|
+
}
|
|
948
|
+
/** Whether the host supplies the backup bucket the sign-in database dumps into (#746). */
|
|
949
|
+
ownSigninDbBackupConfigured() {
|
|
950
|
+
const files = this.ports.ownServices?.provision.files;
|
|
951
|
+
return Boolean(files?.r2Buckets && files.r2Keys);
|
|
952
|
+
}
|
|
943
953
|
/** What the executor's own files dependencies supply, for the planner's blocked lines. */
|
|
944
954
|
ownFilesDeps() {
|
|
945
955
|
const files = this.ports.ownServices?.provision.files;
|
|
@@ -1132,6 +1142,8 @@ export class FoundryStepExecutor {
|
|
|
1132
1142
|
return this.jobsRegistration(action, org, context);
|
|
1133
1143
|
case 'coolify:own-auth':
|
|
1134
1144
|
return this.ownAuth(action, org, context);
|
|
1145
|
+
case 'coolify:own-auth-db-move':
|
|
1146
|
+
return this.ownAuthDbMove(org, context);
|
|
1135
1147
|
case 'coolify:own-jobs':
|
|
1136
1148
|
return this.ownJobs(action, org, context);
|
|
1137
1149
|
case 'coolify:own-mail':
|
|
@@ -2633,6 +2645,7 @@ export class FoundryStepExecutor {
|
|
|
2633
2645
|
databaseUuid: databaseId,
|
|
2634
2646
|
label: `${org.slug} ${database}`,
|
|
2635
2647
|
storageUuid,
|
|
2648
|
+
databaseName: COMPANY_BACKUP_DATABASE_NAMES[database],
|
|
2636
2649
|
});
|
|
2637
2650
|
context.recordScopeId(backupScopeKey(database), uuid);
|
|
2638
2651
|
return undefined;
|
|
@@ -5708,30 +5721,55 @@ export class FoundryStepExecutor {
|
|
|
5708
5721
|
* declared auth ref patches both git apps' branch and redeploys them; the
|
|
5709
5722
|
* applied marker comes last, so a run that died halfway is planned again.
|
|
5710
5723
|
*/
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
const gitRef = action.desired.gitRef;
|
|
5717
|
-
const hn = ownHostnames(org);
|
|
5718
|
-
const scope = scopeFromOrg(context, {});
|
|
5724
|
+
/**
|
|
5725
|
+
* The sign-in ports of one company's steps: its own network and limits, a SMTP target without the
|
|
5726
|
+
* platform's password, and every secret the step reads registered for redaction.
|
|
5727
|
+
*/
|
|
5728
|
+
ownAuthDeps(auth, org, context) {
|
|
5719
5729
|
// The target (host, port, tls, user) is not secret; the platform's SMTP
|
|
5720
5730
|
// password is, so the company's own token is the password instead.
|
|
5721
|
-
const { smtp, ...authDeps } =
|
|
5722
|
-
const
|
|
5731
|
+
const { smtp, signinDbBackup: _platformBackup, ...authDeps } = auth;
|
|
5732
|
+
const files = this.ports.ownServices?.provision.files;
|
|
5733
|
+
return {
|
|
5723
5734
|
...authDeps,
|
|
5735
|
+
// The company's own bucket and key, through the files host's ports; never a platform-wide backup key.
|
|
5736
|
+
signinDbBackup: files?.r2Buckets && files.r2Keys
|
|
5737
|
+
? { r2Buckets: files.r2Buckets, r2Keys: files.r2Keys }
|
|
5738
|
+
: undefined,
|
|
5724
5739
|
...(smtp
|
|
5725
5740
|
? { smtp: { host: smtp.host, port: smtp.port, tls: smtp.tls, user: smtp.user } }
|
|
5726
5741
|
: {}),
|
|
5727
|
-
coolify: limited(this.placedForCompany(
|
|
5742
|
+
coolify: limited(this.placedForCompany(auth.coolify, org, context), LIMITS.ownAuth),
|
|
5728
5743
|
runtime: {
|
|
5729
|
-
...
|
|
5744
|
+
...auth.runtime,
|
|
5730
5745
|
onSecret: (value) => {
|
|
5731
5746
|
this.handledSecrets.add(value);
|
|
5732
5747
|
},
|
|
5733
5748
|
},
|
|
5734
5749
|
};
|
|
5750
|
+
}
|
|
5751
|
+
/** `plan/signin-db-move.ts`: copies a company's sign-in data from its Coolify database to the TLS database. */
|
|
5752
|
+
async ownAuthDbMove(org, context) {
|
|
5753
|
+
const own = this.ports.ownServices;
|
|
5754
|
+
if (!own)
|
|
5755
|
+
throw new Error(OWN_SERVICES_PORT_ABSENT);
|
|
5756
|
+
const deps = this.ownAuthDeps(own.provision.auth, org, context);
|
|
5757
|
+
const placed = this.placedForCompany(own.provision.auth.coolify, org, context);
|
|
5758
|
+
await moveSigninDatabase(scopeFromOrg(context, {}), { ...deps, readOldDatabase: (uuid) => placed.getDatabase(uuid) }, orgTarget(org), {
|
|
5759
|
+
project: { ensure: () => this.ensureCoolifyProject(org, context) },
|
|
5760
|
+
label: `${org.slug}:auth`,
|
|
5761
|
+
});
|
|
5762
|
+
return {};
|
|
5763
|
+
}
|
|
5764
|
+
async ownAuth(action, org, context) {
|
|
5765
|
+
const own = this.ports.ownServices;
|
|
5766
|
+
if (!own)
|
|
5767
|
+
throw new Error(OWN_SERVICES_PORT_ABSENT);
|
|
5768
|
+
const hostname = action.desired.hostname;
|
|
5769
|
+
const gitRef = action.desired.gitRef;
|
|
5770
|
+
const hn = ownHostnames(org);
|
|
5771
|
+
const scope = scopeFromOrg(context, {});
|
|
5772
|
+
const deps = this.ownAuthDeps(own.provision.auth, org, context);
|
|
5735
5773
|
const before = scope.recordedIds();
|
|
5736
5774
|
const refChanged = before[OWN_AUTH_APPLIED_GIT_REF_SCOPE_KEY] !== undefined &&
|
|
5737
5775
|
before[OWN_AUTH_APPLIED_GIT_REF_SCOPE_KEY] !== gitRef;
|
|
@@ -5813,12 +5851,17 @@ export class FoundryStepExecutor {
|
|
|
5813
5851
|
},
|
|
5814
5852
|
zitadel: own.zitadelFor({ issuerUrl, bearerRef: target.refs.instanceBearer }),
|
|
5815
5853
|
zitadelForInstance: undefined,
|
|
5854
|
+
runtime: {
|
|
5855
|
+
...own.provision.mail.runtime,
|
|
5856
|
+
onSecret: (value) => this.handledSecrets.add(value),
|
|
5857
|
+
},
|
|
5816
5858
|
}, target, {
|
|
5817
5859
|
hostname: action.desired.hostname,
|
|
5818
5860
|
apex: hn.mailDomain,
|
|
5819
5861
|
zone: hn.zone,
|
|
5820
5862
|
mailHost: action.desired.mailHost,
|
|
5821
5863
|
imageTag: action.desired.gitRef,
|
|
5864
|
+
switchStore: action.desired.switchMailStore === true,
|
|
5822
5865
|
hostPorts: bind ? `${bind}:${smtpPort}:25` : `${smtpPort}:25`,
|
|
5823
5866
|
issuerUrl,
|
|
5824
5867
|
mxRecord: null,
|
package/dist/plan/index.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ export { AdoptRefusedError } from './adopt.js';
|
|
|
23
23
|
export type { AuditActor, AuditLog, AuthzGate, StepExecutor, StepDestroyer, DestroyTarget, StepExecutionContext, PlatformStepExecutor, PlatformStepExecutionContext, FoundryPlanApplyOptions, FoundryApplyOptions, } from './plan-apply.js';
|
|
24
24
|
export { FoundryPlanApply, DeclaredOrgNotFoundError, StalePlanError, ConfirmationRequiredError, NotAuthorizedError, NotATestOrgError, DeclaredPlatformNotFoundError, PlatformStalePlanError, PlatformConfirmationRequiredError, UpgradeConfirmationRequiredError, planNeedsConfirmation, } from './plan-apply.js';
|
|
25
25
|
export type { UnconfirmedUpgrade } from './confirm-upgrade.js';
|
|
26
|
+
export { MAIL_STORE_SWITCH_SERVICE } from './mail-store.js';
|
|
26
27
|
export { NetworkMoveBackupMissingError, UpgradeBackupMissingError } from './backup-gate.js';
|
|
27
28
|
export type { BackupGatePorts, MoveGatePorts } from './backup-gate.js';
|
|
28
29
|
export { FoundryPlatformStepExecutor, ProvidersPlatformCloudflareReader, ProvidersPlatformCoolifyReader, PLATFORM_MAIL_ADMIN_KEY_DESCRIPTION, filesPublicHostsValue, filesShareDomains, provisionFilesHost, provisionJobsHost, provisionMailServer, } from './platform-providers.js';
|
|
@@ -63,7 +64,7 @@ export { createEdgeAccess } from './edge-access.js';
|
|
|
63
64
|
export type { AccessAppSummary, AccessDecision, AccessPolicyRule, AccessPort, EdgePlatformAccess, } from './edge-access.js';
|
|
64
65
|
export { createEdgeWorkers } from './edge-workers.js';
|
|
65
66
|
export type { EdgePlatformWorkers, EdgeWorkersOptions, PlatformWorkersPort, } from './edge-workers.js';
|
|
66
|
-
export type { PlatformUptimeAccessFactory, PlatformUptimeAccessPort, PlatformUptimeDeps, PlatformWorkersFactory, } from './uptime-worker.js';
|
|
67
|
+
export type { PlatformUptimeAccessFactory, PlatformUptimeAccessPort, PlatformUptimeChatDeps, PlatformUptimeDeps, PlatformWorkersFactory, } from './uptime-worker.js';
|
|
67
68
|
export { createEdgeRateLimits } from './edge-ratelimits.js';
|
|
68
69
|
export type { EdgePlatformRateLimits, EdgeRateLimitsOptions, RateLimitPort, RateLimitRuleInput, } from './edge-ratelimits.js';
|
|
69
70
|
export { createEdgeZones } from './edge-zones.js';
|
package/dist/plan/index.js
CHANGED
|
@@ -10,6 +10,7 @@ export { OWN_FILES_R2_SCOPE_KEY, OWN_FILES_R2_UNDELIVERED_SCOPE_KEY, OWN_SERVICE
|
|
|
10
10
|
export { PlatformOrgBlockedError, PlatformOrgPorts } from './platform-org-ports.js';
|
|
11
11
|
export { AdoptRefusedError } from './adopt.js';
|
|
12
12
|
export { FoundryPlanApply, DeclaredOrgNotFoundError, StalePlanError, ConfirmationRequiredError, NotAuthorizedError, NotATestOrgError, DeclaredPlatformNotFoundError, PlatformStalePlanError, PlatformConfirmationRequiredError, UpgradeConfirmationRequiredError, planNeedsConfirmation, } from './plan-apply.js';
|
|
13
|
+
export { MAIL_STORE_SWITCH_SERVICE } from './mail-store.js';
|
|
13
14
|
export { NetworkMoveBackupMissingError, UpgradeBackupMissingError } from './backup-gate.js';
|
|
14
15
|
export { FoundryPlatformStepExecutor, ProvidersPlatformCloudflareReader, ProvidersPlatformCoolifyReader, PLATFORM_MAIL_ADMIN_KEY_DESCRIPTION, filesPublicHostsValue, filesShareDomains, provisionFilesHost, provisionJobsHost, provisionMailServer, } from './platform-providers.js';
|
|
15
16
|
// #171: how a host signs files management requests with the delivered key.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type VersionChangeItem } from '../plan.js';
|
|
2
|
+
/**
|
|
3
|
+
* Stalwart keeps its data (mail, accounts, settings, DKIM keys) in a Coolify
|
|
4
|
+
* Postgres, so the nightly dump of that database is the off-server backup.
|
|
5
|
+
* These keys are shared by the planners (which must not import
|
|
6
|
+
* `platform-providers.ts`) and by `provisionMailServer`.
|
|
7
|
+
*/
|
|
8
|
+
/** The recorded uuid of the mail Postgres, under the same name for the platform and every company. */
|
|
9
|
+
export declare const MAIL_DATABASE_SCOPE_KEY = "coolify_mail_database";
|
|
10
|
+
/** Recorded as {@link MAIL_STORE_POSTGRES} once the `Bootstrap` write has pointed Stalwart at the database. */
|
|
11
|
+
export declare const MAIL_STORE_SCOPE_KEY = "mail_store";
|
|
12
|
+
export declare const MAIL_STORE_POSTGRES = "postgres";
|
|
13
|
+
/** The recorded uuid of the Stalwart app (`platform-providers.ts` re-exports it under its old name). */
|
|
14
|
+
export declare const MAIL_APP_SCOPE_KEY = "coolify_platform_mail_app";
|
|
15
|
+
/** The database and its owner, and the env var Stalwart reads the password from. */
|
|
16
|
+
export declare const MAIL_DATABASE_NAME = "stalwart";
|
|
17
|
+
export declare const MAIL_DATABASE_USER = "stalwart";
|
|
18
|
+
export declare const MAIL_DATABASE_PASSWORD_ENV = "STALWART_DB_PASSWORD";
|
|
19
|
+
/** The `confirmUpgrade` name that confirms the switch (`VersionChangeItem.service`). */
|
|
20
|
+
export declare const MAIL_STORE_SWITCH_SERVICE = "mail-store";
|
|
21
|
+
/**
|
|
22
|
+
* A Stalwart app is recorded and its data store was never moved to Postgres
|
|
23
|
+
* (no mail database recorded either: a fresh instance records that before it
|
|
24
|
+
* creates the app, so a crash halfway never reads as a volume-backed server).
|
|
25
|
+
*/
|
|
26
|
+
export declare function mailStoreIsLegacy(recorded: Readonly<Record<string, string>>): boolean;
|
|
27
|
+
/** The one step description for the switch. `owner` is `the platform` or the company's slug. */
|
|
28
|
+
export declare function mailStoreSwitchDescription(owner: string): string;
|
|
29
|
+
/**
|
|
30
|
+
* The version-change line that makes `apply` refuse the plan until
|
|
31
|
+
* `confirmUpgrade` names `mail-store`. Fixed text: it is part of the
|
|
32
|
+
* stale-plan comparison.
|
|
33
|
+
*/
|
|
34
|
+
export declare function mailStoreSwitchChange(owner: string): VersionChangeItem;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { VERSION_CHANGE_KIND } from '../plan.js';
|
|
2
|
+
/**
|
|
3
|
+
* Stalwart keeps its data (mail, accounts, settings, DKIM keys) in a Coolify
|
|
4
|
+
* Postgres, so the nightly dump of that database is the off-server backup.
|
|
5
|
+
* These keys are shared by the planners (which must not import
|
|
6
|
+
* `platform-providers.ts`) and by `provisionMailServer`.
|
|
7
|
+
*/
|
|
8
|
+
/** The recorded uuid of the mail Postgres, under the same name for the platform and every company. */
|
|
9
|
+
export const MAIL_DATABASE_SCOPE_KEY = 'coolify_mail_database';
|
|
10
|
+
/** Recorded as {@link MAIL_STORE_POSTGRES} once the `Bootstrap` write has pointed Stalwart at the database. */
|
|
11
|
+
export const MAIL_STORE_SCOPE_KEY = 'mail_store';
|
|
12
|
+
export const MAIL_STORE_POSTGRES = 'postgres';
|
|
13
|
+
/** The recorded uuid of the Stalwart app (`platform-providers.ts` re-exports it under its old name). */
|
|
14
|
+
export const MAIL_APP_SCOPE_KEY = 'coolify_platform_mail_app';
|
|
15
|
+
/** The database and its owner, and the env var Stalwart reads the password from. */
|
|
16
|
+
export const MAIL_DATABASE_NAME = 'stalwart';
|
|
17
|
+
export const MAIL_DATABASE_USER = 'stalwart';
|
|
18
|
+
export const MAIL_DATABASE_PASSWORD_ENV = 'STALWART_DB_PASSWORD';
|
|
19
|
+
/** The `confirmUpgrade` name that confirms the switch (`VersionChangeItem.service`). */
|
|
20
|
+
export const MAIL_STORE_SWITCH_SERVICE = 'mail-store';
|
|
21
|
+
/**
|
|
22
|
+
* A Stalwart app is recorded and its data store was never moved to Postgres
|
|
23
|
+
* (no mail database recorded either: a fresh instance records that before it
|
|
24
|
+
* creates the app, so a crash halfway never reads as a volume-backed server).
|
|
25
|
+
*/
|
|
26
|
+
export function mailStoreIsLegacy(recorded) {
|
|
27
|
+
return (Boolean(recorded[MAIL_APP_SCOPE_KEY]) &&
|
|
28
|
+
!recorded[MAIL_DATABASE_SCOPE_KEY] &&
|
|
29
|
+
recorded[MAIL_STORE_SCOPE_KEY] !== MAIL_STORE_POSTGRES);
|
|
30
|
+
}
|
|
31
|
+
/** The one step description for the switch. `owner` is `the platform` or the company's slug. */
|
|
32
|
+
export function mailStoreSwitchDescription(owner) {
|
|
33
|
+
return `switch ${owner}'s mail store to Postgres (recreates the mail server; mail stored there is deleted)`;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The version-change line that makes `apply` refuse the plan until
|
|
37
|
+
* `confirmUpgrade` names `mail-store`. Fixed text: it is part of the
|
|
38
|
+
* stale-plan comparison.
|
|
39
|
+
*/
|
|
40
|
+
export function mailStoreSwitchChange(owner) {
|
|
41
|
+
return {
|
|
42
|
+
kind: VERSION_CHANGE_KIND,
|
|
43
|
+
service: MAIL_STORE_SWITCH_SERVICE,
|
|
44
|
+
description: `${owner}'s mail server still stores its data on a volume: moving it to Postgres recreates the server and deletes the mail stored there; confirm with confirmUpgrade "${MAIL_STORE_SWITCH_SERVICE}"`,
|
|
45
|
+
from: 'volume',
|
|
46
|
+
to: 'postgres',
|
|
47
|
+
needsConfirmation: true,
|
|
48
|
+
};
|
|
49
|
+
}
|