@wtfalch/foundry 0.20.0 → 0.21.1
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 +23 -1
- package/dist/bootstrap/versions.d.ts +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/migrations/0046_platform_hc.sql +19 -0
- package/dist/plan/deploy-health.js +5 -1
- package/dist/plan/diff.js +23 -0
- package/dist/plan/executor.d.ts +34 -0
- package/dist/plan/executor.js +181 -28
- 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 +2 -0
- package/dist/plan/index.js +1 -0
- package/dist/plan/live.d.ts +12 -1
- package/dist/plan/plan-apply.d.ts +4 -0
- package/dist/plan/platform-diff.d.ts +124 -0
- package/dist/plan/platform-diff.js +287 -3
- package/dist/plan/platform-providers.d.ts +150 -2
- package/dist/plan/platform-providers.js +498 -42
- package/dist/plan/providers-readers.js +32 -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 +1 -1
- package/versions.json +2 -1
|
@@ -117,7 +117,7 @@ export declare const JOBS_ADMIN_TOKEN_REF = "jobs:scheduler:adminToken";
|
|
|
117
117
|
* org's own app, reused here for the platform's one scheduler instead of once
|
|
118
118
|
* per org.
|
|
119
119
|
*/
|
|
120
|
-
type JobsCoolifyPort = Pick<CoolifyPort, 'findProjectByName' | 'createProject' | 'findOrCreateEnvironment' | 'listServers' | 'requireGithubApp' | 'findApplicationByName' | 'createGithubAppApplication' | 'listDatabases' | 'createPostgresDatabase' | 'getDatabase' | 'raw' | 'bulkUpsertEnvs' | 'getApplication' | 'deploy'>;
|
|
120
|
+
type JobsCoolifyPort = Pick<CoolifyPort, 'findProjectByName' | 'createProject' | 'findOrCreateEnvironment' | 'listServers' | 'requireGithubApp' | 'findApplicationByName' | 'createGithubAppApplication' | 'listDatabases' | 'createPostgresDatabase' | 'getDatabase' | 'raw' | 'bulkUpsertEnvs' | 'listEnvs' | 'getApplication' | 'deploy'>;
|
|
121
121
|
/**
|
|
122
122
|
* Options for {@link FoundryPlatformStepExecutor}'s constructor, replacing
|
|
123
123
|
* four positional arguments that shifted on every merge that added a
|
|
@@ -182,6 +182,18 @@ export interface FoundryPlatformStepExecutorOptions {
|
|
|
182
182
|
* planned.
|
|
183
183
|
*/
|
|
184
184
|
backup?: PlatformBackupDeps;
|
|
185
|
+
/**
|
|
186
|
+
* #540: dependencies for `coolify:app` keyed {@link PLATFORM_HC_KEY} --
|
|
187
|
+
* optional for the same reason as `auth`; a host that declares
|
|
188
|
+
* `platform.hc.enabled` without it gets the step reported as blocked.
|
|
189
|
+
*/
|
|
190
|
+
hc?: PlatformHcDeps;
|
|
191
|
+
/**
|
|
192
|
+
* #540: dependencies for the `heartbeats:reconcile` step -- optional for
|
|
193
|
+
* the same reason as `auth`; `hc` applied with this unset gets the
|
|
194
|
+
* heartbeat checks reported as blocked rather than planned.
|
|
195
|
+
*/
|
|
196
|
+
heartbeats?: PlatformHeartbeatsDeps;
|
|
185
197
|
}
|
|
186
198
|
/**
|
|
187
199
|
* The real {@link PlatformStepExecutor} for the two steps built so far:
|
|
@@ -261,6 +273,8 @@ export declare class FoundryPlatformStepExecutor implements PlatformStepExecutor
|
|
|
261
273
|
private readonly edge?;
|
|
262
274
|
private readonly archon?;
|
|
263
275
|
private readonly backup?;
|
|
276
|
+
private readonly hc?;
|
|
277
|
+
private readonly heartbeats?;
|
|
264
278
|
constructor(cloudflare: Pick<CloudflarePort, 'findZoneByApex' | 'createZone'>, declaredOrgStore: Pick<DeclaredOrgStore, 'findOrgIdForZone' | 'list' | 'get'>, options?: FoundryPlatformStepExecutorOptions);
|
|
265
279
|
blockedSteps(platform: DeclaredPlatform, recordedIds?: Readonly<Record<string, string>>): Promise<{
|
|
266
280
|
auth?: string;
|
|
@@ -273,6 +287,9 @@ export declare class FoundryPlatformStepExecutor implements PlatformStepExecutor
|
|
|
273
287
|
archon?: string;
|
|
274
288
|
mail?: string;
|
|
275
289
|
backupExecutorMissing?: boolean;
|
|
290
|
+
hc?: string;
|
|
291
|
+
heartbeatsExecutorMissing?: boolean;
|
|
292
|
+
heartbeatsApiKeyMissing?: boolean;
|
|
276
293
|
}>;
|
|
277
294
|
/**
|
|
278
295
|
* Whether Archon's live Access policy lets through anyone but the declared
|
|
@@ -676,6 +693,17 @@ export interface PlatformAiRuntime {
|
|
|
676
693
|
migrateIntervalMs?: number;
|
|
677
694
|
/** Tries at handing a secret to the host (default 3): the management key and the signing key both use this. */
|
|
678
695
|
deliverAttempts?: number;
|
|
696
|
+
/**
|
|
697
|
+
* #543: injected for tests, against a fake runner host -- the runner's own
|
|
698
|
+
* deploy wait now does a `'ready'` probe of its `/api/health`, the same
|
|
699
|
+
* `DeployHealthRuntime.fetch` field `provisionPlatformMail`/
|
|
700
|
+
* `provisionPlatformArchon` already take. Defaults to the global `fetch`.
|
|
701
|
+
*/
|
|
702
|
+
fetch?: typeof fetch;
|
|
703
|
+
/** How long to wait for the runner to answer ready (default 5 minutes). */
|
|
704
|
+
readyTimeoutMs?: number;
|
|
705
|
+
/** Pause between readiness polls (default 5 seconds). */
|
|
706
|
+
readyIntervalMs?: number;
|
|
679
707
|
}
|
|
680
708
|
/** The ref the AI management key is delivered under; the host's `AiOrganisationPort` adapter reads it back. */
|
|
681
709
|
export declare const PLATFORM_AI_MANAGEMENT_KEY_REF = "ai:platform:managementKey";
|
|
@@ -849,6 +877,17 @@ export interface PlatformFilesRuntime {
|
|
|
849
877
|
migrateIntervalMs?: number;
|
|
850
878
|
/** Tries at handing each secret to the host (default 3). */
|
|
851
879
|
deliverAttempts?: number;
|
|
880
|
+
/**
|
|
881
|
+
* #543: injected for tests, against a fake runner host -- the runner's own
|
|
882
|
+
* deploy wait now does a `'ready'` probe of its `/api/health`, same as
|
|
883
|
+
* `provisionPlatformMail`/`provisionPlatformArchon` already take. Defaults
|
|
884
|
+
* to the global `fetch`.
|
|
885
|
+
*/
|
|
886
|
+
fetch?: typeof fetch;
|
|
887
|
+
/** How long to wait for the runner to answer ready (default 5 minutes). */
|
|
888
|
+
readyTimeoutMs?: number;
|
|
889
|
+
/** Pause between readiness polls (default 5 seconds). */
|
|
890
|
+
readyIntervalMs?: number;
|
|
852
891
|
}
|
|
853
892
|
/**
|
|
854
893
|
* #171: how the host signs a files management request with the key this step
|
|
@@ -903,6 +942,30 @@ export interface PlatformEdgeDeps {
|
|
|
903
942
|
/** Clock, sleep and fetch for the readiness wait and delivery retries; real ones by default. */
|
|
904
943
|
runtime?: PlatformEdgeRuntime;
|
|
905
944
|
}
|
|
945
|
+
export interface PlatformHcRuntime {
|
|
946
|
+
fetch?: typeof fetch;
|
|
947
|
+
sleep?: (ms: number) => Promise<void>;
|
|
948
|
+
now?: () => number;
|
|
949
|
+
readyTimeoutMs?: number;
|
|
950
|
+
readyIntervalMs?: number;
|
|
951
|
+
}
|
|
952
|
+
/**
|
|
953
|
+
* #540: dependencies for `coolify:app` keyed {@link PLATFORM_HC_KEY} -- the
|
|
954
|
+
* {@link PlatformEdgeDeps} bundling shape minus a Cloudflare token of its
|
|
955
|
+
* own: `hc` writes its tunnel and DNS record through edge the same way
|
|
956
|
+
* mail and archon do, and needs no `heldKeys`/`deliverSecret` at all --
|
|
957
|
+
* `SECRET_KEY` (Django's session-signing key) is generated once by
|
|
958
|
+
* {@link provisionPlatformHc} itself and never leaves this Coolify app.
|
|
959
|
+
*/
|
|
960
|
+
export interface PlatformHcDeps {
|
|
961
|
+
coolify: Pick<CoolifyPort, 'findProjectByName' | 'createProject' | 'findOrCreateEnvironment' | 'listServers' | 'findApplicationByName' | 'getApplication' | 'createDockerImageApplication' | 'createDockerfileApplication' | 'listDatabases' | 'createPostgresDatabase' | 'getDatabase' | 'bulkUpsertEnvs' | 'listEnvs' | 'updateApplication' | 'deploy' | 'deployAndWait' | 'raw'>;
|
|
962
|
+
/** F4.dns: every DNS record call, through edge at its recorded address (see {@link PlatformDnsFactory}). */
|
|
963
|
+
dns: PlatformDnsFactory;
|
|
964
|
+
/** F4.tunnels: every tunnel call, through edge at its recorded address (see {@link PlatformTunnelsFactory}). */
|
|
965
|
+
tunnels: PlatformTunnelsFactory;
|
|
966
|
+
/** Clock, sleep and fetch for the readiness wait; real ones by default. */
|
|
967
|
+
runtime?: PlatformHcRuntime;
|
|
968
|
+
}
|
|
906
969
|
/**
|
|
907
970
|
* #174: everything {@link provisionPlatformIntegrations} needs beyond
|
|
908
971
|
* {@link PlatformStepExecutionContext} -- a bundle, like
|
|
@@ -977,6 +1040,91 @@ export interface PlatformBackupDeps {
|
|
|
977
1040
|
/** A cron string or one of Coolify's names (`daily`, `weekly`, ...). Default `'daily'`. */
|
|
978
1041
|
frequency?: string;
|
|
979
1042
|
}
|
|
1043
|
+
/** One check as self-hosted Healthchecks' own Management API reports it. */
|
|
1044
|
+
export interface HeartbeatCheck {
|
|
1045
|
+
id: string;
|
|
1046
|
+
name: string;
|
|
1047
|
+
pingUrl: string;
|
|
1048
|
+
}
|
|
1049
|
+
/**
|
|
1050
|
+
* #540: the heartbeat server's own Management API -- a project API key in a
|
|
1051
|
+
* header, `POST /api/v3/checks/` with `name`/`unique: ['name']` to upsert a
|
|
1052
|
+
* check idempotently by name, `GET /api/v3/checks/` to list every one,
|
|
1053
|
+
* `DELETE /api/v3/checks/<uuid>` to remove one -- restated here as the port
|
|
1054
|
+
* `provisionPlatformHeartbeats` plans and provisions against, never a real
|
|
1055
|
+
* HTTP client: this package never connects itself (`README.md`), so the
|
|
1056
|
+
* host implements this against its own running `hc` instance and its own
|
|
1057
|
+
* project API key, the same seam every other real I/O in this file crosses.
|
|
1058
|
+
*/
|
|
1059
|
+
export interface HeartbeatsPort {
|
|
1060
|
+
listChecks(): Promise<HeartbeatCheck[]>;
|
|
1061
|
+
/** Creates a check named `name`, or returns the existing one of that name -- the Management API's own upsert. */
|
|
1062
|
+
createCheck(name: string): Promise<HeartbeatCheck>;
|
|
1063
|
+
/**
|
|
1064
|
+
* #540 round 2: pauses a check (`POST /api/v3/checks/<uuid>/pause/`) so it
|
|
1065
|
+
* reads "paused" rather than "down" while nothing actually pings it yet --
|
|
1066
|
+
* both the uptime Worker's own check and every backup check (see this
|
|
1067
|
+
* file's own doc comment on `PlatformHeartbeatsDeps` for why). Sending a
|
|
1068
|
+
* check a ping resumes it automatically (Healthchecks' own documented
|
|
1069
|
+
* behaviour), so foundry never has to call the reverse of this: once a
|
|
1070
|
+
* real pinger exists, the check un-pauses itself on its first ping.
|
|
1071
|
+
* `provisionPlatformHeartbeats` calls this exactly once, right after
|
|
1072
|
+
* `createCheck`, and never again on a check it merely finds already live
|
|
1073
|
+
* -- a check already being pinged, or one an operator unpaused by hand,
|
|
1074
|
+
* must never be paused again by a reconcile run.
|
|
1075
|
+
*/
|
|
1076
|
+
pauseCheck(id: string): Promise<void>;
|
|
1077
|
+
deleteCheck(id: string): Promise<void>;
|
|
1078
|
+
}
|
|
1079
|
+
/**
|
|
1080
|
+
* #540: dependencies for the `heartbeats:reconcile` step
|
|
1081
|
+
* (`platform-diff.ts`'s `planHeartbeats`).
|
|
1082
|
+
*
|
|
1083
|
+
* #540 round 2: `resolve` is new. It guards only the *delete* half of
|
|
1084
|
+
* {@link provisionPlatformHeartbeats} -- a live check is only ever deleted
|
|
1085
|
+
* when foundry itself holds a ping URL for its exact name
|
|
1086
|
+
* (`heartbeatCheckUrlRef`, `platform-diff.ts`), proof this run or an
|
|
1087
|
+
* earlier one created it. Without that guard, a hand-made check sharing
|
|
1088
|
+
* foundry's naming scheme (an operator's own `uptime-worker`, say, made
|
|
1089
|
+
* before foundry ever ran) could be deleted the moment that name was not
|
|
1090
|
+
* yet desired -- a backup service with no recorded database yet is in
|
|
1091
|
+
* `provisionPlatformHeartbeats`'s own `ownedNames` but not `desiredSet`.
|
|
1092
|
+
* `resolve` is never consulted on the *create* half: a check already live
|
|
1093
|
+
* under a desired name is always treated as foundry's, the way it always
|
|
1094
|
+
* was, and (the round-2 fix for a second bug) its ping address is now
|
|
1095
|
+
* always re-saved rather than only on the create that first makes it live
|
|
1096
|
+
* -- see `put`'s own doc comment below for why that half carries no
|
|
1097
|
+
* adoption risk the way deleting does.
|
|
1098
|
+
*
|
|
1099
|
+
* `put` records a check's own ping URL -- on every create, and (new this
|
|
1100
|
+
* round) on every run that finds a desired check already live, not only
|
|
1101
|
+
* the run that created it. Before this fix, a save that failed once, or a
|
|
1102
|
+
* run that died between create and save, or a host that wired `heldKeys`
|
|
1103
|
+
* only after the original create, lost that address for good: the next run
|
|
1104
|
+
* found the check already live and never saved anything, despite this very
|
|
1105
|
+
* doc comment already claiming the step "can simply ask the live check
|
|
1106
|
+
* again." Required whenever an address actually needs saving -- optional
|
|
1107
|
+
* only so a host without one still compiles, in which case nothing here
|
|
1108
|
+
* can ever be created or re-confirmed, and the step throws rather than
|
|
1109
|
+
* silently drop an address: CLAUDE.md platform-step rule 1 is written for a
|
|
1110
|
+
* minted credential, not a ping URL (a lost one is simply re-asked from the
|
|
1111
|
+
* live check, never re-minted), but the rule's "never silently lose it"
|
|
1112
|
+
* half applies just as well here.
|
|
1113
|
+
*
|
|
1114
|
+
* Both fields are optional only so a host without either still compiles;
|
|
1115
|
+
* while `PlatformHeartbeatsDeps` is wired at all, `FoundryPlatformStepExecutor
|
|
1116
|
+
* .blockedSteps`'s own `heartbeatsApiKeyMissing` check blocks the step
|
|
1117
|
+
* until a project API key is held under {@link PLATFORM_HC_API_KEY_REF}, so
|
|
1118
|
+
* the live Healthchecks calls below are never reached with nothing behind
|
|
1119
|
+
* them.
|
|
1120
|
+
*/
|
|
1121
|
+
export interface PlatformHeartbeatsDeps {
|
|
1122
|
+
heartbeats: HeartbeatsPort;
|
|
1123
|
+
heldKeys?: {
|
|
1124
|
+
resolve(ref: string): Promise<string | null>;
|
|
1125
|
+
put?(ref: string, value: string): Promise<void>;
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
980
1128
|
/**
|
|
981
1129
|
* The real {@link PlatformCloudflareReader}: the platform's apex zone as
|
|
982
1130
|
* `planApexZone` expects to see it, and nothing else. The zone's DNS records
|
|
@@ -1006,7 +1154,7 @@ export declare class ProvidersPlatformCloudflareReader implements PlatformCloudf
|
|
|
1006
1154
|
export declare class ProvidersPlatformCoolifyReader implements PlatformCoolifyReader {
|
|
1007
1155
|
private readonly client;
|
|
1008
1156
|
private readonly platformStore;
|
|
1009
|
-
constructor(client: Pick<CoolifyPort, 'getApplication' | 'getDatabase' | 'listDatabaseBackups'>, platformStore: Pick<PlatformStore, 'getServiceIds'>);
|
|
1157
|
+
constructor(client: Pick<CoolifyPort, 'getApplication' | 'getDatabase' | 'listDatabaseBackups' | 'listEnvs' | 'raw'>, platformStore: Pick<PlatformStore, 'getServiceIds'>);
|
|
1010
1158
|
listResources(): Promise<LiveResource[]>;
|
|
1011
1159
|
}
|
|
1012
1160
|
/** Scope id (no `:`): the share domains the runner's Coolify `domains` was last set to, comma-joined. */
|