@wtfalch/foundry 0.36.1 → 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.d.ts +8 -0
- package/dist/plan/executor.js +13 -2
- 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 +6 -1
- package/dist/plan/own-services.js +5 -1
- 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 +55 -1
- package/dist/plan/platform-providers.js +205 -14
- package/dist/plan/uptime-worker.d.ts +20 -1
- package/dist/plan/uptime-worker.js +121 -3
- package/package.json +1 -1
|
@@ -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
|
}
|
|
@@ -304,10 +304,23 @@ export interface OwnerFiles {
|
|
|
304
304
|
organisationId: string;
|
|
305
305
|
managementKeyRef: string;
|
|
306
306
|
}
|
|
307
|
+
/**
|
|
308
|
+
* The owner company's chat once `coolify:chat` has applied: where the uptime
|
|
309
|
+
* Worker's bot is made and which people join its room.
|
|
310
|
+
*/
|
|
311
|
+
export interface OwnerChat {
|
|
312
|
+
/** The chat-service app (Coolify uuid), whose env holds `CHAT_INTERNAL_TOKEN`. */
|
|
313
|
+
appId: string;
|
|
314
|
+
/** The company's Agora host: the public origin of the bot route and the Matrix homeserver. */
|
|
315
|
+
agoraHost: string;
|
|
316
|
+
/** ZITADEL subjects of the owner company's owners whose invite is recorded. */
|
|
317
|
+
subjects: readonly string[];
|
|
318
|
+
}
|
|
307
319
|
/** What the platform plan reads about the owner company. */
|
|
308
320
|
export interface PlatformOwnerInput {
|
|
309
321
|
signIn?: OwnerSignIn | null;
|
|
310
322
|
files?: OwnerFiles | null;
|
|
323
|
+
chat?: OwnerChat | null;
|
|
311
324
|
}
|
|
312
325
|
/**
|
|
313
326
|
* The owner's sign-in from its declared org and the ids its own steps recorded.
|
|
@@ -326,3 +339,5 @@ export declare function ownerSignInFrom(owner: DeclaredOrg | null, recorded: Rea
|
|
|
326
339
|
export declare function platformOwnerUserIdFrom(owner: DeclaredOrg | null, recorded: Readonly<Record<string, string>>): string | null;
|
|
327
340
|
/** The owner's files host once its own files step has applied; `null` before. */
|
|
328
341
|
export declare function ownerFilesFrom(owner: DeclaredOrg | null, recorded: Readonly<Record<string, string>>): OwnerFiles | null;
|
|
342
|
+
/** The owner's chat once its `coolify:chat` step has applied; `null` before, and when chat is off. */
|
|
343
|
+
export declare function ownerChatFrom(owner: DeclaredOrg | null, recorded: Readonly<Record<string, string>>): OwnerChat | null;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ownsServices } from '../org.js';
|
|
2
|
-
import { ZITADEL_ORG_SCOPE_KEY } from './agora.js';
|
|
2
|
+
import { ZITADEL_ORG_SCOPE_KEY, agoraHostnameFor } from './agora.js';
|
|
3
3
|
import { ACCESS_PROVIDER_RECORD_KEY } from './gates.js';
|
|
4
4
|
import { FILES_MANAGEMENT_KEY_REF, JOBS_ADMIN_TOKEN_REF, PLATFORM_AI_MANAGEMENT_KEY_REF, } from './held-key-refs.js';
|
|
5
5
|
import { OWN_AUTH_APPLIED_HOSTNAME_SCOPE_KEY, ownAuthApplied } from './login-host.js';
|
|
@@ -352,3 +352,19 @@ export function ownerFilesFrom(owner, recorded) {
|
|
|
352
352
|
managementKeyRef: orgTarget(owner).refs.filesManagement,
|
|
353
353
|
};
|
|
354
354
|
}
|
|
355
|
+
/** The recorded id of the chat-service app (`coolify_chat_service`, set by the `coolify:chat` step). */
|
|
356
|
+
const CHAT_SERVICE_APP_SCOPE_KEY = 'coolify_chat_service';
|
|
357
|
+
/** The owner's chat once its `coolify:chat` step has applied; `null` before, and when chat is off. */
|
|
358
|
+
export function ownerChatFrom(owner, recorded) {
|
|
359
|
+
const appId = recorded[CHAT_SERVICE_APP_SCOPE_KEY];
|
|
360
|
+
if (!owner || !owner.chat.enabled || !appId || !recorded[`coolify:chat:${owner.id}`])
|
|
361
|
+
return null;
|
|
362
|
+
return {
|
|
363
|
+
appId,
|
|
364
|
+
agoraHost: agoraHostnameFor(owner),
|
|
365
|
+
subjects: owner.owners.flatMap((o) => {
|
|
366
|
+
const id = recorded[`zitadel:owner:${owner.id}:${o.email.toLowerCase()}`];
|
|
367
|
+
return id ? [id] : [];
|
|
368
|
+
}),
|
|
369
|
+
};
|
|
370
|
+
}
|
|
@@ -59,6 +59,33 @@ export type PlatformTunnelPort = Pick<TunnelPort, 'findTunnelByName' | 'createTu
|
|
|
59
59
|
export declare function platformTunnels(deps: {
|
|
60
60
|
tunnels: PlatformTunnelsFactory;
|
|
61
61
|
}, recorded: Readonly<Record<string, string>>, step: string): Promise<PlatformTunnelPort>;
|
|
62
|
+
/**
|
|
63
|
+
* Request headers a client can set that ZITADEL reads to pick or name an
|
|
64
|
+
* instance (auth#241, auth#252). Cloudflare removes them from every inbound
|
|
65
|
+
* request on a sign-in hostname, because the tunnel routes ZITADEL's paths
|
|
66
|
+
* straight to ZITADEL and no app code sees them first.
|
|
67
|
+
*/
|
|
68
|
+
export declare const SIGNIN_STRIPPED_REQUEST_HEADERS: readonly ["X-Zitadel-Instance-Host", "X-Zitadel-Public-Host", "X-Zitadel-Forward-Host", "X-Forwarded-Host"];
|
|
69
|
+
/** One Cloudflare request-header Transform Rule (phase `http_request_late_transform`) that removes headers. */
|
|
70
|
+
export interface RequestHeaderStripRule {
|
|
71
|
+
/** Names the rule in the zone: the key that makes a repeat safe. */
|
|
72
|
+
description: string;
|
|
73
|
+
expression: string;
|
|
74
|
+
headers: readonly string[];
|
|
75
|
+
}
|
|
76
|
+
/** The Cloudflare request header rule calls a sign-in step makes. */
|
|
77
|
+
export interface RequestHeaderPort {
|
|
78
|
+
/** Makes the rule, or returns the one already made for that zone and description. */
|
|
79
|
+
ensureRequestHeaderStrip(zoneId: string, rule: RequestHeaderStripRule): Promise<{
|
|
80
|
+
id: string;
|
|
81
|
+
}>;
|
|
82
|
+
}
|
|
83
|
+
/** Like {@link PlatformTunnelsFactory}: builds the {@link RequestHeaderPort} at edge's recorded address. */
|
|
84
|
+
export type PlatformRequestHeadersFactory = (edgeUrl: string) => RequestHeaderPort | Promise<RequestHeaderPort>;
|
|
85
|
+
/** The request header port for a platform step; undefined when edge is not recorded or the host supplied no factory. */
|
|
86
|
+
export declare function platformRequestHeaders(deps: {
|
|
87
|
+
requestHeaders?: PlatformRequestHeadersFactory;
|
|
88
|
+
}, recorded: Readonly<Record<string, string>>): Promise<RequestHeaderPort | undefined>;
|
|
62
89
|
/**
|
|
63
90
|
* F4.access: builds the {@link AccessPort} a platform step finds, creates and
|
|
64
91
|
* deletes its Access app through, given the same address as
|
|
@@ -110,6 +137,11 @@ export interface PlatformAuthDeps {
|
|
|
110
137
|
dns: PlatformDnsFactory;
|
|
111
138
|
/** F4.tunnels: every tunnel call, through edge at its recorded address (see {@link PlatformTunnelsFactory}). */
|
|
112
139
|
tunnels: PlatformTunnelsFactory;
|
|
140
|
+
/**
|
|
141
|
+
* Request header rules through edge. Edge has no such call yet (@wtfalch/edge 0.13.0 offers
|
|
142
|
+
* rate limit rules only), so a host that leaves this out gets no rule and a logged line per run.
|
|
143
|
+
*/
|
|
144
|
+
requestHeaders?: PlatformRequestHeadersFactory;
|
|
113
145
|
cloudflare: Pick<CloudflarePort, 'findZoneByApex'>;
|
|
114
146
|
/** Must target the platform's own new instance (its issuer), or `zitadelForInstance` must be set. */
|
|
115
147
|
zitadel: Pick<ZitadelPort, 'findOrCreateOrg' | 'mintAdminMachineUser' | 'mintLoginClientMachineUser' | 'listLoginClientPats' | 'reserveUserName' | 'closeSelfRegistration' | 'enforceSecondFactor' | 'inviteOrgOwner' | 'ensureDomainPolicy' | 'addOrgDomain' | 'removeOrgDomain' | 'setPrimaryOrgDomain' | 'setUserMetadata' | 'getUserMetadata'> & PlatformZitadelSmtp;
|
|
@@ -322,6 +354,10 @@ export declare class FoundryPlatformStepExecutor implements PlatformStepExecutor
|
|
|
322
354
|
access: boolean;
|
|
323
355
|
vars: string;
|
|
324
356
|
};
|
|
357
|
+
uptimeChat?: {
|
|
358
|
+
pending: boolean;
|
|
359
|
+
blocked?: string;
|
|
360
|
+
};
|
|
325
361
|
archonRunsInsideApp?: boolean;
|
|
326
362
|
archonDeployRunning?: string;
|
|
327
363
|
archonIdTokenUserinfo?: boolean;
|
|
@@ -574,6 +610,16 @@ export interface PlatformMailAdminPort {
|
|
|
574
610
|
issuerUrl: string;
|
|
575
611
|
audience: string;
|
|
576
612
|
};
|
|
613
|
+
/**
|
|
614
|
+
* Where Stalwart keeps its data: a Postgres reached at `host:port`. The password is not here:
|
|
615
|
+
* Stalwart reads it from the env var {@link MAIL_DATABASE_PASSWORD_ENV}.
|
|
616
|
+
*/
|
|
617
|
+
dataStore: {
|
|
618
|
+
host: string;
|
|
619
|
+
port: number;
|
|
620
|
+
database: string;
|
|
621
|
+
user: string;
|
|
622
|
+
};
|
|
577
623
|
}): Promise<'restart' | undefined>;
|
|
578
624
|
/**
|
|
579
625
|
* `StalwartClient.createApiKey`'s own signature. The key belongs to a real
|
|
@@ -610,7 +656,7 @@ export declare function defaultStalwartForInstance(input: {
|
|
|
610
656
|
* own shape.
|
|
611
657
|
*/
|
|
612
658
|
export interface PlatformMailDeps {
|
|
613
|
-
coolify: Pick<CoolifyPort, 'findProjectByName' | 'createProject' | 'findOrCreateEnvironment' | 'listServers' | 'findApplicationByName' | 'getApplication' | 'createDockerImageApplication' | 'createDockerfileApplication' | 'raw' | 'listStorages' | 'createPersistentStorage' | 'bulkUpsertEnvs' | 'listEnvs' | 'updateApplication' | 'deploy' | 'deployAndWait' | 'restart' | 'getDeployment'>;
|
|
659
|
+
coolify: Pick<CoolifyPort, 'findProjectByName' | 'createProject' | 'findOrCreateEnvironment' | 'listServers' | 'findApplicationByName' | 'getApplication' | 'createDockerImageApplication' | 'createDockerfileApplication' | 'createPostgresDatabase' | 'getDatabase' | 'listDatabases' | 'deleteApplication' | 'raw' | 'listStorages' | 'createPersistentStorage' | 'bulkUpsertEnvs' | 'listEnvs' | 'updateApplication' | 'deploy' | 'deployAndWait' | 'restart' | 'getDeployment'>;
|
|
614
660
|
/** F4.dns: every DNS record call, through edge at its recorded address (see {@link PlatformDnsFactory}). */
|
|
615
661
|
dns: PlatformDnsFactory;
|
|
616
662
|
/** F4.tunnels: every tunnel call, through edge at its recorded address (see {@link PlatformTunnelsFactory}). */
|
|
@@ -652,6 +698,8 @@ export interface PlatformMailRuntime {
|
|
|
652
698
|
readyIntervalMs?: number;
|
|
653
699
|
/** Tries at handing the admin key to the host (default 3). */
|
|
654
700
|
deliverAttempts?: number;
|
|
701
|
+
/** Called with the mail database's password, so the host can keep it out of every message it writes. */
|
|
702
|
+
onSecret?: (value: string) => void;
|
|
655
703
|
}
|
|
656
704
|
/**
|
|
657
705
|
* What {@link provisionMailServer} reads besides the target's names: the
|
|
@@ -669,6 +717,12 @@ export interface MailServerInput {
|
|
|
669
717
|
mailHost: string;
|
|
670
718
|
/** The Stalwart image tag. */
|
|
671
719
|
imageTag: string;
|
|
720
|
+
/**
|
|
721
|
+
* The plan's confirmed switch of a volume-backed server to Postgres: delete the Stalwart app and its
|
|
722
|
+
* volume, clear the ids that describe it, then provision as usual. Without it a server still on its
|
|
723
|
+
* volume is refused before any write.
|
|
724
|
+
*/
|
|
725
|
+
switchStore?: boolean;
|
|
672
726
|
/** `host:container` list; empty publishes no host port. */
|
|
673
727
|
hostPorts: string;
|
|
674
728
|
/** The sign-in the server's directory trusts; its `mail` project's id is the audience. */
|