@wtfalch/foundry 0.37.0 → 0.38.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/index.d.ts +1 -1
- package/dist/plan/backup-gate.d.ts +3 -3
- package/dist/plan/backup-gate.js +9 -4
- package/dist/plan/company-backups.d.ts +7 -2
- package/dist/plan/company-backups.js +13 -9
- package/dist/plan/diff.js +4 -0
- package/dist/plan/executor.js +7 -1
- 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.js +15 -4
- package/dist/plan/own-services.d.ts +1 -0
- package/dist/plan/own-services.js +2 -0
- package/dist/plan/plan-apply.js +7 -3
- 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 +23 -1
- package/dist/plan/platform-providers.js +176 -14
- package/dist/plan/uptime-worker.d.ts +20 -1
- package/dist/plan/uptime-worker.js +121 -3
- package/package.json +1 -1
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';
|
|
@@ -52,8 +52,8 @@ export interface BackupGatePorts {
|
|
|
52
52
|
* `unconfirmedUpgrades` and before any step. A service whose database the
|
|
53
53
|
* company does not have passes (there is nothing to migrate); a database that
|
|
54
54
|
* 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
|
-
*
|
|
55
|
+
* old dump, or an unreadable list all refuse. `mail` is covered like the rest:
|
|
56
|
+
* Stalwart's data store is a Postgres database.
|
|
57
57
|
*
|
|
58
58
|
* Where this Coolify has no executions route, the fallback is that the
|
|
59
59
|
* schedule exists, is enabled and saves to S3; that proves a dump is
|
|
@@ -88,6 +88,6 @@ export declare function requireFreshBackupsForMove(ports: MoveGatePorts): Promis
|
|
|
88
88
|
/**
|
|
89
89
|
* The platform databases an upgrade of `service` can migrate. Postgres is all three. archon, ai and
|
|
90
90
|
* 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;
|
|
91
|
+
* sign-in, files and jobs, so the platform has no database for them; edge and hc have no Postgres.
|
|
92
92
|
*/
|
|
93
93
|
export declare function platformDatabasesForService(service: string): readonly string[] | 'all';
|
package/dist/plan/backup-gate.js
CHANGED
|
@@ -123,8 +123,8 @@ function freshSuccess(executions, now, maxAgeMs = BACKUP_MAX_AGE_HOURS * 3_600_0
|
|
|
123
123
|
* `unconfirmedUpgrades` and before any step. A service whose database the
|
|
124
124
|
* company does not have passes (there is nothing to migrate); a database that
|
|
125
125
|
* 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
|
-
*
|
|
126
|
+
* old dump, or an unreadable list all refuse. `mail` is covered like the rest:
|
|
127
|
+
* Stalwart's data store is a Postgres database.
|
|
128
128
|
*
|
|
129
129
|
* Where this Coolify has no executions route, the fallback is that the
|
|
130
130
|
* schedule exists, is enabled and saves to S3; that proves a dump is
|
|
@@ -252,10 +252,15 @@ export async function requireFreshBackupsForMove(ports) {
|
|
|
252
252
|
/**
|
|
253
253
|
* The platform databases an upgrade of `service` can migrate. Postgres is all three. archon, ai and
|
|
254
254
|
* 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;
|
|
255
|
+
* sign-in, files and jobs, so the platform has no database for them; edge and hc have no Postgres.
|
|
256
256
|
*/
|
|
257
257
|
export function platformDatabasesForService(service) {
|
|
258
258
|
if (service === 'postgres')
|
|
259
259
|
return 'all';
|
|
260
|
-
return service === 'archon' ||
|
|
260
|
+
return service === 'archon' ||
|
|
261
|
+
service === 'ai' ||
|
|
262
|
+
service === 'integrations' ||
|
|
263
|
+
service === 'mail'
|
|
264
|
+
? [service]
|
|
265
|
+
: [];
|
|
261
266
|
}
|
|
@@ -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,6 @@
|
|
|
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
4
|
/**
|
|
5
5
|
* F6: the Postgres databases of a company that get a nightly backup, and the
|
|
6
6
|
* recorded id each one's uuid sits under. The keys are second, documented
|
|
@@ -14,6 +14,7 @@ export function companyDatabaseKeys(org) {
|
|
|
14
14
|
return {
|
|
15
15
|
app: `coolify:database:${org.id}`,
|
|
16
16
|
cms: 'coolify_cms_database',
|
|
17
|
+
mail: MAIL_DATABASE_SCOPE_KEY,
|
|
17
18
|
auth: 'coolify_platform_auth_database',
|
|
18
19
|
files: 'coolify_platform_files_database',
|
|
19
20
|
jobs: 'coolify_jobs_database',
|
|
@@ -28,6 +29,13 @@ export function companyDatabaseKeys(org) {
|
|
|
28
29
|
chat_rekey_mas: CHAT_REKEY_MAS_DB_SCOPE_KEY,
|
|
29
30
|
};
|
|
30
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* The database a company backup schedule must name when the Coolify resource
|
|
34
|
+
* is not left to pick its first database. Absent: the resource's first database.
|
|
35
|
+
*/
|
|
36
|
+
export const COMPANY_BACKUP_DATABASE_NAMES = {
|
|
37
|
+
mail: MAIL_DATABASE_NAME,
|
|
38
|
+
};
|
|
31
39
|
/** What every recorded-id key of a company database's backup schedule starts with. */
|
|
32
40
|
export const BACKUP_SCOPE_PREFIX = 'backup_';
|
|
33
41
|
/** The recorded-id key a company database's backup schedule uuid is stored under. */
|
|
@@ -43,6 +51,8 @@ export function databasesForService(service) {
|
|
|
43
51
|
return ['files'];
|
|
44
52
|
case 'jobs':
|
|
45
53
|
return ['jobs'];
|
|
54
|
+
case 'mail':
|
|
55
|
+
return ['mail'];
|
|
46
56
|
case 'synapse':
|
|
47
57
|
return ['synapse', 'chat_synapse'];
|
|
48
58
|
case 'mas':
|
|
@@ -58,8 +68,8 @@ export function databasesForService(service) {
|
|
|
58
68
|
* recorded uuid and no recorded backup yet. `storageUuid` undefined means the
|
|
59
69
|
* host did not wire backups, so nothing is planned and nothing is said; null
|
|
60
70
|
* means the platform's backup storage is not registered, so each database is
|
|
61
|
-
* reported as blocked instead (rule 2). A company
|
|
62
|
-
*
|
|
71
|
+
* reported as blocked instead (rule 2). A company's mail server is one of
|
|
72
|
+
* these databases (Stalwart's data store), dumped like the rest.
|
|
63
73
|
*/
|
|
64
74
|
export function planCompanyBackups(org, recordedIds, storageUuid) {
|
|
65
75
|
const steps = [];
|
|
@@ -91,11 +101,5 @@ export function planCompanyBackups(org, recordedIds, storageUuid) {
|
|
|
91
101
|
},
|
|
92
102
|
});
|
|
93
103
|
}
|
|
94
|
-
if (ownsServices(org)) {
|
|
95
|
-
blocked.push({
|
|
96
|
-
description: `${org.slug}'s mail server keeps its data on a volume, not in Postgres: no backup covers it yet`,
|
|
97
|
-
key: 'backup-not-covered:mail',
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
104
|
return { steps, blocked };
|
|
101
105
|
}
|
package/dist/plan/diff.js
CHANGED
|
@@ -14,6 +14,7 @@ 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
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, planOwnAuth, planOwnFiles, planOwnFilesR2, planOwnJobs, planOwnMail, planOwnSfu, } from './own-diff.js';
|
|
19
20
|
import { ownDnsHostnames } from './own-dns.js';
|
|
@@ -319,6 +320,9 @@ export function computePlanDetail(org, live, recordedIds = {}, agoraHost = NO_AG
|
|
|
319
320
|
// uses, never `versionChangeLine`'s "X -> Y (redeploy)" -- nothing here
|
|
320
321
|
// ever performs that redeploy.
|
|
321
322
|
const versionChanges = [];
|
|
323
|
+
if (ownMail.step?.action.desired.switchMailStore) {
|
|
324
|
+
versionChanges.push(mailStoreSwitchChange(org.slug));
|
|
325
|
+
}
|
|
322
326
|
if (org.calls.enabled && !calls.blocked) {
|
|
323
327
|
const callsAppLive = Boolean(byKey.get(resourceKey('coolify', 'calls', org.id)));
|
|
324
328
|
const synapseChange = versionNotAppliedLine('synapse', callsAppLive, recordedIds[CALLS_SYNAPSE_APPLIED_IMAGE_TAG_SCOPE_KEY], versionPin?.calls ?? VERSIONS.synapse);
|
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';
|
|
@@ -2633,6 +2633,7 @@ export class FoundryStepExecutor {
|
|
|
2633
2633
|
databaseUuid: databaseId,
|
|
2634
2634
|
label: `${org.slug} ${database}`,
|
|
2635
2635
|
storageUuid,
|
|
2636
|
+
databaseName: COMPANY_BACKUP_DATABASE_NAMES[database],
|
|
2636
2637
|
});
|
|
2637
2638
|
context.recordScopeId(backupScopeKey(database), uuid);
|
|
2638
2639
|
return undefined;
|
|
@@ -5813,12 +5814,17 @@ export class FoundryStepExecutor {
|
|
|
5813
5814
|
},
|
|
5814
5815
|
zitadel: own.zitadelFor({ issuerUrl, bearerRef: target.refs.instanceBearer }),
|
|
5815
5816
|
zitadelForInstance: undefined,
|
|
5817
|
+
runtime: {
|
|
5818
|
+
...own.provision.mail.runtime,
|
|
5819
|
+
onSecret: (value) => this.handledSecrets.add(value),
|
|
5820
|
+
},
|
|
5816
5821
|
}, target, {
|
|
5817
5822
|
hostname: action.desired.hostname,
|
|
5818
5823
|
apex: hn.mailDomain,
|
|
5819
5824
|
zone: hn.zone,
|
|
5820
5825
|
mailHost: action.desired.mailHost,
|
|
5821
5826
|
imageTag: action.desired.gitRef,
|
|
5827
|
+
switchStore: action.desired.switchMailStore === true,
|
|
5822
5828
|
hostPorts: bind ? `${bind}:${smtpPort}:25` : `${smtpPort}:25`,
|
|
5823
5829
|
issuerUrl,
|
|
5824
5830
|
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
|
+
}
|
package/dist/plan/own-diff.js
CHANGED
|
@@ -6,6 +6,7 @@ import { brokerEntryAppliedScopeKey } from './broker-entries.js';
|
|
|
6
6
|
import { ZITADEL_FILES_PROJECT_SCOPE_KEY } from './core-files.js';
|
|
7
7
|
import { OWN_AUTH_APPLIED_HOSTNAME_SCOPE_KEY, ownAuthApplied } from './login-host.js';
|
|
8
8
|
import { OWN_MAIL_SMTP_INTERNAL_SCOPE_KEY, OWN_MAIL_SMTP_PORT_BASE, OWN_MAIL_SMTP_PORT_SCOPE_KEY, isOwnMailSmtpPort, } from './mail-relay.js';
|
|
9
|
+
import { mailStoreIsLegacy, mailStoreSwitchDescription } from './mail-store.js';
|
|
9
10
|
import { ownHostnames } from './own-hostnames.js';
|
|
10
11
|
import { OWN_FILES_R2_SCOPE_KEY, OWN_SERVICES_APPLIED_SCOPE_KEY, orgTarget, } from './own-services.js';
|
|
11
12
|
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';
|
|
@@ -212,7 +213,9 @@ export function planOwnMail(options) {
|
|
|
212
213
|
const smtpPort = options.smtpPort ?? OWN_MAIL_SMTP_PORT_BASE;
|
|
213
214
|
if (!isOwnMailSmtpPort(smtpPort))
|
|
214
215
|
return blocked('its mail port is outside the range foundry uses');
|
|
215
|
-
|
|
216
|
+
const storeSwitch = mailStoreIsLegacy(recordedIds);
|
|
217
|
+
if (!storeSwitch &&
|
|
218
|
+
recordedIds[`coolify:own-mail:${org.id}`] &&
|
|
216
219
|
recordedIds[OWN_MAIL_SMTP_PORT_SCOPE_KEY] === String(smtpPort) &&
|
|
217
220
|
recordedIds[PLATFORM_MAIL_APPLIED_HOSTNAME_SCOPE_KEY] === hostname &&
|
|
218
221
|
recordedIds[PLATFORM_MAIL_APPLIED_GIT_REF_SCOPE_KEY] === gitRef &&
|
|
@@ -227,11 +230,19 @@ export function planOwnMail(options) {
|
|
|
227
230
|
system: 'coolify',
|
|
228
231
|
kind: 'own-mail',
|
|
229
232
|
key: org.id,
|
|
230
|
-
desired: {
|
|
233
|
+
desired: {
|
|
234
|
+
hostname,
|
|
235
|
+
gitRef,
|
|
236
|
+
mailHost: platformMailMxHost(platformApex),
|
|
237
|
+
smtpPort,
|
|
238
|
+
...(storeSwitch ? { switchMailStore: true } : {}),
|
|
239
|
+
},
|
|
231
240
|
},
|
|
232
241
|
step: {
|
|
233
|
-
description:
|
|
234
|
-
|
|
242
|
+
description: storeSwitch
|
|
243
|
+
? mailStoreSwitchDescription(org.slug)
|
|
244
|
+
: `provision ${org.slug}'s own mail server`,
|
|
245
|
+
destroys: storeSwitch,
|
|
235
246
|
migrates: false,
|
|
236
247
|
},
|
|
237
248
|
},
|
|
@@ -17,6 +17,7 @@ export function platformTarget() {
|
|
|
17
17
|
authTunnel: 'platform-auth-tunnel',
|
|
18
18
|
authCloudflared: 'platform-auth-cloudflared',
|
|
19
19
|
mailApp: 'platform-mail-stalwart',
|
|
20
|
+
mailDb: 'platform-mail-postgres',
|
|
20
21
|
mailTunnel: 'platform-mail-tunnel',
|
|
21
22
|
mailCloudflared: 'platform-mail-cloudflared',
|
|
22
23
|
filesDb: 'platform-files-db',
|
|
@@ -66,6 +67,7 @@ export function orgTarget(org) {
|
|
|
66
67
|
authTunnel: `${s}-auth-tunnel`,
|
|
67
68
|
authCloudflared: `${s}-auth-cloudflared`,
|
|
68
69
|
mailApp: `${s}-mail-stalwart`,
|
|
70
|
+
mailDb: `${s}-mail-postgres`,
|
|
69
71
|
mailTunnel: `${s}-mail-tunnel`,
|
|
70
72
|
mailCloudflared: `${s}-mail-cloudflared`,
|
|
71
73
|
filesDb: `${s}-files-db`,
|
package/dist/plan/plan-apply.js
CHANGED
|
@@ -22,7 +22,7 @@ import { findNameClashes, nameClashMessage } from './org-names.js';
|
|
|
22
22
|
import { ownHostnames } from './own-hostnames.js';
|
|
23
23
|
import { OWN_FILES_R2_UNDELIVERED_SCOPE_KEY, orgTarget } from './own-services.js';
|
|
24
24
|
import { PLATFORM_ARCHON_KEY, PLATFORM_BACKUP_DATABASE_SCOPE_KEYS, PLATFORM_FILES_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_FILES_KEY, appStepKey, planPlatform as computePlatformPlanDetail, toPlatformPlan, } from './platform-diff.js';
|
|
25
|
-
import { DEFAULT_ORG_PLATFORM_READINESS, ORG_PLATFORM_KEY_REFS, agoraHostValuesForOwn, agoraHostValuesFromPlatform, dnsBlockedReason, heldOwnKeyRefs, heldPlatformKeyRefs, orgPlatformReadinessFor, orgPlatformReadinessFrom, ownerFilesFrom, ownerSignInFrom, platformOwnerUserIdFrom, serviceGitRefsOf, } from './platform-org.js';
|
|
25
|
+
import { DEFAULT_ORG_PLATFORM_READINESS, ORG_PLATFORM_KEY_REFS, agoraHostValuesForOwn, agoraHostValuesFromPlatform, dnsBlockedReason, heldOwnKeyRefs, heldPlatformKeyRefs, orgPlatformReadinessFor, orgPlatformReadinessFrom, ownerChatFrom, ownerFilesFrom, ownerSignInFrom, platformOwnerUserIdFrom, serviceGitRefsOf, } from './platform-org.js';
|
|
26
26
|
import { PLATFORM_AUTH_BROKER_MAIL_KEY_REF, PLATFORM_BACKUP_S3_STORAGE_SCOPE_KEY, } from './platform-providers.js';
|
|
27
27
|
import { CLOUDFLARE_TUNNEL_ENTRY_ROUTES_SCOPE_KEY, CLOUDFLARE_TUNNEL_OIKOS_HOSTNAMES_SCOPE_KEY, } from './proxy-labels.js';
|
|
28
28
|
import { liveWorkers, workerTraceScopeKeys } from './worker.js';
|
|
@@ -1330,9 +1330,13 @@ export class FoundryPlanApply {
|
|
|
1330
1330
|
async platformOwnerInput(platform) {
|
|
1331
1331
|
const owner = await this.store.get(platform.ownerOrgId);
|
|
1332
1332
|
if (!owner)
|
|
1333
|
-
return { signIn: null, files: null };
|
|
1333
|
+
return { signIn: null, files: null, chat: null };
|
|
1334
1334
|
const recorded = (await this.store.getAppliedState(owner.id))?.serviceIds ?? {};
|
|
1335
|
-
return {
|
|
1335
|
+
return {
|
|
1336
|
+
signIn: ownerSignInFrom(owner, recorded),
|
|
1337
|
+
files: ownerFilesFrom(owner, recorded),
|
|
1338
|
+
chat: ownerChatFrom(owner, recorded),
|
|
1339
|
+
};
|
|
1336
1340
|
}
|
|
1337
1341
|
/**
|
|
1338
1342
|
* What the shared mail relay needs from the orgs. A route exists for an org
|
|
@@ -1030,6 +1030,25 @@ export declare const PLATFORM_UPTIME_APPLIED_SECRETS_SCOPE_KEY = "uptime_applied
|
|
|
1030
1030
|
export declare const PLATFORM_UPTIME_APPLIED_VARS_SCOPE_KEY = "uptime_applied_vars";
|
|
1031
1031
|
/** `'1'` once the step removed the Worker because the platform no longer enables it; cleared when it deploys again. */
|
|
1032
1032
|
export declare const PLATFORM_UPTIME_REMOVED_SCOPE_KEY = "uptime_removed";
|
|
1033
|
+
/**
|
|
1034
|
+
* The chat bot's room (`!id:server`), recorded as soon as chat-service names it.
|
|
1035
|
+
* Recorded before the bot's token is held, so a crash between the two leaves the room known.
|
|
1036
|
+
*/
|
|
1037
|
+
export declare const PLATFORM_UPTIME_CHAT_ROOM_ID_SCOPE_KEY = "platform_uptime_chat_room_id";
|
|
1038
|
+
/**
|
|
1039
|
+
* A random id, recorded and persisted before the chat route is called (it mints the bot's token) and
|
|
1040
|
+
* cleared once that token is held. Set while the token is not held, it means a token was minted that may
|
|
1041
|
+
* be lost, so the next call asks the route to rotate it.
|
|
1042
|
+
*/
|
|
1043
|
+
export declare const PLATFORM_UPTIME_CHAT_UNDELIVERED_SCOPE_KEY = "platform_uptime_chat_token_undelivered";
|
|
1044
|
+
/**
|
|
1045
|
+
* The Worker's plain text variables: the chat homeserver and room foundry made, under whatever the
|
|
1046
|
+
* host configured (a host value that is set wins). Unset values are left out.
|
|
1047
|
+
*/
|
|
1048
|
+
export declare function uptimeEffectiveVars(hostVars: Readonly<Record<string, string | undefined>> | undefined, chat: {
|
|
1049
|
+
homeserverUrl: string;
|
|
1050
|
+
roomId: string;
|
|
1051
|
+
} | null): Record<string, string | undefined>;
|
|
1033
1052
|
/** The digest of the Worker's plain text variables that are set; empty when none is. */
|
|
1034
1053
|
export declare function uptimeVarsDigest(vars?: Readonly<Record<string, string | undefined>>): string;
|
|
1035
1054
|
/** The Worker secret names the step sets: the three it mints, and each held one. */
|
|
@@ -1164,6 +1183,16 @@ export interface PlanPlatformOptions {
|
|
|
1164
1183
|
access: boolean;
|
|
1165
1184
|
vars: string;
|
|
1166
1185
|
};
|
|
1186
|
+
/**
|
|
1187
|
+
* The uptime Worker's chat bot, when the host wired one. `pending`: the bot's token is not held and
|
|
1188
|
+
* Company Included's chat is applied, so the step makes it. `blocked`: why it cannot yet (a fixed
|
|
1189
|
+
* line, reported without stopping the rest of the Worker). Absent: no bot path, so the plan keeps
|
|
1190
|
+
* asking for a hand-held credential.
|
|
1191
|
+
*/
|
|
1192
|
+
uptimeChat?: {
|
|
1193
|
+
pending: boolean;
|
|
1194
|
+
blocked?: string;
|
|
1195
|
+
};
|
|
1167
1196
|
/** Why Archon's step must wait: the deployment it triggered is still running. Blocks even an applied step. */
|
|
1168
1197
|
archonDeployRunning?: string;
|
|
1169
1198
|
/**
|
|
@@ -1279,6 +1308,7 @@ export declare const PLATFORM_BACKUP_DATABASE_SCOPE_KEYS: {
|
|
|
1279
1308
|
readonly archon: "coolify_platform_archon_database";
|
|
1280
1309
|
readonly ai: "coolify_platform_ai_database";
|
|
1281
1310
|
readonly integrations: "coolify_integrations_database";
|
|
1311
|
+
readonly mail: "coolify_mail_database";
|
|
1282
1312
|
};
|
|
1283
1313
|
export type PlatformBackupServiceKey = keyof typeof PLATFORM_BACKUP_DATABASE_SCOPE_KEYS;
|
|
1284
1314
|
export declare const PLATFORM_BACKUP_SERVICE_KEYS: readonly PlatformBackupServiceKey[];
|
|
@@ -7,6 +7,7 @@ import { versionChangeNeedsConfirmation } from './confirm-upgrade.js';
|
|
|
7
7
|
import { ACCESS_PROVIDER_RECORD_KEY } from './gates.js';
|
|
8
8
|
import { legacyAppHostname } from './hostnames.js';
|
|
9
9
|
import { PLATFORM_MAIL_RELAY_KEY, planMailRelay } from './mail-relay.js';
|
|
10
|
+
import { MAIL_DATABASE_SCOPE_KEY, mailStoreIsLegacy, mailStoreSwitchChange, mailStoreSwitchDescription, } from './mail-store.js';
|
|
10
11
|
import { planOikosIngress } from './oikos-ingress.js';
|
|
11
12
|
import { callsSfuAppliedFingerprint } from './platform-sfu.js';
|
|
12
13
|
import { signInDomainBlockedText } from './sign-in-org.js';
|
|
@@ -1155,6 +1156,28 @@ export const PLATFORM_UPTIME_APPLIED_SECRETS_SCOPE_KEY = 'uptime_applied_secrets
|
|
|
1155
1156
|
export const PLATFORM_UPTIME_APPLIED_VARS_SCOPE_KEY = 'uptime_applied_vars';
|
|
1156
1157
|
/** `'1'` once the step removed the Worker because the platform no longer enables it; cleared when it deploys again. */
|
|
1157
1158
|
export const PLATFORM_UPTIME_REMOVED_SCOPE_KEY = 'uptime_removed';
|
|
1159
|
+
/**
|
|
1160
|
+
* The chat bot's room (`!id:server`), recorded as soon as chat-service names it.
|
|
1161
|
+
* Recorded before the bot's token is held, so a crash between the two leaves the room known.
|
|
1162
|
+
*/
|
|
1163
|
+
export const PLATFORM_UPTIME_CHAT_ROOM_ID_SCOPE_KEY = 'platform_uptime_chat_room_id';
|
|
1164
|
+
/**
|
|
1165
|
+
* A random id, recorded and persisted before the chat route is called (it mints the bot's token) and
|
|
1166
|
+
* cleared once that token is held. Set while the token is not held, it means a token was minted that may
|
|
1167
|
+
* be lost, so the next call asks the route to rotate it.
|
|
1168
|
+
*/
|
|
1169
|
+
export const PLATFORM_UPTIME_CHAT_UNDELIVERED_SCOPE_KEY = 'platform_uptime_chat_token_undelivered';
|
|
1170
|
+
/**
|
|
1171
|
+
* The Worker's plain text variables: the chat homeserver and room foundry made, under whatever the
|
|
1172
|
+
* host configured (a host value that is set wins). Unset values are left out.
|
|
1173
|
+
*/
|
|
1174
|
+
export function uptimeEffectiveVars(hostVars, chat) {
|
|
1175
|
+
const host = Object.fromEntries(Object.entries(hostVars ?? {}).filter(([, v]) => Boolean(v)));
|
|
1176
|
+
return {
|
|
1177
|
+
...(chat ? { CHAT_HOMESERVER_URL: chat.homeserverUrl, CHAT_ROOM_ID: chat.roomId } : {}),
|
|
1178
|
+
...host,
|
|
1179
|
+
};
|
|
1180
|
+
}
|
|
1158
1181
|
/** The digest of the Worker's plain text variables that are set; empty when none is. */
|
|
1159
1182
|
export function uptimeVarsDigest(vars = {}) {
|
|
1160
1183
|
const set = Object.entries(vars)
|
|
@@ -1332,7 +1355,7 @@ export function planPlatform(platform, live, options = {}) {
|
|
|
1332
1355
|
plannedSteps.push(heartbeats.step);
|
|
1333
1356
|
// foundry#542: after Archon is applied (never merely planned): the Worker
|
|
1334
1357
|
// probes Archon, and Archon's env takes the token this step mints.
|
|
1335
|
-
const uptime = planUptime(platform, recordedIds, blocked.uptime, blocked.uptimeSecretsHeld ?? [], Boolean(recordedIds[resourceKey('coolify', 'app', PLATFORM_ARCHON_KEY)]), blocked.uptimeHeld);
|
|
1358
|
+
const uptime = planUptime(platform, recordedIds, blocked.uptime, blocked.uptimeSecretsHeld ?? [], Boolean(recordedIds[resourceKey('coolify', 'app', PLATFORM_ARCHON_KEY)]), blocked.uptimeHeld, blocked.uptimeChat);
|
|
1336
1359
|
if (uptime.step)
|
|
1337
1360
|
plannedSteps.push(uptime.step);
|
|
1338
1361
|
if (archon.step && blocked.archonRunsInsideApp)
|
|
@@ -1353,6 +1376,9 @@ export function planPlatform(platform, live, options = {}) {
|
|
|
1353
1376
|
// read for, is premature, not informational.
|
|
1354
1377
|
const versionChanges = [];
|
|
1355
1378
|
for (const change of [
|
|
1379
|
+
mail.blocked || !mail.step?.action.desired.switchMailStore
|
|
1380
|
+
? null
|
|
1381
|
+
: mailStoreSwitchChange('the platform'),
|
|
1356
1382
|
mail.blocked
|
|
1357
1383
|
? null
|
|
1358
1384
|
: versionChangeLine('mail', recordedIds[PLATFORM_MAIL_APPLIED_GIT_REF_SCOPE_KEY], platform.mail.gitRef, 'image, redeploy'),
|
|
@@ -1593,11 +1619,13 @@ export const PLATFORM_BACKUP_DATABASE_SCOPE_KEYS = {
|
|
|
1593
1619
|
archon: 'coolify_platform_archon_database',
|
|
1594
1620
|
ai: 'coolify_platform_ai_database',
|
|
1595
1621
|
integrations: 'coolify_integrations_database',
|
|
1622
|
+
mail: MAIL_DATABASE_SCOPE_KEY,
|
|
1596
1623
|
};
|
|
1597
1624
|
export const PLATFORM_BACKUP_SERVICE_KEYS = [
|
|
1598
1625
|
'archon',
|
|
1599
1626
|
'ai',
|
|
1600
1627
|
'integrations',
|
|
1628
|
+
'mail',
|
|
1601
1629
|
];
|
|
1602
1630
|
/**
|
|
1603
1631
|
* #257: a nightly backup for each platform database foundry itself creates,
|
|
@@ -2047,7 +2075,9 @@ function planMail(platform, recordedIds, executorBlocked, authReady) {
|
|
|
2047
2075
|
if (!platform.mail.enabled)
|
|
2048
2076
|
return { step: null, blocked: null };
|
|
2049
2077
|
const key = resourceKey('coolify', 'app', PLATFORM_MAIL_KEY);
|
|
2050
|
-
|
|
2078
|
+
const storeSwitch = mailStoreIsLegacy(recordedIds);
|
|
2079
|
+
if (!storeSwitch &&
|
|
2080
|
+
recordedIds[key] &&
|
|
2051
2081
|
recordedIds[PLATFORM_MAIL_CONNECTOR_APP_SCOPE_KEY] &&
|
|
2052
2082
|
recordedIds[PLATFORM_MAIL_APPLIED_HOSTNAME_SCOPE_KEY] === platform.mail.hostname &&
|
|
2053
2083
|
recordedIds[PLATFORM_MAIL_APPLIED_GIT_REF_SCOPE_KEY] === platform.mail.gitRef &&
|
|
@@ -2083,6 +2113,28 @@ function planMail(platform, recordedIds, executorBlocked, authReady) {
|
|
|
2083
2113
|
blocked: "the platform's mail server is blocked, not planned: its OIDC directory uses the platform's own sign-in, which is neither applied nor planned",
|
|
2084
2114
|
};
|
|
2085
2115
|
}
|
|
2116
|
+
if (storeSwitch) {
|
|
2117
|
+
return {
|
|
2118
|
+
step: {
|
|
2119
|
+
action: {
|
|
2120
|
+
system: 'coolify',
|
|
2121
|
+
kind: 'app',
|
|
2122
|
+
key: PLATFORM_MAIL_KEY,
|
|
2123
|
+
desired: {
|
|
2124
|
+
hostname: platform.mail.hostname,
|
|
2125
|
+
gitRef: platform.mail.gitRef,
|
|
2126
|
+
switchMailStore: true,
|
|
2127
|
+
},
|
|
2128
|
+
},
|
|
2129
|
+
step: {
|
|
2130
|
+
description: mailStoreSwitchDescription('the platform'),
|
|
2131
|
+
destroys: true,
|
|
2132
|
+
migrates: false,
|
|
2133
|
+
},
|
|
2134
|
+
},
|
|
2135
|
+
blocked: null,
|
|
2136
|
+
};
|
|
2137
|
+
}
|
|
2086
2138
|
return {
|
|
2087
2139
|
step: {
|
|
2088
2140
|
action: {
|
|
@@ -2684,7 +2736,7 @@ function planUptime(platform, recordedIds, executorBlocked, heldOptional, archon
|
|
|
2684
2736
|
token: true,
|
|
2685
2737
|
access: true,
|
|
2686
2738
|
vars: '',
|
|
2687
|
-
}) {
|
|
2739
|
+
}, chat) {
|
|
2688
2740
|
const { uptime } = platform;
|
|
2689
2741
|
const deployed = Boolean(recordedIds[resourceKey('cloudflare', 'worker', PLATFORM_UPTIME_KEY)]) &&
|
|
2690
2742
|
!recordedIds[PLATFORM_UPTIME_REMOVED_SCOPE_KEY];
|
|
@@ -2733,7 +2785,7 @@ function planUptime(platform, recordedIds, executorBlocked, heldOptional, archon
|
|
|
2733
2785
|
if (executorBlocked)
|
|
2734
2786
|
return { step: null, blocked: [executorBlocked], notes: [] };
|
|
2735
2787
|
const notes = [];
|
|
2736
|
-
if (!heldOptional.includes('CHAT_ACCESS_TOKEN')) {
|
|
2788
|
+
if (!chat && !heldOptional.includes('CHAT_ACCESS_TOKEN')) {
|
|
2737
2789
|
notes.push({
|
|
2738
2790
|
key: 'uptime:CHAT_ACCESS_TOKEN',
|
|
2739
2791
|
service: 'uptime',
|
|
@@ -2759,9 +2811,12 @@ function planUptime(platform, recordedIds, executorBlocked, heldOptional, archon
|
|
|
2759
2811
|
recordedIds[PLATFORM_UPTIME_APPLIED_SHA256_SCOPE_KEY] === uptime.sha256 &&
|
|
2760
2812
|
recordedIds[PLATFORM_UPTIME_APPLIED_HOSTNAME_SCOPE_KEY] === hostname &&
|
|
2761
2813
|
(recordedIds[PLATFORM_UPTIME_APPLIED_VARS_SCOPE_KEY] ?? '') === uptimeHeld.vars &&
|
|
2762
|
-
recordedIds[PLATFORM_UPTIME_APPLIED_SECRETS_SCOPE_KEY] === uptimeSecretNames(heldOptional)
|
|
2814
|
+
recordedIds[PLATFORM_UPTIME_APPLIED_SECRETS_SCOPE_KEY] === uptimeSecretNames(heldOptional) &&
|
|
2815
|
+
!chat?.pending;
|
|
2816
|
+
// Reported beside the step, not instead of it: the Worker deploys without chat alerts meanwhile.
|
|
2817
|
+
const blockedLines = chat?.blocked ? [chat.blocked] : [];
|
|
2763
2818
|
if (applied)
|
|
2764
|
-
return { step: null, blocked:
|
|
2819
|
+
return { step: null, blocked: blockedLines, notes };
|
|
2765
2820
|
return {
|
|
2766
2821
|
step: {
|
|
2767
2822
|
action: action({ hostname, gitRef: uptime.tag, sha256: uptime.sha256 }),
|
|
@@ -2771,7 +2826,7 @@ function planUptime(platform, recordedIds, executorBlocked, heldOptional, archon
|
|
|
2771
2826
|
migrates: false,
|
|
2772
2827
|
},
|
|
2773
2828
|
},
|
|
2774
|
-
blocked:
|
|
2829
|
+
blocked: blockedLines,
|
|
2775
2830
|
notes,
|
|
2776
2831
|
};
|
|
2777
2832
|
}
|