@wtfalch/foundry 0.19.1 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bootstrap/declare.d.ts +13 -0
- package/dist/bootstrap/declare.js +31 -7
- package/dist/bootstrap/versions.d.ts +1 -0
- package/dist/index.d.ts +7 -3
- package/dist/index.js +4 -2
- package/dist/migrations/0046_platform_hc.sql +19 -0
- package/dist/plan/cloudflared-connector.d.ts +3 -2
- package/dist/plan/cloudflared-connector.js +2 -2
- package/dist/plan/deploy-health.js +5 -1
- package/dist/plan/diff.js +27 -4
- package/dist/plan/edge-access.d.ts +68 -0
- package/dist/plan/edge-access.js +123 -0
- package/dist/plan/edge-dns.d.ts +89 -0
- package/dist/plan/edge-dns.js +227 -0
- package/dist/plan/edge-ratelimits.d.ts +54 -0
- package/dist/plan/edge-ratelimits.js +79 -0
- package/dist/plan/edge-tunnels.d.ts +44 -0
- package/dist/plan/edge-tunnels.js +108 -0
- package/dist/plan/edge-workers.d.ts +31 -0
- package/dist/plan/edge-workers.js +165 -0
- package/dist/plan/edge-zones.d.ts +37 -0
- package/dist/plan/edge-zones.js +64 -0
- package/dist/plan/entry-edge-key.d.ts +10 -0
- package/dist/plan/entry-edge-key.js +38 -8
- package/dist/plan/executor.d.ts +146 -143
- package/dist/plan/executor.js +370 -115
- package/dist/plan/health-check-settings.d.ts +47 -0
- package/dist/plan/health-check-settings.js +47 -0
- package/dist/plan/health-targets.d.ts +86 -0
- package/dist/plan/health-targets.js +344 -0
- package/dist/plan/index.d.ts +17 -3
- package/dist/plan/index.js +9 -2
- package/dist/plan/live.d.ts +12 -1
- package/dist/plan/plan-apply.d.ts +4 -0
- package/dist/plan/plan-apply.js +5 -1
- package/dist/plan/platform-diff.d.ts +127 -0
- package/dist/plan/platform-diff.js +304 -8
- package/dist/plan/platform-providers.d.ts +202 -7
- package/dist/plan/platform-providers.js +545 -79
- package/dist/plan/providers-readers.d.ts +23 -1
- package/dist/plan/providers-readers.js +61 -1
- package/dist/plan/rollout.d.ts +9 -8
- package/dist/platform.d.ts +14 -1
- package/dist/platform.js +1 -0
- package/dist/store/postgres.js +8 -0
- package/dist/store/schema.d.ts +51 -0
- package/dist/store/schema.js +4 -0
- package/package.json +2 -1
- package/versions.json +2 -1
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #543: the one shared helper for the Coolify `updateApplication` quartet
|
|
3
|
+
* that turns an app's own container health check on -- lifted out of
|
|
4
|
+
* `platform-providers.ts`'s `provisionPlatformArchon` (#480), which sent
|
|
5
|
+
* these four fields by hand for Archon alone:
|
|
6
|
+
*
|
|
7
|
+
* ```
|
|
8
|
+
* health_check_enabled: true,
|
|
9
|
+
* health_check_path: ARCHON_HEALTH_CHECK_PATH,
|
|
10
|
+
* health_check_port: Number(ARCHON_PORT),
|
|
11
|
+
* health_check_start_period: ARCHON_HEALTH_CHECK_START_PERIOD_SECONDS,
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* Archon's own doc comment explains why this exists at all: without a health
|
|
15
|
+
* check, Coolify's rolling update has nothing to wait on and stops the old
|
|
16
|
+
* container the moment the new one starts, before it can possibly be
|
|
17
|
+
* healthy -- the #475 incident's own failure mode, and the 2026-10-05 Archon
|
|
18
|
+
* 502 this issue (#543) was opened over. Every other app foundry creates
|
|
19
|
+
* that answers an HTTP health route gets the same four fields, through this
|
|
20
|
+
* one function, so a change to the shape (a fifth field, a different
|
|
21
|
+
* default) reaches every caller instead of drifting between Archon's copy
|
|
22
|
+
* and whichever app copied it next.
|
|
23
|
+
*
|
|
24
|
+
* `startPeriodSeconds` defaults to 30 -- Archon's own number
|
|
25
|
+
* (`ARCHON_HEALTH_CHECK_START_PERIOD_SECONDS`'s doc comment: "long enough
|
|
26
|
+
* for the slowest migration run plus boot seen so far, with margin; there is
|
|
27
|
+
* no live measurement to pin it to instead"). A caller whose app is known to
|
|
28
|
+
* be slower or faster to boot passes its own.
|
|
29
|
+
*/
|
|
30
|
+
/** The four Coolify `updateApplication` fields {@link healthCheckSettings} returns. */
|
|
31
|
+
export interface HealthCheckPatch {
|
|
32
|
+
health_check_enabled: true;
|
|
33
|
+
health_check_path: string;
|
|
34
|
+
health_check_port: number;
|
|
35
|
+
health_check_start_period: number;
|
|
36
|
+
}
|
|
37
|
+
/** Archon's own number (see this module's doc comment) -- the default every other caller inherits unless it knows better. */
|
|
38
|
+
export declare const DEFAULT_HEALTH_CHECK_START_PERIOD_SECONDS = 30;
|
|
39
|
+
/**
|
|
40
|
+
* The quartet of `updateApplication` fields that turn on and configure an
|
|
41
|
+
* app's own Coolify health check, spread into whatever patch object a caller
|
|
42
|
+
* is already building (`{ ...patch, ...healthCheckSettings(path, port) }`).
|
|
43
|
+
* `port` is the port Coolify's health check probes *inside the container's
|
|
44
|
+
* own network* -- the same port the app's `ports_exposes` (or its
|
|
45
|
+
* Dockerfile's `EXPOSE`) names, not a public one.
|
|
46
|
+
*/
|
|
47
|
+
export declare function healthCheckSettings(path: string, port: number, startPeriodSeconds?: number): HealthCheckPatch;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #543: the one shared helper for the Coolify `updateApplication` quartet
|
|
3
|
+
* that turns an app's own container health check on -- lifted out of
|
|
4
|
+
* `platform-providers.ts`'s `provisionPlatformArchon` (#480), which sent
|
|
5
|
+
* these four fields by hand for Archon alone:
|
|
6
|
+
*
|
|
7
|
+
* ```
|
|
8
|
+
* health_check_enabled: true,
|
|
9
|
+
* health_check_path: ARCHON_HEALTH_CHECK_PATH,
|
|
10
|
+
* health_check_port: Number(ARCHON_PORT),
|
|
11
|
+
* health_check_start_period: ARCHON_HEALTH_CHECK_START_PERIOD_SECONDS,
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* Archon's own doc comment explains why this exists at all: without a health
|
|
15
|
+
* check, Coolify's rolling update has nothing to wait on and stops the old
|
|
16
|
+
* container the moment the new one starts, before it can possibly be
|
|
17
|
+
* healthy -- the #475 incident's own failure mode, and the 2026-10-05 Archon
|
|
18
|
+
* 502 this issue (#543) was opened over. Every other app foundry creates
|
|
19
|
+
* that answers an HTTP health route gets the same four fields, through this
|
|
20
|
+
* one function, so a change to the shape (a fifth field, a different
|
|
21
|
+
* default) reaches every caller instead of drifting between Archon's copy
|
|
22
|
+
* and whichever app copied it next.
|
|
23
|
+
*
|
|
24
|
+
* `startPeriodSeconds` defaults to 30 -- Archon's own number
|
|
25
|
+
* (`ARCHON_HEALTH_CHECK_START_PERIOD_SECONDS`'s doc comment: "long enough
|
|
26
|
+
* for the slowest migration run plus boot seen so far, with margin; there is
|
|
27
|
+
* no live measurement to pin it to instead"). A caller whose app is known to
|
|
28
|
+
* be slower or faster to boot passes its own.
|
|
29
|
+
*/
|
|
30
|
+
/** Archon's own number (see this module's doc comment) -- the default every other caller inherits unless it knows better. */
|
|
31
|
+
export const DEFAULT_HEALTH_CHECK_START_PERIOD_SECONDS = 30;
|
|
32
|
+
/**
|
|
33
|
+
* The quartet of `updateApplication` fields that turn on and configure an
|
|
34
|
+
* app's own Coolify health check, spread into whatever patch object a caller
|
|
35
|
+
* is already building (`{ ...patch, ...healthCheckSettings(path, port) }`).
|
|
36
|
+
* `port` is the port Coolify's health check probes *inside the container's
|
|
37
|
+
* own network* -- the same port the app's `ports_exposes` (or its
|
|
38
|
+
* Dockerfile's `EXPOSE`) names, not a public one.
|
|
39
|
+
*/
|
|
40
|
+
export function healthCheckSettings(path, port, startPeriodSeconds = DEFAULT_HEALTH_CHECK_START_PERIOD_SECONDS) {
|
|
41
|
+
return {
|
|
42
|
+
health_check_enabled: true,
|
|
43
|
+
health_check_path: path,
|
|
44
|
+
health_check_port: port,
|
|
45
|
+
health_check_start_period: startPeriodSeconds,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { DeclaredOrg } from '../org.js';
|
|
2
|
+
import type { DeclaredPlatform } from '../platform.js';
|
|
3
|
+
/**
|
|
4
|
+
* #541: every service foundry runs, for an outside uptime prober -- the gap
|
|
5
|
+
* `healthUrls` (`executor.ts`) left open. That function returns one bare URL
|
|
6
|
+
* per hosting/site app and `[]` for a company that runs neither, even though
|
|
7
|
+
* foundry always runs Boule, Agora and sign-in for every org, and mail, chat,
|
|
8
|
+
* calls and the shared sign-in broker whenever that org has them
|
|
9
|
+
* (`health-urls.test.ts` line 59 is the empty list this issue replaces for
|
|
10
|
+
* probing). The platform's own services (ZITADEL, the broker, Archon, mail,
|
|
11
|
+
* ai, files, integrations, edge) had no health list at all.
|
|
12
|
+
*
|
|
13
|
+
* `healthTargets`/`platformHealthTargets` are that list, richer than a bare
|
|
14
|
+
* URL string: `url: null` plus `reason` says "not probed" rather than hiding
|
|
15
|
+
* the service, and `restartSafe` tells a future auto-restart step which
|
|
16
|
+
* failing checks it must never act on by itself.
|
|
17
|
+
*
|
|
18
|
+
* Every hostname below comes from the function that already owns it --
|
|
19
|
+
* `bouleHostnameFor`, `agoraHostnameFor`, `loginHostnameFor`, `cmsHostnameFor`,
|
|
20
|
+
* `callsHostnameFor`, `entryHostnames` -- never a second copy of the rule
|
|
21
|
+
* that builds one, the same discipline `healthUrls`'s own doc comment states
|
|
22
|
+
* ("Exported so archon ... never repeats this file's hostname rules").
|
|
23
|
+
* `cmsHostnameFor`/`callsHostnameFor` were private to `executor.ts`; this
|
|
24
|
+
* issue exports them for that reason alone, and `healthUrls` itself is
|
|
25
|
+
* untouched -- Archon still imports it.
|
|
26
|
+
*
|
|
27
|
+
* The *paths* below are not discovered here -- none of the apps they belong
|
|
28
|
+
* to (auth, chat, calls) are built in this repo -- so they are hand-verified
|
|
29
|
+
* facts (#541's own "Real paths today"), cited beside each one against
|
|
30
|
+
* whatever this repo already knows about the service that owns it.
|
|
31
|
+
* `expect` is `{ status: 200 }` everywhere: nothing in this estate's own
|
|
32
|
+
* wait-for-ready logic (`platform-providers.ts`'s `waitForInstance`/
|
|
33
|
+
* `waitForMailInstance`, both `response.ok` alone, no body read) asserts a
|
|
34
|
+
* response body today, so this catalogue does not invent one either.
|
|
35
|
+
*/
|
|
36
|
+
/** `'deep'` when the route queries its own database, `'shallow'` when it answers without one, `'page'` when only a page load (no API) is possible. */
|
|
37
|
+
export type HealthTargetDepth = 'deep' | 'shallow' | 'page';
|
|
38
|
+
export interface HealthTargetExpect {
|
|
39
|
+
status: number;
|
|
40
|
+
/** Set only for a route this estate's own code already asserts a body against; none do today (see this file's own doc comment). */
|
|
41
|
+
body?: string;
|
|
42
|
+
}
|
|
43
|
+
export interface HealthTarget {
|
|
44
|
+
/**
|
|
45
|
+
* A stable id, e.g. `'boule'`, `'agora'`, `'login'`, `'mail'`. Not unique
|
|
46
|
+
* *across* `healthTargets` and `platformHealthTargets` -- the two catalogue
|
|
47
|
+
* different things reached at different hostnames (a company's own
|
|
48
|
+
* `loginHostnameFor` vs. the platform's shared `platform.auth.hostname`),
|
|
49
|
+
* even when the id reads the same.
|
|
50
|
+
*/
|
|
51
|
+
service: string;
|
|
52
|
+
/** `null` when nothing here can be probed from outside -- see `reason`. */
|
|
53
|
+
url: string | null;
|
|
54
|
+
expect: HealthTargetExpect;
|
|
55
|
+
depth: HealthTargetDepth;
|
|
56
|
+
/**
|
|
57
|
+
* False for anything a future "restart on red" step must never touch
|
|
58
|
+
* mid-incident on its own: a database, auth (ZITADEL, zitadel-login, the
|
|
59
|
+
* broker), mail, chat's Synapse and MAS, the calls service, jobs, Archon.
|
|
60
|
+
* Everything else here is a stateless app container, safe to bounce.
|
|
61
|
+
*/
|
|
62
|
+
restartSafe: boolean;
|
|
63
|
+
/** True when Cloudflare Access gates the hostname, so a prober with no service token reads every check as a redirect, never a real answer. */
|
|
64
|
+
behindAccess: boolean;
|
|
65
|
+
/** Set only when `url` is `null`: why nothing can be probed yet. */
|
|
66
|
+
reason?: string;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* One target per service foundry runs for this company (#541). Always Boule,
|
|
70
|
+
* Agora and login; mail, chat (the chat service, Synapse, MAS), calls and
|
|
71
|
+
* the broker whenever the org has them; then whichever of hosting or site
|
|
72
|
+
* the org runs, if either.
|
|
73
|
+
*/
|
|
74
|
+
export declare function healthTargets(org: DeclaredOrg): HealthTarget[];
|
|
75
|
+
/**
|
|
76
|
+
* The seven platform services #541 names (`platform:auth`, `platform:archon`,
|
|
77
|
+
* `platform:mail`, `platform:ai`, `platform:files`, `platform:integrations`,
|
|
78
|
+
* `platform:edge`) -- `platform:sfu` and `platform:jobs` are shared platform
|
|
79
|
+
* resources too, but neither is in that list, and this issue does not expand
|
|
80
|
+
* it: the SFU has no hostname (`platform-sfu.ts`'s own doc comment: no cheap
|
|
81
|
+
* way to read it over SSH, let alone over HTTP) and the jobs scheduler's
|
|
82
|
+
* admin API answers 401 with no token (`platform-providers.ts`'s own
|
|
83
|
+
* `coolify:jobs` step), so an outside prober could not usefully reach either
|
|
84
|
+
* one anyway.
|
|
85
|
+
*/
|
|
86
|
+
export declare function platformHealthTargets(platform: DeclaredPlatform): HealthTarget[];
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import { APP_ENTRY_KEY, appEntry } from '../org.js';
|
|
2
|
+
import { agoraHostnameFor } from './agora.js';
|
|
3
|
+
import { bouleHostnameFor } from './boule.js';
|
|
4
|
+
import { callsHostnameFor, cmsHostnameFor } from './executor.js';
|
|
5
|
+
import { entryHostnames } from './hostnames.js';
|
|
6
|
+
import { loginHostnameFor } from './login-host.js';
|
|
7
|
+
function okExpect() {
|
|
8
|
+
return { status: 200 };
|
|
9
|
+
}
|
|
10
|
+
const DEFAULT_API_HEALTH_PATH = '/api/health';
|
|
11
|
+
const STATIC_PAGE_PATH = '/';
|
|
12
|
+
/** zitadel-login's own readiness route (#541's "Real paths today"). `depth: 'shallow'`, not `'deep'`: login holds no database of its own (`login-host.ts`), whatever its name suggests. */
|
|
13
|
+
const LOGIN_READY_PATH = '/ready';
|
|
14
|
+
/** The broker answers on `platform.auth.hostname` under `AUTH_BROKER_PATH_PATTERN` (`platform-providers.ts`, `'^/api(/|$)'`), so its own health path carries that prefix. */
|
|
15
|
+
const BROKER_HEALTH_PATH = '/api/healthy';
|
|
16
|
+
/** `platform-providers.ts`'s own `waitForInstance` call target (`` `https://${hostname}/debug/healthz}` ``), also the one path `AUTH_ZITADEL_PATH_PATTERN` names by name. */
|
|
17
|
+
const ZITADEL_HEALTH_PATH = '/debug/healthz';
|
|
18
|
+
/**
|
|
19
|
+
* `platform-providers.ts`'s own private `STALWART_READY_PATH` ('/healthz/ready'),
|
|
20
|
+
* restated here: that constant is not exported, and exporting it (or
|
|
21
|
+
* importing the whole provider module for one string) costs more than a
|
|
22
|
+
* second copy of a value both cite back to Stalwart's own route.
|
|
23
|
+
*/
|
|
24
|
+
const STALWART_READY_PATH = '/healthz/ready';
|
|
25
|
+
const CHAT_SERVICE_HEALTH_PATH = '/_chat/health';
|
|
26
|
+
const CALLS_HEALTH_PATH = '/healthz';
|
|
27
|
+
/** Boule, Agora and the company's own login host: every org gets these, with no flag to check (the "Why" this issue opens with). */
|
|
28
|
+
function alwaysOnTargets(org) {
|
|
29
|
+
return [
|
|
30
|
+
{
|
|
31
|
+
service: 'boule',
|
|
32
|
+
url: `https://${bouleHostnameFor(org)}${DEFAULT_API_HEALTH_PATH}`,
|
|
33
|
+
expect: okExpect(),
|
|
34
|
+
depth: 'deep',
|
|
35
|
+
restartSafe: true,
|
|
36
|
+
behindAccess: false,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
// Caddy serves Agora statically (`agora.ts`'s own doc comment: its
|
|
40
|
+
// Dockerfile serves Caddy on 8080) -- no `/api/health` to ask, so only
|
|
41
|
+
// a page load proves it is up ("static Agora /" in #541's own list).
|
|
42
|
+
service: 'agora',
|
|
43
|
+
url: `https://${agoraHostnameFor(org)}${STATIC_PAGE_PATH}`,
|
|
44
|
+
expect: okExpect(),
|
|
45
|
+
depth: 'page',
|
|
46
|
+
restartSafe: true,
|
|
47
|
+
behindAccess: false,
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
service: 'login',
|
|
51
|
+
url: `https://${loginHostnameFor(org)}${LOGIN_READY_PATH}`,
|
|
52
|
+
expect: okExpect(),
|
|
53
|
+
depth: 'shallow',
|
|
54
|
+
restartSafe: false,
|
|
55
|
+
behindAccess: false,
|
|
56
|
+
},
|
|
57
|
+
];
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The shared sign-in broker (`broker-entries.ts`), when this company depends
|
|
61
|
+
* on it: any hosting entry declaring `uses.auth` signs in through it. The
|
|
62
|
+
* broker is the platform's one app, not a per-company address `org` alone
|
|
63
|
+
* can build -- its real, reachable URL is `platformHealthTargets`'s own
|
|
64
|
+
* `'auth-broker'` target, under `platform.auth.hostname`. This target still
|
|
65
|
+
* belongs in a company's own list (#541: "Include ... the broker when the
|
|
66
|
+
* company has them") so a status page scoped to one company shows every
|
|
67
|
+
* service that company's sign-in actually depends on, even a shared one.
|
|
68
|
+
*/
|
|
69
|
+
function brokerTarget(org) {
|
|
70
|
+
const usesBroker = org.hosting.entries.some((entry) => Boolean(entry.uses?.auth));
|
|
71
|
+
if (!usesBroker)
|
|
72
|
+
return null;
|
|
73
|
+
return {
|
|
74
|
+
service: 'broker',
|
|
75
|
+
url: null,
|
|
76
|
+
expect: okExpect(),
|
|
77
|
+
depth: 'shallow',
|
|
78
|
+
restartSafe: false,
|
|
79
|
+
behindAccess: false,
|
|
80
|
+
reason: "the sign-in broker is the platform's one shared app (broker-entries.ts), not a per-company address this function can build from `org` alone -- see platformHealthTargets's 'auth-broker' target",
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Mail, when this company's mail is not off. Every org's mail is served by
|
|
85
|
+
* the one shared Stalwart instance (`executor.ts`'s `mailOrgFor`), never a
|
|
86
|
+
* per-org app, so there is no per-company hostname to build here either --
|
|
87
|
+
* `platformHealthTargets`'s own `'mail'` target is the real, reachable one.
|
|
88
|
+
*/
|
|
89
|
+
function mailTarget(org) {
|
|
90
|
+
if (org.mail.mode === 'off')
|
|
91
|
+
return null;
|
|
92
|
+
return {
|
|
93
|
+
service: 'mail',
|
|
94
|
+
url: null,
|
|
95
|
+
expect: okExpect(),
|
|
96
|
+
depth: 'deep',
|
|
97
|
+
restartSafe: false,
|
|
98
|
+
behindAccess: false,
|
|
99
|
+
reason: "mail is the platform's one shared Stalwart instance, not a per-company address this function can build from `org` alone -- see platformHealthTargets's 'mail' target",
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Chat, when enabled (on by default for a new org -- `org.ts`'s own
|
|
104
|
+
* `ChatConfig` doc comment). The chat service is the only one of the three
|
|
105
|
+
* with a public route of its own, proxied through Agora
|
|
106
|
+
* (`chat-matrix.ts`'s own "NO PUBLIC HOSTNAME OF ITS OWN": Synapse and MAS
|
|
107
|
+
* answer only on Coolify's internal network, never a tunnel or DNS record).
|
|
108
|
+
*/
|
|
109
|
+
function chatTargets(org) {
|
|
110
|
+
if (!org.chat.enabled)
|
|
111
|
+
return [];
|
|
112
|
+
const noPublicHostname = "chat's Synapse and MAS have no public hostname of their own (chat-matrix.ts: 'NO PUBLIC HOSTNAME OF ITS OWN') -- only the chat service's own route through Agora is reachable from outside";
|
|
113
|
+
return [
|
|
114
|
+
{
|
|
115
|
+
service: 'chat',
|
|
116
|
+
url: `https://${agoraHostnameFor(org)}${CHAT_SERVICE_HEALTH_PATH}`,
|
|
117
|
+
expect: okExpect(),
|
|
118
|
+
depth: 'deep',
|
|
119
|
+
restartSafe: true,
|
|
120
|
+
behindAccess: false,
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
service: 'chat-synapse',
|
|
124
|
+
url: null,
|
|
125
|
+
expect: okExpect(),
|
|
126
|
+
depth: 'shallow',
|
|
127
|
+
restartSafe: false,
|
|
128
|
+
behindAccess: false,
|
|
129
|
+
reason: noPublicHostname,
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
service: 'chat-mas',
|
|
133
|
+
url: null,
|
|
134
|
+
expect: okExpect(),
|
|
135
|
+
depth: 'shallow',
|
|
136
|
+
restartSafe: false,
|
|
137
|
+
behindAccess: false,
|
|
138
|
+
reason: noPublicHostname,
|
|
139
|
+
},
|
|
140
|
+
];
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Calls, when enabled. Unlike chat, the calls *service* is the one thing
|
|
144
|
+
* with its own public hostname (`callsHostnameFor`); its own Synapse/MAS are
|
|
145
|
+
* internal-only the same way chat's are (`calls-matrix.ts`'s own "NO PUBLIC
|
|
146
|
+
* HOSTNAME OF ITS OWN"), so this issue does not add separate targets for
|
|
147
|
+
* them -- #541's own "How" lists "calls" as one service here, not three the
|
|
148
|
+
* way chat's is spelled out.
|
|
149
|
+
*/
|
|
150
|
+
function callsTargets(org) {
|
|
151
|
+
if (!org.calls.enabled)
|
|
152
|
+
return [];
|
|
153
|
+
return [
|
|
154
|
+
{
|
|
155
|
+
service: 'calls',
|
|
156
|
+
url: `https://${callsHostnameFor(org)}${CALLS_HEALTH_PATH}`,
|
|
157
|
+
expect: okExpect(),
|
|
158
|
+
depth: 'shallow',
|
|
159
|
+
restartSafe: false,
|
|
160
|
+
behindAccess: false,
|
|
161
|
+
},
|
|
162
|
+
];
|
|
163
|
+
}
|
|
164
|
+
/** One hosting entry's own target, the `healthUrls` fallback rule restated richly rather than copied differently. */
|
|
165
|
+
function hostingEntryTarget(org, entry) {
|
|
166
|
+
const [hostname] = entryHostnames(org, entry);
|
|
167
|
+
if (!hostname) {
|
|
168
|
+
return {
|
|
169
|
+
service: entry.key,
|
|
170
|
+
url: null,
|
|
171
|
+
expect: okExpect(),
|
|
172
|
+
depth: 'shallow',
|
|
173
|
+
restartSafe: true,
|
|
174
|
+
behindAccess: false,
|
|
175
|
+
reason: `the '${entry.key}' entry is internal: it has no public hostname (entryHostnames) to probe`,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
const path = entry.healthPath ?? (entry.buildPack === 'static' ? STATIC_PAGE_PATH : DEFAULT_API_HEALTH_PATH);
|
|
179
|
+
return {
|
|
180
|
+
service: entry.key,
|
|
181
|
+
url: `https://${hostname}${path}`,
|
|
182
|
+
expect: okExpect(),
|
|
183
|
+
depth: entry.buildPack === 'static' ? 'page' : entry.database ? 'deep' : 'shallow',
|
|
184
|
+
restartSafe: true,
|
|
185
|
+
// Only the `app` entry may be gated behind Cloudflare Access at all
|
|
186
|
+
// (`org.ts`'s own `HostingEntry.accessEmails` doc comment).
|
|
187
|
+
behindAccess: entry.key === APP_ENTRY_KEY && entry.accessEmails.length > 0,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* The org's own app: either its hosting entries, or its site + cms pair,
|
|
192
|
+
* never both -- the same `if` / `else if` `healthUrls` uses, restated so the
|
|
193
|
+
* richer shape stays in step with it rather than drifting from a second,
|
|
194
|
+
* differently-written version of the same choice.
|
|
195
|
+
*/
|
|
196
|
+
function appTargets(org) {
|
|
197
|
+
if (org.hosting.enabled && appEntry(org.hosting)) {
|
|
198
|
+
return org.hosting.entries.map((entry) => hostingEntryTarget(org, entry));
|
|
199
|
+
}
|
|
200
|
+
if (org.site.enabled && org.domain) {
|
|
201
|
+
return [
|
|
202
|
+
{
|
|
203
|
+
service: 'site',
|
|
204
|
+
url: `https://${org.domain}${DEFAULT_API_HEALTH_PATH}`,
|
|
205
|
+
expect: okExpect(),
|
|
206
|
+
depth: 'deep',
|
|
207
|
+
restartSafe: true,
|
|
208
|
+
behindAccess: false,
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
service: 'cms',
|
|
212
|
+
url: `https://${cmsHostnameFor(org)}${DEFAULT_API_HEALTH_PATH}`,
|
|
213
|
+
expect: okExpect(),
|
|
214
|
+
depth: 'deep',
|
|
215
|
+
restartSafe: true,
|
|
216
|
+
behindAccess: false,
|
|
217
|
+
},
|
|
218
|
+
];
|
|
219
|
+
}
|
|
220
|
+
return [];
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* One target per service foundry runs for this company (#541). Always Boule,
|
|
224
|
+
* Agora and login; mail, chat (the chat service, Synapse, MAS), calls and
|
|
225
|
+
* the broker whenever the org has them; then whichever of hosting or site
|
|
226
|
+
* the org runs, if either.
|
|
227
|
+
*/
|
|
228
|
+
export function healthTargets(org) {
|
|
229
|
+
const targets = alwaysOnTargets(org);
|
|
230
|
+
const broker = brokerTarget(org);
|
|
231
|
+
if (broker)
|
|
232
|
+
targets.push(broker);
|
|
233
|
+
const mail = mailTarget(org);
|
|
234
|
+
if (mail)
|
|
235
|
+
targets.push(mail);
|
|
236
|
+
targets.push(...chatTargets(org));
|
|
237
|
+
targets.push(...callsTargets(org));
|
|
238
|
+
targets.push(...appTargets(org));
|
|
239
|
+
return targets;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* ZITADEL, zitadel-login and the broker all answer on the one
|
|
243
|
+
* `platform.auth.hostname`, path-routed (`platform-providers.ts`'s own
|
|
244
|
+
* `provisionPlatformAuth`: `AUTH_ZITADEL_PATH_PATTERN` and
|
|
245
|
+
* `AUTH_BROKER_PATH_PATTERN`, with login the catch-all for everything
|
|
246
|
+
* neither matches) -- three services foundry runs under the one
|
|
247
|
+
* `platform:auth` step, so three targets here, not one.
|
|
248
|
+
*/
|
|
249
|
+
function authTargets(platform) {
|
|
250
|
+
const hostname = platform.auth.hostname;
|
|
251
|
+
return [
|
|
252
|
+
{
|
|
253
|
+
service: 'auth-zitadel',
|
|
254
|
+
url: `https://${hostname}${ZITADEL_HEALTH_PATH}`,
|
|
255
|
+
expect: okExpect(),
|
|
256
|
+
depth: 'shallow',
|
|
257
|
+
restartSafe: false,
|
|
258
|
+
behindAccess: false,
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
service: 'auth-login',
|
|
262
|
+
url: `https://${hostname}${LOGIN_READY_PATH}`,
|
|
263
|
+
expect: okExpect(),
|
|
264
|
+
depth: 'shallow',
|
|
265
|
+
restartSafe: false,
|
|
266
|
+
behindAccess: false,
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
service: 'auth-broker',
|
|
270
|
+
url: `https://${hostname}${BROKER_HEALTH_PATH}`,
|
|
271
|
+
expect: okExpect(),
|
|
272
|
+
depth: 'shallow',
|
|
273
|
+
restartSafe: false,
|
|
274
|
+
behindAccess: false,
|
|
275
|
+
},
|
|
276
|
+
];
|
|
277
|
+
}
|
|
278
|
+
/** Why `platform:ai`/`platform:files` have nothing to probe yet: both runners are created with no Coolify `domains`, tunnel or DNS of their own (`platform-providers.ts`'s own doc comments, verbatim in each reason below). */
|
|
279
|
+
function internalOnlyTarget(service, reason) {
|
|
280
|
+
return {
|
|
281
|
+
service,
|
|
282
|
+
url: null,
|
|
283
|
+
expect: okExpect(),
|
|
284
|
+
depth: 'shallow',
|
|
285
|
+
restartSafe: true,
|
|
286
|
+
behindAccess: false,
|
|
287
|
+
reason,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* The seven platform services #541 names (`platform:auth`, `platform:archon`,
|
|
292
|
+
* `platform:mail`, `platform:ai`, `platform:files`, `platform:integrations`,
|
|
293
|
+
* `platform:edge`) -- `platform:sfu` and `platform:jobs` are shared platform
|
|
294
|
+
* resources too, but neither is in that list, and this issue does not expand
|
|
295
|
+
* it: the SFU has no hostname (`platform-sfu.ts`'s own doc comment: no cheap
|
|
296
|
+
* way to read it over SSH, let alone over HTTP) and the jobs scheduler's
|
|
297
|
+
* admin API answers 401 with no token (`platform-providers.ts`'s own
|
|
298
|
+
* `coolify:jobs` step), so an outside prober could not usefully reach either
|
|
299
|
+
* one anyway.
|
|
300
|
+
*/
|
|
301
|
+
export function platformHealthTargets(platform) {
|
|
302
|
+
return [
|
|
303
|
+
...authTargets(platform),
|
|
304
|
+
{
|
|
305
|
+
service: 'archon',
|
|
306
|
+
url: `https://${platform.archon.hostname}${DEFAULT_API_HEALTH_PATH}`,
|
|
307
|
+
expect: okExpect(),
|
|
308
|
+
depth: 'deep',
|
|
309
|
+
restartSafe: false,
|
|
310
|
+
behindAccess: false,
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
service: 'mail',
|
|
314
|
+
url: `https://${platform.mail.hostname}${STALWART_READY_PATH}`,
|
|
315
|
+
expect: okExpect(),
|
|
316
|
+
depth: 'deep',
|
|
317
|
+
restartSafe: false,
|
|
318
|
+
behindAccess: false,
|
|
319
|
+
},
|
|
320
|
+
internalOnlyTarget('ai', "platform:ai's runner is created with no Coolify domains, tunnel, DNS record or Access app (platform-providers.ts) -- nothing public to probe yet"),
|
|
321
|
+
internalOnlyTarget('files', "platform:files' runner is created with no domains either, the same as platform:auth's apps (platform-providers.ts) -- nothing public to probe yet"),
|
|
322
|
+
{
|
|
323
|
+
service: 'integrations',
|
|
324
|
+
url: null,
|
|
325
|
+
expect: okExpect(),
|
|
326
|
+
depth: 'shallow',
|
|
327
|
+
restartSafe: true,
|
|
328
|
+
behindAccess: false,
|
|
329
|
+
reason: "platform:integrations' hostname has no Cloudflare wiring of its own (platform-providers.ts: 'no Cloudflare wiring is created for hostname') -- whether it resolves at all depends on DNS this executor never set up",
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
// Edge gets a real tunnel and DNS record, unlike ai/files/integrations
|
|
333
|
+
// (`provisionPlatformEdge`'s own doc comment: "the public-service shape
|
|
334
|
+
// provisionPlatformMail/provisionPlatformAuth use ... EDGE_URL has to
|
|
335
|
+
// be reachable by every org's own app").
|
|
336
|
+
service: 'edge',
|
|
337
|
+
url: `https://${platform.edge.hostname}${DEFAULT_API_HEALTH_PATH}`,
|
|
338
|
+
expect: okExpect(),
|
|
339
|
+
depth: 'deep',
|
|
340
|
+
restartSafe: true,
|
|
341
|
+
behindAccess: false,
|
|
342
|
+
},
|
|
343
|
+
];
|
|
344
|
+
}
|
package/dist/plan/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export { computePlanDetail, toPlan } from './diff.js';
|
|
|
5
5
|
export type { StepSystem } from './step-system.js';
|
|
6
6
|
export { stepSystemOf } from './step-system.js';
|
|
7
7
|
export type { PlatformPlanDetail } from './platform-diff.js';
|
|
8
|
-
export { planPlatform, toPlatformPlan, JOBS_SCHEDULER_KEY, PLATFORM_AI_KEY, PLATFORM_AI_INTERNAL_URL_SCOPE_KEY, PLATFORM_AI_MANAGEMENT_KEY_SCOPE_KEY, PLATFORM_AI_ORGANISATION_SCOPE_KEY, PLATFORM_AUTH_KEY, PLATFORM_FILES_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, PLATFORM_FILES_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_MAIL_KEY, PLATFORM_MAIL_INTERNAL_URL_SCOPE_KEY, PLATFORM_MAIL_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_EDGE_KEY, PLATFORM_EDGE_KEY_REF, PLATFORM_EDGE_CLOUDFLARE_TOKEN_REF, } from './platform-diff.js';
|
|
8
|
+
export { planPlatform, toPlatformPlan, JOBS_SCHEDULER_KEY, PLATFORM_AI_KEY, PLATFORM_AI_INTERNAL_URL_SCOPE_KEY, PLATFORM_AI_MANAGEMENT_KEY_SCOPE_KEY, PLATFORM_AI_ORGANISATION_SCOPE_KEY, PLATFORM_AUTH_KEY, PLATFORM_FILES_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, PLATFORM_FILES_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_MAIL_KEY, PLATFORM_MAIL_INTERNAL_URL_SCOPE_KEY, PLATFORM_MAIL_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_EDGE_KEY, PLATFORM_EDGE_INTERNAL_URL_SCOPE_KEY, PLATFORM_EDGE_KEY_REF, PLATFORM_EDGE_CLOUDFLARE_TOKEN_REF, } from './platform-diff.js';
|
|
9
9
|
export type { HeldKeyResolver, OrgPlatformReadiness } from './platform-org.js';
|
|
10
10
|
export { DEFAULT_ORG_PLATFORM_READINESS, ORG_PLATFORM_KEY_REFS, agoraHostValuesFromPlatform, heldPlatformKeyRefs, orgPlatformReadinessFrom, } from './platform-org.js';
|
|
11
11
|
export type { PlatformOrgClientFactories } from './platform-org-ports.js';
|
|
@@ -15,7 +15,7 @@ export { AdoptRefusedError } from './adopt.js';
|
|
|
15
15
|
export type { AuditActor, AuditLog, AuthzGate, StepExecutor, StepDestroyer, DestroyTarget, StepExecutionContext, PlatformStepExecutor, PlatformStepExecutionContext, FoundryPlanApplyOptions, } from './plan-apply.js';
|
|
16
16
|
export { FoundryPlanApply, DeclaredOrgNotFoundError, StalePlanError, ConfirmationRequiredError, NotAuthorizedError, NotATestOrgError, DeclaredPlatformNotFoundError, PlatformStalePlanError, PlatformConfirmationRequiredError, planNeedsConfirmation, } from './plan-apply.js';
|
|
17
17
|
export { FoundryPlatformStepExecutor, ProvidersPlatformCloudflareReader, ProvidersPlatformCoolifyReader, PLATFORM_MAIL_ADMIN_KEY_DESCRIPTION, filesPublicHostsValue, filesShareDomains, } from './platform-providers.js';
|
|
18
|
-
export type { PlatformAuthDeps, PlatformAuthRuntime, PlatformFilesDeps, PlatformFilesRuntime, PlatformEdgeDeps, PlatformEdgeRuntime, } from './platform-providers.js';
|
|
18
|
+
export type { PlatformAuthDeps, PlatformAuthRuntime, PlatformFilesDeps, PlatformFilesRuntime, PlatformEdgeDeps, PlatformDnsFactory, PlatformAccessFactory, PlatformAccessPort, PlatformTunnelsFactory, PlatformEdgeRuntime, } from './platform-providers.js';
|
|
19
19
|
export type { PlatformMailDeps, PlatformMailAdminPort, PlatformMailRuntime, } from './platform-providers.js';
|
|
20
20
|
export { FILES_INTROSPECTION_SECRET_REF, FILES_MANAGEMENT_ISSUER, FILES_MANAGEMENT_KEY_ID, FILES_MANAGEMENT_KEY_REF, PLATFORM_AUTH_INSTANCE_BEARER_REF, } from './platform-providers.js';
|
|
21
21
|
export { AI_MANAGEMENT_ISSUER, AI_MANAGEMENT_KEY_ID, AI_SIGNING_KEY_REF, AiManagementKeyExistsError, createAiManagementKeyMinter, PLATFORM_AI_MANAGEMENT_KEY_REF, PLATFORM_AI_MANAGEMENT_PERMISSIONS, } from './platform-providers.js';
|
|
@@ -27,7 +27,7 @@ export { MissingCallsSfuConnectionError, applyCallsSfuPlatformStep } from './pla
|
|
|
27
27
|
export type { MailCopyJob, MailCopyPort, MailCopyStepResult } from './mail-copy.js';
|
|
28
28
|
export { MAIL_COPY_COMPLETED_SCOPE_KEY, MAIL_COPY_DONE_SCOPE_KEY, MissingMailCopyAccountsError, MissingMailCopySourceTokenError, MissingMailCopyStalwartTokenError, parseMailCopyCompleted, runMailCopyStep, } from './mail-copy.js';
|
|
29
29
|
export type { ScopeResolver, RecordedIds } from './providers-readers.js';
|
|
30
|
-
export { ProvidersCloudflareReader, ProvidersCmsReader, ProvidersCoolifyReader, ProvidersZitadelReader, storeScopeResolver, storeRecordedIds, } from './providers-readers.js';
|
|
30
|
+
export { ProvidersAiReader, ProvidersCloudflareReader, ProvidersCmsReader, ProvidersCoolifyReader, ProvidersZitadelReader, storeScopeResolver, storeRecordedIds, } from './providers-readers.js';
|
|
31
31
|
export type { AgoraHostPort, AgoraHostValues } from './agora.js';
|
|
32
32
|
export type { BrokerPort } from './broker-entries.js';
|
|
33
33
|
export { PlatformBrokerPort } from './broker-entries.js';
|
|
@@ -39,6 +39,20 @@ export type { MailAllowlistApp } from './mail-identity-allowlist.js';
|
|
|
39
39
|
export { checkMailAppsAllowlist } from './mail-identity-allowlist.js';
|
|
40
40
|
export type { CoolifyPort, CloudflarePort, ZitadelPort, CallsSfuPort, JobsSchedulerPort, FilesOrganisationPort, AiOrganisationPort, FoundryStepExecutorPorts, PlatformOrgPortSource, } from './executor.js';
|
|
41
41
|
export { FoundryStepExecutor, NotBuiltError, healthUrls } from './executor.js';
|
|
42
|
+
export type { HealthTarget, HealthTargetDepth, HealthTargetExpect } from './health-targets.js';
|
|
43
|
+
export { healthTargets, platformHealthTargets } from './health-targets.js';
|
|
44
|
+
export { createEdgeDns, EDGE_DNS_WRITE_CONCURRENCY } from './edge-dns.js';
|
|
45
|
+
export type { DnsPort, DnsRecordInput, DnsRecordSummary, EdgePlatformDns } from './edge-dns.js';
|
|
46
|
+
export { createEdgeAccess } from './edge-access.js';
|
|
47
|
+
export type { AccessAppSummary, AccessDecision, AccessPolicyRule, AccessPort, EdgePlatformAccess, } from './edge-access.js';
|
|
48
|
+
export { createEdgeWorkers } from './edge-workers.js';
|
|
49
|
+
export type { EdgePlatformWorkers, EdgeWorkersOptions } from './edge-workers.js';
|
|
50
|
+
export { createEdgeRateLimits } from './edge-ratelimits.js';
|
|
51
|
+
export type { EdgePlatformRateLimits, EdgeRateLimitsOptions, RateLimitPort, RateLimitRuleInput, } from './edge-ratelimits.js';
|
|
52
|
+
export { createEdgeZones } from './edge-zones.js';
|
|
53
|
+
export type { EdgePlatformZones, EdgeZonesOptions, ZonePort } from './edge-zones.js';
|
|
54
|
+
export { createEdgeTunnels } from './edge-tunnels.js';
|
|
55
|
+
export type { EdgePlatformTunnels, TunnelIngress, TunnelPort, TunnelSummary, } from './edge-tunnels.js';
|
|
42
56
|
export type { ZitadelListAppsRawApp } from './executor.js';
|
|
43
57
|
export { adaptZitadelListApps } from './executor.js';
|
|
44
58
|
export { HttpJobsSchedulerClient } from './jobs-scheduler-http.js';
|
package/dist/plan/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { readLiveResources, readLivePlatformResources } from './live.js';
|
|
2
2
|
export { computePlanDetail, toPlan } from './diff.js';
|
|
3
3
|
export { stepSystemOf } from './step-system.js';
|
|
4
|
-
export { planPlatform, toPlatformPlan, JOBS_SCHEDULER_KEY, PLATFORM_AI_KEY, PLATFORM_AI_INTERNAL_URL_SCOPE_KEY, PLATFORM_AI_MANAGEMENT_KEY_SCOPE_KEY, PLATFORM_AI_ORGANISATION_SCOPE_KEY, PLATFORM_AUTH_KEY, PLATFORM_FILES_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, PLATFORM_FILES_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_MAIL_KEY, PLATFORM_MAIL_INTERNAL_URL_SCOPE_KEY, PLATFORM_MAIL_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_EDGE_KEY, PLATFORM_EDGE_KEY_REF, PLATFORM_EDGE_CLOUDFLARE_TOKEN_REF, } from './platform-diff.js';
|
|
4
|
+
export { planPlatform, toPlatformPlan, JOBS_SCHEDULER_KEY, PLATFORM_AI_KEY, PLATFORM_AI_INTERNAL_URL_SCOPE_KEY, PLATFORM_AI_MANAGEMENT_KEY_SCOPE_KEY, PLATFORM_AI_ORGANISATION_SCOPE_KEY, PLATFORM_AUTH_KEY, PLATFORM_FILES_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, PLATFORM_FILES_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_MAIL_KEY, PLATFORM_MAIL_INTERNAL_URL_SCOPE_KEY, PLATFORM_MAIL_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_EDGE_KEY, PLATFORM_EDGE_INTERNAL_URL_SCOPE_KEY, PLATFORM_EDGE_KEY_REF, PLATFORM_EDGE_CLOUDFLARE_TOKEN_REF, } from './platform-diff.js';
|
|
5
5
|
export { DEFAULT_ORG_PLATFORM_READINESS, ORG_PLATFORM_KEY_REFS, agoraHostValuesFromPlatform, heldPlatformKeyRefs, orgPlatformReadinessFrom, } from './platform-org.js';
|
|
6
6
|
export { PlatformOrgBlockedError, PlatformOrgPorts } from './platform-org-ports.js';
|
|
7
7
|
export { AdoptRefusedError } from './adopt.js';
|
|
@@ -14,11 +14,18 @@ export { createZitadelAdminMinter } from './zitadel-machine-user.js';
|
|
|
14
14
|
export { createZitadelProjectRoles } from './zitadel-project-roles.js';
|
|
15
15
|
export { MissingCallsSfuConnectionError, applyCallsSfuPlatformStep } from './platform-sfu.js';
|
|
16
16
|
export { MAIL_COPY_COMPLETED_SCOPE_KEY, MAIL_COPY_DONE_SCOPE_KEY, MissingMailCopyAccountsError, MissingMailCopySourceTokenError, MissingMailCopyStalwartTokenError, parseMailCopyCompleted, runMailCopyStep, } from './mail-copy.js';
|
|
17
|
-
export { ProvidersCloudflareReader, ProvidersCmsReader, ProvidersCoolifyReader, ProvidersZitadelReader, storeScopeResolver, storeRecordedIds, } from './providers-readers.js';
|
|
17
|
+
export { ProvidersAiReader, ProvidersCloudflareReader, ProvidersCmsReader, ProvidersCoolifyReader, ProvidersZitadelReader, storeScopeResolver, storeRecordedIds, } from './providers-readers.js';
|
|
18
18
|
export { PlatformBrokerPort } from './broker-entries.js';
|
|
19
19
|
export { loginHostnameFor } from './login-host.js';
|
|
20
20
|
export { checkMailIdentityAudience } from './mail-identity.js';
|
|
21
21
|
export { checkMailAppsAllowlist } from './mail-identity-allowlist.js';
|
|
22
22
|
export { FoundryStepExecutor, NotBuiltError, healthUrls } from './executor.js';
|
|
23
|
+
export { healthTargets, platformHealthTargets } from './health-targets.js';
|
|
24
|
+
export { createEdgeDns, EDGE_DNS_WRITE_CONCURRENCY } from './edge-dns.js';
|
|
25
|
+
export { createEdgeAccess } from './edge-access.js';
|
|
26
|
+
export { createEdgeWorkers } from './edge-workers.js';
|
|
27
|
+
export { createEdgeRateLimits } from './edge-ratelimits.js';
|
|
28
|
+
export { createEdgeZones } from './edge-zones.js';
|
|
29
|
+
export { createEdgeTunnels } from './edge-tunnels.js';
|
|
23
30
|
export { adaptZitadelListApps } from './executor.js';
|
|
24
31
|
export { HttpJobsSchedulerClient } from './jobs-scheduler-http.js';
|
package/dist/plan/live.d.ts
CHANGED
|
@@ -29,8 +29,19 @@ export interface LiveResource {
|
|
|
29
29
|
* this union so `StepAction.system` (`diff.ts`) can name it. `'edge'`
|
|
30
30
|
* (#317) is the same: a edge key has no live lookup of its own, the same
|
|
31
31
|
* shape `ai`/`files` entry keys already follow.
|
|
32
|
+
*
|
|
33
|
+
* `'heartbeats'` (#540) is the one system here with a cheap live list of
|
|
34
|
+
* its own (`HeartbeatsPort.listChecks`, `platform-providers.ts`) --
|
|
35
|
+
* `platform-diff.ts`'s `planHeartbeats` still does not diff against a
|
|
36
|
+
* `LiveResource` list here: the reconcile step itself (list, create
|
|
37
|
+
* missing, delete stale) is what does that diffing, against the live
|
|
38
|
+
* check names. `planHeartbeats` plans that step only when its own
|
|
39
|
+
* recorded desired-set marker (`PLATFORM_HEARTBEATS_APPLIED_SCOPE_KEY`)
|
|
40
|
+
* no longer matches what is currently declared, never unconditionally --
|
|
41
|
+
* see that function's own doc comment for why "always planned" broke
|
|
42
|
+
* `bootstrapPlatform`.
|
|
32
43
|
*/
|
|
33
|
-
system: 'zitadel' | 'coolify' | 'cloudflare' | 'files' | 'ai' | 'cms' | 'jobs' | 'sfu' | 'edge';
|
|
44
|
+
system: 'zitadel' | 'coolify' | 'cloudflare' | 'files' | 'ai' | 'cms' | 'jobs' | 'sfu' | 'edge' | 'heartbeats';
|
|
34
45
|
kind: string;
|
|
35
46
|
key: string;
|
|
36
47
|
config: Record<string, unknown>;
|
|
@@ -208,6 +208,10 @@ export interface PlatformStepExecutor {
|
|
|
208
208
|
mail?: string;
|
|
209
209
|
/** #257: see `PlanPlatformOptions.blocked.backupExecutorMissing` (`platform-diff.ts`). */
|
|
210
210
|
backupExecutorMissing?: boolean;
|
|
211
|
+
/** #540: see `PlanPlatformOptions.blocked.hc` (`platform-diff.ts`). */
|
|
212
|
+
hc?: string;
|
|
213
|
+
/** #540: see `PlanPlatformOptions.blocked.heartbeatsExecutorMissing` (`platform-diff.ts`). */
|
|
214
|
+
heartbeatsExecutorMissing?: boolean;
|
|
211
215
|
}>;
|
|
212
216
|
/**
|
|
213
217
|
* Steps whose live state differs from the declared one in a way the recorded
|
package/dist/plan/plan-apply.js
CHANGED
|
@@ -8,7 +8,7 @@ import { AdoptRefusedError, adoptCandidates, adoptedKeysUnreadable, adoptedScope
|
|
|
8
8
|
import { NO_AGORA_HOST_VALUES, readAgoraHost, } from './agora.js';
|
|
9
9
|
import { AI_KEY_UNDELIVERED_SCOPE_KEY, JOBS_REGISTRATION_UNDELIVERED_SCOPE_KEY, computePlanDetail, toPlan, } from './diff.js';
|
|
10
10
|
import { entryAiUndeliveredScopeKey } from './entry-ai-key.js';
|
|
11
|
-
import { entryEdgeRetiredScopeKey, entryEdgeUndeliveredScopeKey } from './entry-edge-key.js';
|
|
11
|
+
import { BOULE_EDGE_ENTRY_KEY, entryEdgeRetiredScopeKey, entryEdgeUndeliveredScopeKey, } from './entry-edge-key.js';
|
|
12
12
|
import { FILES_ROOT_UNDELIVERED_SCOPE_KEY, entryFilesUndeliveredScopeKey, } from './entry-files-key.js';
|
|
13
13
|
import { readLivePlatformResources, readLiveResources, } from './live.js';
|
|
14
14
|
import { lockedHostnames } from './locked-hostnames.js';
|
|
@@ -1376,6 +1376,10 @@ function failedFields(org, detail, completed, serviceIds) {
|
|
|
1376
1376
|
failed.add('hosting.entries.uses.edge');
|
|
1377
1377
|
}
|
|
1378
1378
|
}
|
|
1379
|
+
// F3: Boule's own key, wanted whenever the org has a domain.
|
|
1380
|
+
if (org.domain && serviceIds[entryEdgeUndeliveredScopeKey(BOULE_EDGE_ENTRY_KEY)]) {
|
|
1381
|
+
failed.add('hosting.entries.uses.edge');
|
|
1382
|
+
}
|
|
1379
1383
|
// The same for a jobs registration whose credentials never reached the host.
|
|
1380
1384
|
if (serviceIds[JOBS_REGISTRATION_UNDELIVERED_SCOPE_KEY] &&
|
|
1381
1385
|
!serviceIds[`jobs:registration:${org.id}`]) {
|