@wtfalch/foundry 0.29.0 → 0.30.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/calls-sfu-ssh-client.d.ts +11 -0
- package/dist/calls-sfu-ssh-client.js +65 -11
- package/dist/index.d.ts +7 -5
- package/dist/index.js +4 -3
- package/dist/org.d.ts +88 -0
- package/dist/org.js +68 -0
- package/dist/plan/agora.d.ts +13 -0
- package/dist/plan/agora.js +15 -0
- package/dist/plan/chat-matrix.d.ts +31 -2
- package/dist/plan/chat-matrix.js +40 -6
- package/dist/plan/diff.d.ts +45 -2
- package/dist/plan/diff.js +162 -10
- package/dist/plan/executor.d.ts +93 -0
- package/dist/plan/executor.js +345 -9
- package/dist/plan/index.d.ts +5 -2
- package/dist/plan/index.js +3 -1
- package/dist/plan/plan-apply.js +1 -0
- package/dist/plan/platform-diff.d.ts +9 -0
- package/dist/plan/platform-diff.js +15 -3
- package/dist/plan/platform-org.d.ts +2 -0
- package/dist/plan/platform-org.js +4 -1
- package/dist/plan/platform-providers.d.ts +1 -1
- package/dist/plan/platform-providers.js +79 -22
- package/dist/plan/providers-readers.d.ts +14 -1
- package/dist/plan/providers-readers.js +25 -2
- package/dist/plan/step-system.js +1 -0
- package/dist/plan/version-status.d.ts +200 -0
- package/dist/plan/version-status.js +213 -0
- package/dist/plan/zitadel-login-policy.d.ts +23 -0
- package/dist/plan/zitadel-login-policy.js +103 -0
- package/dist/plan/zitadel-machine-user.d.ts +1 -1
- package/dist/plan/zitadel-machine-user.js +34 -0
- package/dist/platform.d.ts +1 -1
- package/package.json +3 -2
|
@@ -18,12 +18,14 @@ interface SshStream {
|
|
|
18
18
|
stderr: {
|
|
19
19
|
on(event: string, listener: (...args: never[]) => void): unknown;
|
|
20
20
|
};
|
|
21
|
+
close(): void;
|
|
21
22
|
}
|
|
22
23
|
interface SshSftp {
|
|
23
24
|
writeFile(path: string, contents: string, options: {
|
|
24
25
|
mode: number;
|
|
25
26
|
} | undefined, cb: (err?: Error | null) => void): void;
|
|
26
27
|
chmod(path: string, mode: number, cb: (err?: Error | null) => void): void;
|
|
28
|
+
end(): void;
|
|
27
29
|
}
|
|
28
30
|
interface SshClient {
|
|
29
31
|
on(event: string, listener: (...args: never[]) => void): SshClient;
|
|
@@ -41,6 +43,15 @@ export interface CallsSfuSshConnectOptions {
|
|
|
41
43
|
/** `SHA256:...`. Without it every host key is refused. */
|
|
42
44
|
sshHostFingerprint: string | undefined;
|
|
43
45
|
}
|
|
46
|
+
/** `docker compose up -d` pulls images, so a command gets two minutes. */
|
|
47
|
+
export declare const SSH_COMMAND_TIMEOUT_MS = 120000;
|
|
48
|
+
/** One SFTP write (open, write, chmod) of a small config file. */
|
|
49
|
+
export declare const SSH_WRITE_TIMEOUT_MS = 30000;
|
|
50
|
+
/** Handshake limit; ssh2's default is 20 s, set here so it is explicit. */
|
|
51
|
+
export declare const SSH_READY_TIMEOUT_MS = 20000;
|
|
52
|
+
/** A dead link ends the session after 3 missed keepalives, 10 s apart. */
|
|
53
|
+
export declare const SSH_KEEPALIVE_INTERVAL_MS = 10000;
|
|
54
|
+
export declare const SSH_KEEPALIVE_COUNT_MAX = 3;
|
|
44
55
|
export declare function connectCallsSfuSsh(options: CallsSfuSshConnectOptions, deps?: {
|
|
45
56
|
Client?: SshClientConstructor;
|
|
46
57
|
}): Promise<CallsSfuSshSession & {
|
|
@@ -13,36 +13,87 @@
|
|
|
13
13
|
* hands in a fake and never opens a socket.
|
|
14
14
|
*/
|
|
15
15
|
import { verifyHostKey } from './calls-sfu-deploy-support.js';
|
|
16
|
-
|
|
16
|
+
/** `docker compose up -d` pulls images, so a command gets two minutes. */
|
|
17
|
+
export const SSH_COMMAND_TIMEOUT_MS = 120_000;
|
|
18
|
+
/** One SFTP write (open, write, chmod) of a small config file. */
|
|
19
|
+
export const SSH_WRITE_TIMEOUT_MS = 30_000;
|
|
20
|
+
/** Handshake limit; ssh2's default is 20 s, set here so it is explicit. */
|
|
21
|
+
export const SSH_READY_TIMEOUT_MS = 20_000;
|
|
22
|
+
/** A dead link ends the session after 3 missed keepalives, 10 s apart. */
|
|
23
|
+
export const SSH_KEEPALIVE_INTERVAL_MS = 10_000;
|
|
24
|
+
export const SSH_KEEPALIVE_COUNT_MAX = 3;
|
|
25
|
+
/** `sshExec` keeps the last 64 KiB of each of stdout and stderr. */
|
|
26
|
+
const SSH_OUTPUT_MAX_CHARS = 64 * 1024;
|
|
27
|
+
// The timeout messages name the command or the file path and nothing else.
|
|
28
|
+
// Every caller passes a fixed command or a fixed path (`calls-sfu.ts`,
|
|
29
|
+
// `calls-sfu-ssh-adapter.ts`); the cloudflared token and the LiveKit keys
|
|
30
|
+
// only ever travel as file contents, which are never named here.
|
|
31
|
+
function sshExec(conn, command, timeoutMs = SSH_COMMAND_TIMEOUT_MS) {
|
|
17
32
|
return new Promise((resolve, reject) => {
|
|
18
|
-
|
|
19
|
-
|
|
33
|
+
let stream;
|
|
34
|
+
let timedOut = false;
|
|
35
|
+
const timer = setTimeout(() => {
|
|
36
|
+
timedOut = true;
|
|
37
|
+
stream?.close();
|
|
38
|
+
reject(new Error(`ssh command timed out after ${timeoutMs / 1000}s: ${command}`));
|
|
39
|
+
}, timeoutMs);
|
|
40
|
+
conn.exec(command, (err, execStream) => {
|
|
41
|
+
if (timedOut)
|
|
42
|
+
return execStream?.close();
|
|
43
|
+
if (err) {
|
|
44
|
+
clearTimeout(timer);
|
|
20
45
|
return reject(err);
|
|
46
|
+
}
|
|
47
|
+
stream = execStream;
|
|
21
48
|
let stdout = '';
|
|
22
49
|
let stderr = '';
|
|
23
|
-
stream.on('close', (code) =>
|
|
50
|
+
stream.on('close', (code) => {
|
|
51
|
+
clearTimeout(timer);
|
|
52
|
+
resolve({ exitCode: code ?? 0, stdout, stderr });
|
|
53
|
+
});
|
|
54
|
+
stream.on('error', (streamErr) => {
|
|
55
|
+
clearTimeout(timer);
|
|
56
|
+
reject(streamErr);
|
|
57
|
+
});
|
|
24
58
|
stream.on('data', (chunk) => {
|
|
25
|
-
stdout
|
|
59
|
+
stdout = (stdout + chunk).slice(-SSH_OUTPUT_MAX_CHARS);
|
|
26
60
|
});
|
|
27
61
|
stream.stderr.on('data', (chunk) => {
|
|
28
|
-
stderr
|
|
62
|
+
stderr = (stderr + chunk).slice(-SSH_OUTPUT_MAX_CHARS);
|
|
29
63
|
});
|
|
30
64
|
});
|
|
31
65
|
});
|
|
32
66
|
}
|
|
33
|
-
function sshWriteFile(conn, path, contents, mode) {
|
|
67
|
+
function sshWriteFile(conn, path, contents, mode, timeoutMs = SSH_WRITE_TIMEOUT_MS) {
|
|
34
68
|
return new Promise((resolve, reject) => {
|
|
69
|
+
let session;
|
|
70
|
+
let timedOut = false;
|
|
71
|
+
const timer = setTimeout(() => {
|
|
72
|
+
timedOut = true;
|
|
73
|
+
session?.end();
|
|
74
|
+
reject(new Error(`sftp write timed out after ${timeoutMs / 1000}s: ${path}`));
|
|
75
|
+
}, timeoutMs);
|
|
76
|
+
const settle = (err) => {
|
|
77
|
+
clearTimeout(timer);
|
|
78
|
+
if (err)
|
|
79
|
+
reject(err);
|
|
80
|
+
else
|
|
81
|
+
resolve();
|
|
82
|
+
};
|
|
35
83
|
conn.sftp((err, sftp) => {
|
|
84
|
+
if (timedOut)
|
|
85
|
+
return sftp?.end();
|
|
36
86
|
if (err)
|
|
37
|
-
return
|
|
87
|
+
return settle(err);
|
|
88
|
+
session = sftp;
|
|
38
89
|
sftp.writeFile(path, contents, mode ? { mode } : undefined, (writeErr) => {
|
|
39
90
|
if (writeErr)
|
|
40
|
-
return
|
|
91
|
+
return settle(writeErr);
|
|
41
92
|
// `mode` on writeFile only applies when the file is created; chmod
|
|
42
93
|
// repairs a file that already existed with looser permissions.
|
|
43
94
|
if (!mode)
|
|
44
|
-
return
|
|
45
|
-
sftp.chmod(path, mode,
|
|
95
|
+
return settle();
|
|
96
|
+
sftp.chmod(path, mode, settle);
|
|
46
97
|
});
|
|
47
98
|
});
|
|
48
99
|
});
|
|
@@ -64,6 +115,9 @@ export async function connectCallsSfuSsh(options, deps = {}) {
|
|
|
64
115
|
port: options.sshPort,
|
|
65
116
|
username: options.sshUser,
|
|
66
117
|
privateKey: options.sshPrivateKey,
|
|
118
|
+
readyTimeout: SSH_READY_TIMEOUT_MS,
|
|
119
|
+
keepaliveInterval: SSH_KEEPALIVE_INTERVAL_MS,
|
|
120
|
+
keepaliveCountMax: SSH_KEEPALIVE_COUNT_MAX,
|
|
67
121
|
// One argument: ssh2 treats the return value as the verdict.
|
|
68
122
|
hostVerifier: (key) => {
|
|
69
123
|
hostKey = key;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export type { OrgId, MailMode, MailConfig, ChatConfig, CallsConfig, IssuerConfig, SiteConfig, IssuerAppKind, IssuerApp, HostingConfig, HostingEntry, EntryGate, HostingEntryUses, EntryAuth, EntryFiles, EntryAi, EnvValue, SsoConfig, GuestsConfig, SoloConfig, StorageConfig, AiConfig, BuyerAddress, BuyerIdentity, AdoptedAppForm, AdoptedApp, OwnerIdentity, OwnerMailbox, DeclaredOrg, } from './org.js';
|
|
2
|
-
export { APP_ENTRY_KEY, DEFAULT_GATE_LIMIT, FILES_ENTRY_PERMISSIONS, AI_ENTRY_PERMISSIONS, DEFAULT_MAIL_APPS, appEntry, ownerMailboxes, validateBuyerIdentity, validateHosting, validateMail, validateOwners, validateSite, } from './org.js';
|
|
1
|
+
export type { OrgId, MailMode, MailConfig, ChatConfig, CallsConfig, IssuerConfig, SiteConfig, IssuerAppKind, IssuerApp, HostingConfig, HostingEntry, EntryGate, HostingEntryUses, EntryAuth, EntryFiles, EntryAi, EnvValue, SsoConfig, GuestsConfig, SoloConfig, StorageConfig, AiConfig, BuyerAddress, BuyerIdentity, AdoptedAppForm, AdoptedApp, OwnerIdentity, OwnerMailbox, DeclaredOrg, OrgServiceSchema, } from './org.js';
|
|
2
|
+
export { APP_ENTRY_KEY, DEFAULT_GATE_LIMIT, FILES_ENTRY_PERMISSIONS, AI_ENTRY_PERMISSIONS, DEFAULT_MAIL_APPS, ORG_SERVICE_SCHEMAS, appEntry, ownerMailboxes, validateBuyerIdentity, validateHosting, validateMail, validateOwners, validateSite, } from './org.js';
|
|
3
3
|
export type { DeclaredPlatform, PlatformService, PlatformServiceKey, PlatformSfuService, } from './platform.js';
|
|
4
4
|
export { PLATFORM_SERVICE_KEYS, defaultPlatformHostname, validatePlatform, } from './platform.js';
|
|
5
5
|
import { type PinnedVersions } from './bootstrap/versions.js';
|
|
@@ -18,7 +18,7 @@ export { createPostgresAppliedVersionsStore, createPostgresDeclaredOrgStore, cre
|
|
|
18
18
|
export { createPostgresAuditLog } from './store/audit.js';
|
|
19
19
|
export type { FoundryStoreDb } from './store/postgres.js';
|
|
20
20
|
export { declaredOrgs, foundryAppliedState, foundryAppliedVersions, foundryAuditEvents, foundryDeclaredPlatform, foundryRequestedChanges, type AuditEventRow, type DeclaredOrgRow, type NewDeclaredOrgRow, type AppliedStateRow, type NewAppliedStateRow, type AppliedVersionRow, type NewAppliedVersionRow, type DeclaredPlatformRow, type NewDeclaredPlatformRow, type RequestedChangeRow, type NewRequestedChangeRow, } from './store/schema.js';
|
|
21
|
-
export type { LiveResource, ZitadelReader, CoolifyReader, CloudflareReader, FilesReader, AiReader, CmsReader, LiveReaders, PlatformCloudflareReader, PlatformCoolifyReader, PlatformLiveReaders, StepAction, PlannedStep, StepSystem, PlanDetail, PlatformPlanDetail, AdoptCandidate, AdoptInput, AdoptPlan, AdoptSnapshot, AuditActor, AuditLog, AuthzGate, StepExecutor, StepDestroyer, DestroyTarget, StepExecutionContext, PlatformStepExecutor, PlatformStepExecutionContext, FoundryPlanApplyOptions, } from './plan/index.js';
|
|
21
|
+
export type { LiveResource, ZitadelReader, CoolifyReader, CloudflareReader, FilesReader, AiReader, CmsReader, LiveReaders, PlatformCloudflareReader, PlatformCoolifyReader, PlatformLiveReaders, StepAction, PlannedStep, StepSystem, PlanDetail, OrgPostgresPlan, PlatformPlanDetail, AdoptCandidate, AdoptInput, AdoptPlan, AdoptSnapshot, AuditActor, AuditLog, AuthzGate, StepExecutor, StepDestroyer, DestroyTarget, StepExecutionContext, PlatformStepExecutor, PlatformStepExecutionContext, FoundryPlanApplyOptions, } from './plan/index.js';
|
|
22
22
|
export { AdoptRefusedError } from './plan/index.js';
|
|
23
23
|
export { createEdgeAccess, createEdgeDns, edgeDnsRecordOwners, createEdgeRateLimits, createEdgeTunnels, createEdgeWorkers, createEdgeZones, } from './plan/index.js';
|
|
24
24
|
export type { AccessAppSummary, AccessDecision, AccessPolicyRule, AccessPort, DnsPort, EdgePlatformAccess, EdgePlatformDns, EdgePlatformRateLimits, EdgePlatformWorkers, EdgePlatformZones, EdgeZonesOptions, ZonePort, EdgeRateLimitsOptions, RateLimitPort, RateLimitRuleInput, EdgeWorkersOptions, EdgePlatformTunnels, TunnelPort, } from './plan/index.js';
|
|
@@ -33,14 +33,14 @@ export { HttpJobsSchedulerClient } from './plan/index.js';
|
|
|
33
33
|
export type { HealthTarget, HealthTargetDepth, HealthTargetExpect } from './plan/index.js';
|
|
34
34
|
export { healthTargets, platformHealthTargets } from './plan/index.js';
|
|
35
35
|
export type { ScopeResolver, RecordedIds, DnsRecordOwners } from './plan/index.js';
|
|
36
|
-
export { ProvidersAiReader, ProvidersCloudflareReader, ProvidersCmsReader, ProvidersCoolifyReader, ProvidersZitadelReader, storeScopeResolver, storeRecordedIds, } from './plan/index.js';
|
|
36
|
+
export { ProvidersAiReader, ProvidersCloudflareReader, ProvidersCmsReader, ProvidersCoolifyReader, ProvidersZitadelReader, storeLoginHostResolver, storeScopeResolver, storeRecordedIds, } from './plan/index.js';
|
|
37
37
|
export type { ZitadelListAppsRawApp } from './plan/index.js';
|
|
38
38
|
export { adaptZitadelListApps } from './plan/index.js';
|
|
39
39
|
export type { MailIdentityApp, MailIdentityAudienceCheck } from './plan/index.js';
|
|
40
40
|
export { checkMailIdentityAudience } from './plan/index.js';
|
|
41
41
|
export type { MailAllowlistApp } from './plan/index.js';
|
|
42
42
|
export { checkMailAppsAllowlist } from './plan/index.js';
|
|
43
|
-
export { FoundryPlatformStepExecutor, ProvidersPlatformCloudflareReader, ProvidersPlatformCoolifyReader, createZitadelAdminMinter, createZitadelProjectRoles, PLATFORM_AUTH_KEY, PLATFORM_MAIL_KEY, PLATFORM_MAIL_ADMIN_KEY_DESCRIPTION, } from './plan/index.js';
|
|
43
|
+
export { FoundryPlatformStepExecutor, ProvidersPlatformCloudflareReader, ProvidersPlatformCoolifyReader, createZitadelAdminMinter, createZitadelLoginPolicy, createZitadelProjectRoles, PLATFORM_AUTH_KEY, PLATFORM_MAIL_KEY, PLATFORM_MAIL_ADMIN_KEY_DESCRIPTION, } from './plan/index.js';
|
|
44
44
|
export type { PlatformAuthDeps, PlatformAuthSmtp, PlatformAuthRuntime, PlatformFilesDeps, PlatformFilesRuntime, PlatformEdgeDeps, PlatformDnsFactory, PlatformAccessFactory, PlatformUptimeAccessFactory, PlatformUptimeAccessPort, PlatformUptimeDeps, PlatformWorkersFactory, PlatformWorkersPort, PlatformAccessPort, PlatformTunnelsFactory, PlatformEdgeRuntime, } from './plan/index.js';
|
|
45
45
|
export type { PlatformMailDeps, PlatformMailAdminPort, PlatformMailRuntime, } from './plan/index.js';
|
|
46
46
|
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,6 +60,8 @@ export { MAIL_COPY_COMPLETED_SCOPE_KEY, MAIL_COPY_DONE_SCOPE_KEY, MissingMailCop
|
|
|
60
60
|
export { migrationsDir as MIGRATIONS_DIR } from './migrations-dir.js';
|
|
61
61
|
export type { BootstrapCompany, BootstrapExecutorDeps, BootstrapOwner, BootstrapPlatformDeps, BootstrapPlatformInput, BootstrapProgress, BootstrapResult, RootCredentials, } from './bootstrap/index.js';
|
|
62
62
|
export { bootstrapPlatform } from './bootstrap/index.js';
|
|
63
|
+
export type { OrgAppVersionRef, OrgAppVersionStatus, WtfalchPackageDrift, PlatformVersionStatus, ReadPlatformVersionStatusOptions, StatusCoolifyPort, StatusGithubPort, StatusGhExec, } from './plan/index.js';
|
|
64
|
+
export { readPlatformVersionStatus, wtfalchVersionsFromPnpmLockfile, providersStatusCoolifyPort, ghCliStatusGithubPort, } from './plan/index.js';
|
|
63
65
|
export type { RunnerDb } from './runner/db.js';
|
|
64
66
|
export { AlreadyRunningError, RunNotFoundError, StepFailedError, StepMismatchError, } from './runner/errors.js';
|
|
65
67
|
export { createJobRunner } from './runner/runner.js';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { APP_ENTRY_KEY, DEFAULT_GATE_LIMIT, FILES_ENTRY_PERMISSIONS, AI_ENTRY_PERMISSIONS, DEFAULT_MAIL_APPS, appEntry, ownerMailboxes, validateBuyerIdentity, validateHosting, validateMail, validateOwners, validateSite, } from './org.js';
|
|
1
|
+
export { APP_ENTRY_KEY, DEFAULT_GATE_LIMIT, FILES_ENTRY_PERMISSIONS, AI_ENTRY_PERMISSIONS, DEFAULT_MAIL_APPS, ORG_SERVICE_SCHEMAS, appEntry, ownerMailboxes, validateBuyerIdentity, validateHosting, validateMail, validateOwners, validateSite, } from './org.js';
|
|
2
2
|
export { PLATFORM_SERVICE_KEYS, defaultPlatformHostname, validatePlatform, } from './platform.js';
|
|
3
3
|
// archon#273: the pinned platform service versions the planner deploys, so a
|
|
4
4
|
// host can show declared against running. Readonly: it is the planner's own
|
|
@@ -19,11 +19,11 @@ export { PlatformOrgBlockedError, PlatformOrgPorts } from './plan/index.js';
|
|
|
19
19
|
export { FoundryStepExecutor, NotBuiltError, healthUrls, loginHostnameFor } from './plan/index.js';
|
|
20
20
|
export { HttpJobsSchedulerClient } from './plan/index.js';
|
|
21
21
|
export { healthTargets, platformHealthTargets } from './plan/index.js';
|
|
22
|
-
export { ProvidersAiReader, ProvidersCloudflareReader, ProvidersCmsReader, ProvidersCoolifyReader, ProvidersZitadelReader, storeScopeResolver, storeRecordedIds, } from './plan/index.js';
|
|
22
|
+
export { ProvidersAiReader, ProvidersCloudflareReader, ProvidersCmsReader, ProvidersCoolifyReader, ProvidersZitadelReader, storeLoginHostResolver, storeScopeResolver, storeRecordedIds, } from './plan/index.js';
|
|
23
23
|
export { adaptZitadelListApps } from './plan/index.js';
|
|
24
24
|
export { checkMailIdentityAudience } from './plan/index.js';
|
|
25
25
|
export { checkMailAppsAllowlist } from './plan/index.js';
|
|
26
|
-
export { FoundryPlatformStepExecutor, ProvidersPlatformCloudflareReader, ProvidersPlatformCoolifyReader, createZitadelAdminMinter, createZitadelProjectRoles, PLATFORM_AUTH_KEY, PLATFORM_MAIL_KEY, PLATFORM_MAIL_ADMIN_KEY_DESCRIPTION, } from './plan/index.js';
|
|
26
|
+
export { FoundryPlatformStepExecutor, ProvidersPlatformCloudflareReader, ProvidersPlatformCoolifyReader, createZitadelAdminMinter, createZitadelLoginPolicy, createZitadelProjectRoles, PLATFORM_AUTH_KEY, PLATFORM_MAIL_KEY, PLATFORM_MAIL_ADMIN_KEY_DESCRIPTION, } from './plan/index.js';
|
|
27
27
|
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';
|
|
28
28
|
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';
|
|
29
29
|
export { MissingCallsSfuConnectionError, applyCallsSfuPlatformStep, callsSfuAppliedFingerprint, } from './plan/index.js';
|
|
@@ -36,5 +36,6 @@ export { MAIL_COPY_COMPLETED_SCOPE_KEY, MAIL_COPY_DONE_SCOPE_KEY, MissingMailCop
|
|
|
36
36
|
// imports nothing else.
|
|
37
37
|
export { migrationsDir as MIGRATIONS_DIR } from './migrations-dir.js';
|
|
38
38
|
export { bootstrapPlatform } from './bootstrap/index.js';
|
|
39
|
+
export { readPlatformVersionStatus, wtfalchVersionsFromPnpmLockfile, providersStatusCoolifyPort, ghCliStatusGithubPort, } from './plan/index.js';
|
|
39
40
|
export { AlreadyRunningError, RunNotFoundError, StepFailedError, StepMismatchError, } from './runner/errors.js';
|
|
40
41
|
export { createJobRunner } from './runner/runner.js';
|
package/dist/org.d.ts
CHANGED
|
@@ -843,3 +843,91 @@ export interface DeclaredOrg {
|
|
|
843
843
|
*/
|
|
844
844
|
test: boolean;
|
|
845
845
|
}
|
|
846
|
+
/**
|
|
847
|
+
* The one Postgres server an org's own service schemas and roles live on
|
|
848
|
+
* (#444) — the settled replacement for today's one-container-per-service
|
|
849
|
+
* model: `plan/executor.ts`'s `coolifyDatabase` mints Archon's/lokessmie's
|
|
850
|
+
* own database, `ensureCmsDatabase` mints CMS's, and `ensureCallsDatabase`
|
|
851
|
+
* mints Boule's and chat's and calls' own Synapse, matrix-authentication-
|
|
852
|
+
* service and service databases, each as its own Coolify Postgres resource
|
|
853
|
+
* today. Not a field on {@link DeclaredOrg}: an operator never picks a
|
|
854
|
+
* server, the same way nobody picks an invited owner's ZITADEL user id
|
|
855
|
+
* (`plan/diff.ts`'s `planOwners`, idempotent on a *recorded* resource id
|
|
856
|
+
* rather than a declared field) — there is exactly one per org, and this is
|
|
857
|
+
* the single flat key it is recorded under once `plan/executor.ts`
|
|
858
|
+
* provisions it (444b). 444a (this slice) only ever reads this key through
|
|
859
|
+
* `recordedIds`; nothing here writes it.
|
|
860
|
+
*/
|
|
861
|
+
export declare const ORG_POSTGRES_SERVER_SCOPE_KEY = "org_postgres_server";
|
|
862
|
+
/**
|
|
863
|
+
* One service's own slice of the org's shared Postgres server (#444): a
|
|
864
|
+
* dedicated schema, an owner role that runs its migrations and owns every
|
|
865
|
+
* object inside it, and a restricted runtime role the service's own app
|
|
866
|
+
* connects as day to day and cannot create or drop anything with. The `_rt`
|
|
867
|
+
* suffix on {@link OrgServiceSchema.runtimeRole} is the one this estate
|
|
868
|
+
* already uses for exactly this role, everywhere a service splits an owner
|
|
869
|
+
* connection from a runtime one — every `package-template`-descended repo's
|
|
870
|
+
* own `CLAUDE.md` names it "the runtime role `<database>_rt`" — just scoped
|
|
871
|
+
* to a schema instead of a whole database, now that several schemas share
|
|
872
|
+
* one server. `scopeKey` is where `plan/diff.ts` and, later, 444b's
|
|
873
|
+
* `plan/executor.ts` record the schema's existence, the same derived-lookup
|
|
874
|
+
* shape {@link OwnerMailbox.scopeKey} already uses: nothing here is itself
|
|
875
|
+
* stored, {@link ORG_SERVICE_SCHEMAS} is rebuilt fresh every time it is read.
|
|
876
|
+
*
|
|
877
|
+
* Every field below is the **target** name 444b provisions under, not what
|
|
878
|
+
* is live today: today there is no schema at all (each service gets its own
|
|
879
|
+
* whole database), its "owner" is really whatever superuser Coolify's
|
|
880
|
+
* database-creation call happened to name, and a database's role is scoped
|
|
881
|
+
* to that whole database, never to a schema within it. The live runtime
|
|
882
|
+
* role names already in production do not all match {@link runtimeRole}'s
|
|
883
|
+
* `<schema>_rt` shape either — CMS's does (`cms_rt`), but chat's own service
|
|
884
|
+
* connects as `chat_rt`, Boule as `<slug>_boule_db_rt`, and calls' own
|
|
885
|
+
* service as `<dbName>_rt`. 444b must not rename a role a password was
|
|
886
|
+
* already minted for; it maps each of these live names onto the matching
|
|
887
|
+
* target below rather than provisioning a second, differently-named role
|
|
888
|
+
* beside it.
|
|
889
|
+
*/
|
|
890
|
+
export interface OrgServiceSchema {
|
|
891
|
+
/** The service this schema belongs to, for a plan step's or an error's own wording — never part of an identifier. */
|
|
892
|
+
service: string;
|
|
893
|
+
/** The schema's target name on the org's one Postgres server. Unique by construction: {@link ORG_SERVICE_SCHEMAS} names each service's schema exactly once. */
|
|
894
|
+
schema: string;
|
|
895
|
+
/** The target role that owns {@link schema} and runs its migrations — this estate's `DATABASE_URL_OWNER` connection, scoped to one schema rather than a whole database. No live role matches this name yet (see this interface's own doc comment). */
|
|
896
|
+
ownerRole: string;
|
|
897
|
+
/** The target restricted role the service's own app connects as day to day — this estate's `DATABASE_URL` connection. Several services' live runtime roles already carry this exact name; others 444b must map onto it (see this interface's own doc comment). */
|
|
898
|
+
runtimeRole: string;
|
|
899
|
+
/** Where this schema's existence is recorded, once provisioned. */
|
|
900
|
+
scopeKey: string;
|
|
901
|
+
}
|
|
902
|
+
/**
|
|
903
|
+
* The full, fixed schema topology every org's shared Postgres server
|
|
904
|
+
* settles into (#444's acceptance: "the documented service schema/role
|
|
905
|
+
* descriptors") — not a per-org choice, so unlike {@link ownerMailboxes}
|
|
906
|
+
* this takes no `DeclaredOrg` at all. One entry per Postgres database
|
|
907
|
+
* `plan/executor.ts` mints today as its own container, confirmed by reading
|
|
908
|
+
* it rather than assumed from the issue or any triage comment: Boule's own
|
|
909
|
+
* (`coolifyBoule`'s `ensureCallsDatabase` call) and CMS's own
|
|
910
|
+
* (`ensureCmsDatabase`) are each one; chat's and calls' own Matrix stacks
|
|
911
|
+
* are each three — `coolifyChat`/`coolifyCalls` each mint a Synapse and a
|
|
912
|
+
* matrix-authentication-service database, and `coolifyChat`/
|
|
913
|
+
* `coolifyCallsService` each mint a third for the service app beside them
|
|
914
|
+
* — eight; plus the org's own app database, `coolifyDatabase`'s `<slug>-db`,
|
|
915
|
+
* for the one hosting entry that declares `database: true` (Archon's or
|
|
916
|
+
* lokessmie's own data) — nine in total.
|
|
917
|
+
*
|
|
918
|
+
* Boule's own entry is provisioned unconditionally — `validateHosting`
|
|
919
|
+
* already refuses an `issuerApps` entry named `boule` because "every org
|
|
920
|
+
* gets" one — so it, and this constant as a whole, name the *full* topology
|
|
921
|
+
* regardless of which feature switches are on, the same way `ORG_ENTRY_KEY`
|
|
922
|
+
* names `app` whether or not an org has one. Which of these schemas
|
|
923
|
+
* `plan/diff.ts`'s `planOrgPostgres` actually reports as missing on a given
|
|
924
|
+
* org is a narrower question than what this list contains — CMS's,
|
|
925
|
+
* chat's and calls' entries sit alongside their own feature's on/off switch
|
|
926
|
+
* ({@link SiteConfig.enabled}, {@link ChatConfig.enabled},
|
|
927
|
+
* {@link CallsConfig.enabled}) and the app entry beside whether any hosting
|
|
928
|
+
* entry declares `database: true`; `planOrgPostgres`'s own doc comment says
|
|
929
|
+
* which. A schema nothing needs yet simply is not reported missing, the
|
|
930
|
+
* same way an org with no app entry (foundry#447) has no app schema to
|
|
931
|
+
* create.
|
|
932
|
+
*/
|
|
933
|
+
export declare const ORG_SERVICE_SCHEMAS: readonly OrgServiceSchema[];
|
package/dist/org.js
CHANGED
|
@@ -930,3 +930,71 @@ export function validateMail(org, opts) {
|
|
|
930
930
|
}
|
|
931
931
|
return null;
|
|
932
932
|
}
|
|
933
|
+
/**
|
|
934
|
+
* The one Postgres server an org's own service schemas and roles live on
|
|
935
|
+
* (#444) — the settled replacement for today's one-container-per-service
|
|
936
|
+
* model: `plan/executor.ts`'s `coolifyDatabase` mints Archon's/lokessmie's
|
|
937
|
+
* own database, `ensureCmsDatabase` mints CMS's, and `ensureCallsDatabase`
|
|
938
|
+
* mints Boule's and chat's and calls' own Synapse, matrix-authentication-
|
|
939
|
+
* service and service databases, each as its own Coolify Postgres resource
|
|
940
|
+
* today. Not a field on {@link DeclaredOrg}: an operator never picks a
|
|
941
|
+
* server, the same way nobody picks an invited owner's ZITADEL user id
|
|
942
|
+
* (`plan/diff.ts`'s `planOwners`, idempotent on a *recorded* resource id
|
|
943
|
+
* rather than a declared field) — there is exactly one per org, and this is
|
|
944
|
+
* the single flat key it is recorded under once `plan/executor.ts`
|
|
945
|
+
* provisions it (444b). 444a (this slice) only ever reads this key through
|
|
946
|
+
* `recordedIds`; nothing here writes it.
|
|
947
|
+
*/
|
|
948
|
+
export const ORG_POSTGRES_SERVER_SCOPE_KEY = 'org_postgres_server';
|
|
949
|
+
/** Builds one {@link OrgServiceSchema}: its owner role, runtime role and scope key all follow from {@link schema} alone, so every caller gets the same shape. */
|
|
950
|
+
function orgServiceSchema(service, schema) {
|
|
951
|
+
return {
|
|
952
|
+
service,
|
|
953
|
+
schema,
|
|
954
|
+
ownerRole: `${schema}_owner`,
|
|
955
|
+
runtimeRole: `${schema}_rt`,
|
|
956
|
+
scopeKey: `org_postgres_schema.${schema}`,
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* The full, fixed schema topology every org's shared Postgres server
|
|
961
|
+
* settles into (#444's acceptance: "the documented service schema/role
|
|
962
|
+
* descriptors") — not a per-org choice, so unlike {@link ownerMailboxes}
|
|
963
|
+
* this takes no `DeclaredOrg` at all. One entry per Postgres database
|
|
964
|
+
* `plan/executor.ts` mints today as its own container, confirmed by reading
|
|
965
|
+
* it rather than assumed from the issue or any triage comment: Boule's own
|
|
966
|
+
* (`coolifyBoule`'s `ensureCallsDatabase` call) and CMS's own
|
|
967
|
+
* (`ensureCmsDatabase`) are each one; chat's and calls' own Matrix stacks
|
|
968
|
+
* are each three — `coolifyChat`/`coolifyCalls` each mint a Synapse and a
|
|
969
|
+
* matrix-authentication-service database, and `coolifyChat`/
|
|
970
|
+
* `coolifyCallsService` each mint a third for the service app beside them
|
|
971
|
+
* — eight; plus the org's own app database, `coolifyDatabase`'s `<slug>-db`,
|
|
972
|
+
* for the one hosting entry that declares `database: true` (Archon's or
|
|
973
|
+
* lokessmie's own data) — nine in total.
|
|
974
|
+
*
|
|
975
|
+
* Boule's own entry is provisioned unconditionally — `validateHosting`
|
|
976
|
+
* already refuses an `issuerApps` entry named `boule` because "every org
|
|
977
|
+
* gets" one — so it, and this constant as a whole, name the *full* topology
|
|
978
|
+
* regardless of which feature switches are on, the same way `ORG_ENTRY_KEY`
|
|
979
|
+
* names `app` whether or not an org has one. Which of these schemas
|
|
980
|
+
* `plan/diff.ts`'s `planOrgPostgres` actually reports as missing on a given
|
|
981
|
+
* org is a narrower question than what this list contains — CMS's,
|
|
982
|
+
* chat's and calls' entries sit alongside their own feature's on/off switch
|
|
983
|
+
* ({@link SiteConfig.enabled}, {@link ChatConfig.enabled},
|
|
984
|
+
* {@link CallsConfig.enabled}) and the app entry beside whether any hosting
|
|
985
|
+
* entry declares `database: true`; `planOrgPostgres`'s own doc comment says
|
|
986
|
+
* which. A schema nothing needs yet simply is not reported missing, the
|
|
987
|
+
* same way an org with no app entry (foundry#447) has no app schema to
|
|
988
|
+
* create.
|
|
989
|
+
*/
|
|
990
|
+
export const ORG_SERVICE_SCHEMAS = [
|
|
991
|
+
orgServiceSchema('Boule', 'boule'),
|
|
992
|
+
orgServiceSchema('CMS', 'cms'),
|
|
993
|
+
orgServiceSchema("chat's Synapse homeserver", 'chat_synapse'),
|
|
994
|
+
orgServiceSchema("chat's matrix-authentication-service", 'chat_mas'),
|
|
995
|
+
orgServiceSchema("chat's own service", 'chat_service'),
|
|
996
|
+
orgServiceSchema("calls' Synapse homeserver", 'calls_synapse'),
|
|
997
|
+
orgServiceSchema("calls' matrix-authentication-service", 'calls_mas'),
|
|
998
|
+
orgServiceSchema("calls' own service", 'calls_service'),
|
|
999
|
+
orgServiceSchema("the org's own app", 'app'),
|
|
1000
|
+
];
|
package/dist/plan/agora.d.ts
CHANGED
|
@@ -167,6 +167,19 @@ export declare const CALLS_INTROSPECTION_UNDELIVERED_SCOPE_KEY = "zitadel_calls_
|
|
|
167
167
|
*/
|
|
168
168
|
export declare const CHAT_INTROSPECTION_SCOPE_KEY = "zitadel_chat_introspection_app";
|
|
169
169
|
export declare const CHAT_INTROSPECTION_UNDELIVERED_SCOPE_KEY = "zitadel_chat_introspection_undelivered";
|
|
170
|
+
/**
|
|
171
|
+
* The one internal token per org that lets the calls service ask chat who is
|
|
172
|
+
* in a room: `CHAT_INTERNAL_TOKEN` on chat-service, `CALLS_CHAT_TOKEN` on the
|
|
173
|
+
* calls service (platform rule 1). The first holds the token's id once both
|
|
174
|
+
* apps carry it; the second is recorded and persisted before either does.
|
|
175
|
+
* The third is `<id> <chat address>`, recorded once both apps run with it. No
|
|
176
|
+
* `:` in any.
|
|
177
|
+
*/
|
|
178
|
+
export declare const CHAT_CALLS_TOKEN_SCOPE_KEY = "coolify_chat_calls_token";
|
|
179
|
+
export declare const CHAT_CALLS_TOKEN_UNDELIVERED_SCOPE_KEY = "coolify_chat_calls_token_undelivered";
|
|
180
|
+
export declare const CHAT_CALLS_APPLIED_SCOPE_KEY = "coolify_chat_calls_applied";
|
|
181
|
+
/** The value {@link CHAT_CALLS_APPLIED_SCOPE_KEY} holds once the token and address are live on both apps. */
|
|
182
|
+
export declare function chatCallsApplied(tokenId: string, chatUrl: string): string;
|
|
170
183
|
/** The chat service's introspection API app name; one per org, exact match only. */
|
|
171
184
|
export declare function chatIntrospectionAppName(org: DeclaredOrg): string;
|
|
172
185
|
/** What the plan hands the calls service step: the two host values the token env needs. */
|
package/dist/plan/agora.js
CHANGED
|
@@ -179,6 +179,21 @@ export const CALLS_INTROSPECTION_UNDELIVERED_SCOPE_KEY = 'zitadel_calls_introspe
|
|
|
179
179
|
*/
|
|
180
180
|
export const CHAT_INTROSPECTION_SCOPE_KEY = 'zitadel_chat_introspection_app';
|
|
181
181
|
export const CHAT_INTROSPECTION_UNDELIVERED_SCOPE_KEY = 'zitadel_chat_introspection_undelivered';
|
|
182
|
+
/**
|
|
183
|
+
* The one internal token per org that lets the calls service ask chat who is
|
|
184
|
+
* in a room: `CHAT_INTERNAL_TOKEN` on chat-service, `CALLS_CHAT_TOKEN` on the
|
|
185
|
+
* calls service (platform rule 1). The first holds the token's id once both
|
|
186
|
+
* apps carry it; the second is recorded and persisted before either does.
|
|
187
|
+
* The third is `<id> <chat address>`, recorded once both apps run with it. No
|
|
188
|
+
* `:` in any.
|
|
189
|
+
*/
|
|
190
|
+
export const CHAT_CALLS_TOKEN_SCOPE_KEY = 'coolify_chat_calls_token';
|
|
191
|
+
export const CHAT_CALLS_TOKEN_UNDELIVERED_SCOPE_KEY = 'coolify_chat_calls_token_undelivered';
|
|
192
|
+
export const CHAT_CALLS_APPLIED_SCOPE_KEY = 'coolify_chat_calls_applied';
|
|
193
|
+
/** The value {@link CHAT_CALLS_APPLIED_SCOPE_KEY} holds once the token and address are live on both apps. */
|
|
194
|
+
export function chatCallsApplied(tokenId, chatUrl) {
|
|
195
|
+
return `${tokenId} ${chatUrl}`;
|
|
196
|
+
}
|
|
182
197
|
/** The chat service's introspection API app name; one per org, exact match only. */
|
|
183
198
|
export function chatIntrospectionAppName(org) {
|
|
184
199
|
return `chat-introspection-${org.slug}`;
|
|
@@ -47,6 +47,20 @@ export declare function chatUpstreams(orgSlug: string): {
|
|
|
47
47
|
matrix: string;
|
|
48
48
|
mas: string;
|
|
49
49
|
};
|
|
50
|
+
/**
|
|
51
|
+
* Internal names for the org's re-keyed homeserver pair (#463, design note
|
|
52
|
+
* `docs/chat-rekey-design.md` Option 1): a second Synapse and MAS,
|
|
53
|
+
* provisioned beside the original pair under `chatInternalNames` so both
|
|
54
|
+
* ends of the copy are live at once. The chat service and its own database
|
|
55
|
+
* are never re-created — same app, repointed once the copy is done and the
|
|
56
|
+
* original pair is parked.
|
|
57
|
+
*/
|
|
58
|
+
export declare function chatRekeyedInternalNames(orgSlug: string): {
|
|
59
|
+
synapse: string;
|
|
60
|
+
mas: string;
|
|
61
|
+
synapseDb: string;
|
|
62
|
+
masDb: string;
|
|
63
|
+
};
|
|
50
64
|
/**
|
|
51
65
|
* Never resolved by DNS and never federates; the identifier Synapse, MAS and
|
|
52
66
|
* the chat service agree on. A level-1 org's chat lives on this name until a
|
|
@@ -54,13 +68,26 @@ export declare function chatUpstreams(orgSlug: string): {
|
|
|
54
68
|
* not collide with a hostname some org owns.
|
|
55
69
|
*/
|
|
56
70
|
export declare function chatServerName(orgSlug: string): string;
|
|
57
|
-
/**
|
|
58
|
-
|
|
71
|
+
/**
|
|
72
|
+
* The Matrix user the chat service acts as
|
|
73
|
+
* (`chat/apps/chat-service/src/workspace.ts`). `serverName` defaults to the
|
|
74
|
+
* org's level-1 `chatServerName`; a re-key (#463) passes the new one so the
|
|
75
|
+
* gate module's `admin_user` matches the homeserver it is loaded into.
|
|
76
|
+
*/
|
|
77
|
+
export declare function chatAdminUser(orgSlug: string, serverName?: string): string;
|
|
78
|
+
/** A re-key's `server_name` is a lowercase DNS name with at least two labels; anything else would be written into both configs. */
|
|
79
|
+
export declare function assertChatServerName(serverName: string): void;
|
|
59
80
|
export interface ChatSynapseHomeserverInput {
|
|
60
81
|
orgSlug: string;
|
|
61
82
|
db: CallsDbConnection;
|
|
62
83
|
masHost: string;
|
|
63
84
|
masSharedSecret: string;
|
|
85
|
+
/**
|
|
86
|
+
* #463: the `server_name` this homeserver answers to. Defaults to the
|
|
87
|
+
* org's level-1 `chatServerName` so every existing caller is unaffected;
|
|
88
|
+
* a re-key's new Synapse passes the org's own domain instead.
|
|
89
|
+
*/
|
|
90
|
+
serverName?: string;
|
|
64
91
|
}
|
|
65
92
|
/** `homeserver.yaml`: federation off, presence on (stable Synapse features only -- no MSC3026 `busy`, foundry#41), MAS for auth, the chat gate module on. */
|
|
66
93
|
export declare function chatSynapseHomeserverYaml(input: ChatSynapseHomeserverInput): string;
|
|
@@ -76,6 +103,8 @@ export interface ChatMasConfigInput {
|
|
|
76
103
|
encryptionKey: string;
|
|
77
104
|
rsaKeyPem: string;
|
|
78
105
|
ecKeyPem: string;
|
|
106
|
+
/** #463: see {@link ChatSynapseHomeserverInput.serverName} -- the two must agree. */
|
|
107
|
+
serverName?: string;
|
|
79
108
|
}
|
|
80
109
|
/** MAS `config.yaml`: password login and registration off; the chat service is the one client, with admin. */
|
|
81
110
|
export declare function chatMasConfigYaml(input: ChatMasConfigInput): string;
|
package/dist/plan/chat-matrix.js
CHANGED
|
@@ -29,6 +29,22 @@ export function chatUpstreams(orgSlug) {
|
|
|
29
29
|
mas: `http://${names.mas}:${CHAT_MAS_PORT}`,
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Internal names for the org's re-keyed homeserver pair (#463, design note
|
|
34
|
+
* `docs/chat-rekey-design.md` Option 1): a second Synapse and MAS,
|
|
35
|
+
* provisioned beside the original pair under `chatInternalNames` so both
|
|
36
|
+
* ends of the copy are live at once. The chat service and its own database
|
|
37
|
+
* are never re-created — same app, repointed once the copy is done and the
|
|
38
|
+
* original pair is parked.
|
|
39
|
+
*/
|
|
40
|
+
export function chatRekeyedInternalNames(orgSlug) {
|
|
41
|
+
return {
|
|
42
|
+
synapse: `${orgSlug}-chat-synapse-2`,
|
|
43
|
+
mas: `${orgSlug}-chat-mas-2`,
|
|
44
|
+
synapseDb: `${orgSlug}-chat-synapse-2-db`,
|
|
45
|
+
masDb: `${orgSlug}-chat-mas-2-db`,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
32
48
|
/**
|
|
33
49
|
* Never resolved by DNS and never federates; the identifier Synapse, MAS and
|
|
34
50
|
* the chat service agree on. A level-1 org's chat lives on this name until a
|
|
@@ -38,18 +54,33 @@ export function chatUpstreams(orgSlug) {
|
|
|
38
54
|
export function chatServerName(orgSlug) {
|
|
39
55
|
return `chat-${orgSlug}.internal`;
|
|
40
56
|
}
|
|
41
|
-
/**
|
|
42
|
-
|
|
43
|
-
|
|
57
|
+
/**
|
|
58
|
+
* The Matrix user the chat service acts as
|
|
59
|
+
* (`chat/apps/chat-service/src/workspace.ts`). `serverName` defaults to the
|
|
60
|
+
* org's level-1 `chatServerName`; a re-key (#463) passes the new one so the
|
|
61
|
+
* gate module's `admin_user` matches the homeserver it is loaded into.
|
|
62
|
+
*/
|
|
63
|
+
export function chatAdminUser(orgSlug, serverName = chatServerName(orgSlug)) {
|
|
64
|
+
return `@chat-admin:${serverName}`;
|
|
44
65
|
}
|
|
45
66
|
const q = (value) => JSON.stringify(value);
|
|
67
|
+
const SERVER_NAME_PATTERN = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)+$/;
|
|
68
|
+
/** A re-key's `server_name` is a lowercase DNS name with at least two labels; anything else would be written into both configs. */
|
|
69
|
+
export function assertChatServerName(serverName) {
|
|
70
|
+
if (serverName.length > 253 || !SERVER_NAME_PATTERN.test(serverName)) {
|
|
71
|
+
throw new Error('chat server_name must be a lowercase domain name');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
46
74
|
/** `homeserver.yaml`: federation off, presence on (stable Synapse features only -- no MSC3026 `busy`, foundry#41), MAS for auth, the chat gate module on. */
|
|
47
75
|
export function chatSynapseHomeserverYaml(input) {
|
|
76
|
+
const serverName = input.serverName ?? chatServerName(input.orgSlug);
|
|
77
|
+
if (input.serverName !== undefined)
|
|
78
|
+
assertChatServerName(serverName);
|
|
48
79
|
return [
|
|
49
80
|
'# Generated by @wtfalch/foundry (executor.ts, coolify:chat). Do not edit',
|
|
50
81
|
'# by hand; re-run apply instead -- it will not rotate secrets that exist.',
|
|
51
82
|
'',
|
|
52
|
-
`server_name: ${q(
|
|
83
|
+
`server_name: ${q(serverName)}`,
|
|
53
84
|
'pid_file: /data/homeserver.pid',
|
|
54
85
|
'report_stats: false',
|
|
55
86
|
'',
|
|
@@ -106,13 +137,16 @@ export function chatSynapseHomeserverYaml(input) {
|
|
|
106
137
|
'modules:',
|
|
107
138
|
' - module: wtfalch_chat_gate.ChatGate',
|
|
108
139
|
' config:',
|
|
109
|
-
` admin_user: ${q(chatAdminUser(input.orgSlug))}`,
|
|
140
|
+
` admin_user: ${q(chatAdminUser(input.orgSlug, serverName))}`,
|
|
110
141
|
' allow_encrypted_channels: false',
|
|
111
142
|
'',
|
|
112
143
|
].join('\n');
|
|
113
144
|
}
|
|
114
145
|
/** MAS `config.yaml`: password login and registration off; the chat service is the one client, with admin. */
|
|
115
146
|
export function chatMasConfigYaml(input) {
|
|
147
|
+
const serverName = input.serverName ?? chatServerName(input.orgSlug);
|
|
148
|
+
if (input.serverName !== undefined)
|
|
149
|
+
assertChatServerName(serverName);
|
|
116
150
|
const indentKey = (pem, spaces) => pem
|
|
117
151
|
.trimEnd()
|
|
118
152
|
.split('\n')
|
|
@@ -150,7 +184,7 @@ export function chatMasConfigYaml(input) {
|
|
|
150
184
|
'',
|
|
151
185
|
'matrix:',
|
|
152
186
|
' kind: synapse',
|
|
153
|
-
` homeserver: ${q(
|
|
187
|
+
` homeserver: ${q(serverName)}`,
|
|
154
188
|
` endpoint: ${q(`http://${input.synapseHost}:${CHAT_SYNAPSE_PORT}`)}`,
|
|
155
189
|
` secret: ${q(input.sharedSecret)}`,
|
|
156
190
|
'',
|
package/dist/plan/diff.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DeclaredOrg, OrgId } from '../org.js';
|
|
1
|
+
import type { DeclaredOrg, OrgId, OrgServiceSchema } from '../org.js';
|
|
2
2
|
import type { Plan, PlanStep, UnmanagedItem, VersionChangeItem } from '../plan.js';
|
|
3
3
|
import { type AgoraHostReading } from './agora.js';
|
|
4
4
|
import type { LiveResource } from './live.js';
|
|
@@ -64,6 +64,8 @@ export interface PlanDetail {
|
|
|
64
64
|
unmanaged: UnmanagedItem[];
|
|
65
65
|
/** #251: see `VersionChangeItem` (`../plan.ts`). Never folded into `plannedSteps`. */
|
|
66
66
|
versionChanges: VersionChangeItem[];
|
|
67
|
+
/** #444a: see {@link OrgPostgresPlan}. Never folded into `plannedSteps`, for the same reason `versionChanges` above is not. */
|
|
68
|
+
orgPostgres: OrgPostgresPlan;
|
|
67
69
|
}
|
|
68
70
|
/** The `Plan` (#19's public contract) a `PlanDetail` carries. */
|
|
69
71
|
export declare function toPlan(detail: PlanDetail): Plan;
|
|
@@ -110,7 +112,12 @@ export declare function toPlan(detail: PlanDetail): Plan;
|
|
|
110
112
|
* app-creating step below: {@link planLoginHost}, the org's own company
|
|
111
113
|
* login host -- also idempotent on the *recorded* resource id, since
|
|
112
114
|
* `live` has no notion of a ZITADEL trusted domain or a login binding
|
|
113
|
-
* either.
|
|
115
|
+
* either. #444a adds one more fact, in a different shape from every other
|
|
116
|
+
* addition here: {@link planOrgPostgres}, the org's one shared Postgres
|
|
117
|
+
* server plus whichever of `org.ts`'s `ORG_SERVICE_SCHEMAS` (nine today)
|
|
118
|
+
* this org actually runs and has not yet recorded -- idempotent on the
|
|
119
|
+
* *recorded* resource id like {@link planOwners}, but never folded into
|
|
120
|
+
* `plannedSteps`: its own {@link OrgPostgresPlan} field says why. Everything
|
|
114
121
|
* else in `live` that none of these eighteen singletons and however many
|
|
115
122
|
* declared issuer apps claim is unmanaged. The full field-by-field provisioning surface (SSO,
|
|
116
123
|
* guests, calls on the org's own domain, adopted/bespoke apps, the actual
|
|
@@ -128,6 +135,42 @@ export declare function computePlanDetail(org: DeclaredOrg, live: LiveResource[]
|
|
|
128
135
|
* its login host equal to the shared ZITADEL instance's own.
|
|
129
136
|
*/
|
|
130
137
|
export declare function isPlatformOwnOrg(org: DeclaredOrg, platformApex: string | null): boolean;
|
|
138
|
+
/**
|
|
139
|
+
* What {@link planOrgPostgres} reports about the org's one shared Postgres
|
|
140
|
+
* server and its fixed schema topology (#444a, "manifest-and-plan-only"):
|
|
141
|
+
* missing only, nothing applied. Deliberately **not** folded into
|
|
142
|
+
* `plannedSteps` -- the same reason {@link VersionChangeItem} (`../plan.ts`)
|
|
143
|
+
* never is, restated in `computePlanDetail`'s own doc comment below, but
|
|
144
|
+
* sharper here: `plan-apply.ts`'s `runSteps` executes every
|
|
145
|
+
* `detail.plannedSteps` entry through `StepExecutor.execute`, which
|
|
146
|
+
* switches on `` `${system}:${kind}` `` and throws on anything it has no
|
|
147
|
+
* case for. `plan/executor.ts` is untouched this slice (444a's own
|
|
148
|
+
* acceptance line), so a `'coolify:postgres-server'`/`'coolify:postgres-
|
|
149
|
+
* schema'` entry in `plannedSteps` would not sit there harmlessly the way
|
|
150
|
+
* an unapplied field does -- the next real `apply()` would reach it and
|
|
151
|
+
* throw `FoundryStepExecutor has no dispatch for coolify:postgres-server`,
|
|
152
|
+
* for every org, immediately. This field is how 444a reports the same
|
|
153
|
+
* "missing" fact to a caller of `computePlanDetail` directly without that
|
|
154
|
+
* happening; wiring it into the public `Plan` (`toPlan` below) and into
|
|
155
|
+
* real `plannedSteps` entries is 444b's job, once `plan/executor.ts` has a
|
|
156
|
+
* dispatch for them.
|
|
157
|
+
*/
|
|
158
|
+
export interface OrgPostgresPlan {
|
|
159
|
+
/** True until the org's one shared Postgres server is recorded under {@link ORG_POSTGRES_SERVER_SCOPE_KEY}. */
|
|
160
|
+
serverMissing: boolean;
|
|
161
|
+
/**
|
|
162
|
+
* Every `org.ts` {@link ORG_SERVICE_SCHEMAS} entry this org actually runs
|
|
163
|
+
* and has not yet recorded under its own `scopeKey`, in that constant's
|
|
164
|
+
* own order -- see {@link orgServiceSchemaEnabled} for which entries that
|
|
165
|
+
* is. Boule's is always included, the same way `ORG_SERVICE_SCHEMAS`
|
|
166
|
+
* itself always carries it; CMS's, chat's and calls' sit behind their own
|
|
167
|
+
* feature switch, and the app schema behind whether any hosting entry
|
|
168
|
+
* declares a database. A schema this org does not run yet is never
|
|
169
|
+
* reported missing, even though {@link ORG_SERVICE_SCHEMAS} still names
|
|
170
|
+
* it as part of the full topology.
|
|
171
|
+
*/
|
|
172
|
+
missingSchemas: readonly OrgServiceSchema[];
|
|
173
|
+
}
|
|
131
174
|
/** Restated from `executor.ts`, the same rule: the id of an ai key minted but not delivered. */
|
|
132
175
|
export declare const AI_KEY_UNDELIVERED_SCOPE_KEY = "ai_key_undelivered";
|
|
133
176
|
/** Restated from `executor.ts`, the same rule: the id of a jobs project registered but not delivered. */
|