@wtfalch/foundry 0.22.1 → 0.22.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/plan/diff.js +13 -11
- package/dist/plan/executor.d.ts +0 -2
- package/dist/plan/executor.js +24 -20
- package/dist/plan/hostnames.d.ts +13 -1
- package/dist/plan/hostnames.js +17 -1
- package/dist/plan/index.d.ts +1 -1
- package/dist/plan/index.js +1 -1
- package/dist/plan/platform-diff.d.ts +26 -6
- package/dist/plan/platform-diff.js +31 -7
- package/dist/plan/platform-org-ports.js +3 -2
- package/dist/plan/platform-org.d.ts +7 -1
- package/dist/plan/platform-org.js +4 -2
- package/dist/plan/platform-providers.d.ts +5 -1
- package/dist/plan/platform-providers.js +39 -8
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -22,7 +22,7 @@ export type { LiveResource, ZitadelReader, CoolifyReader, CloudflareReader, File
|
|
|
22
22
|
export { AdoptRefusedError } from './plan/index.js';
|
|
23
23
|
export { createEdgeAccess, createEdgeDns, 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';
|
|
25
|
-
export { readLiveResources, readLivePlatformResources, computePlanDetail, toPlan, 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_FILES_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, PLATFORM_FILES_ZITADEL_PROJECT_SCOPE_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, FoundryPlanApply, DeclaredOrgNotFoundError, StalePlanError, ConfirmationRequiredError, NotAuthorizedError, NotATestOrgError, DeclaredPlatformNotFoundError, PlatformStalePlanError, PlatformConfirmationRequiredError, planNeedsConfirmation, } from './plan/index.js';
|
|
25
|
+
export { readLiveResources, readLivePlatformResources, computePlanDetail, toPlan, planPlatform, toPlatformPlan, JOBS_INTERNAL_URL_SCOPE_KEY, JOBS_SCHEDULER_KEY, PLATFORM_AI_KEY, PLATFORM_AI_INTERNAL_URL_SCOPE_KEY, PLATFORM_AI_MANAGEMENT_KEY_SCOPE_KEY, PLATFORM_AI_ORGANISATION_SCOPE_KEY, PLATFORM_FILES_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, PLATFORM_FILES_ZITADEL_PROJECT_SCOPE_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, FoundryPlanApply, DeclaredOrgNotFoundError, StalePlanError, ConfirmationRequiredError, NotAuthorizedError, NotATestOrgError, DeclaredPlatformNotFoundError, PlatformStalePlanError, PlatformConfirmationRequiredError, planNeedsConfirmation, } from './plan/index.js';
|
|
26
26
|
export type { HeldKeyResolver, OrgPlatformReadiness } from './plan/index.js';
|
|
27
27
|
export { DEFAULT_ORG_PLATFORM_READINESS, ORG_PLATFORM_KEY_REFS, agoraHostValuesFromPlatform, heldPlatformKeyRefs, orgPlatformReadinessFrom, } from './plan/index.js';
|
|
28
28
|
export type { PlatformOrgClientFactories } from './plan/index.js';
|
package/dist/index.js
CHANGED
|
@@ -13,7 +13,7 @@ export { createPostgresAuditLog } from './store/audit.js';
|
|
|
13
13
|
export { declaredOrgs, foundryAppliedState, foundryAppliedVersions, foundryAuditEvents, foundryDeclaredPlatform, foundryRequestedChanges, } from './store/schema.js';
|
|
14
14
|
export { AdoptRefusedError } from './plan/index.js';
|
|
15
15
|
export { createEdgeAccess, createEdgeDns, createEdgeRateLimits, createEdgeTunnels, createEdgeWorkers, createEdgeZones, } from './plan/index.js';
|
|
16
|
-
export { readLiveResources, readLivePlatformResources, computePlanDetail, toPlan, 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_FILES_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, PLATFORM_FILES_ZITADEL_PROJECT_SCOPE_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, FoundryPlanApply, DeclaredOrgNotFoundError, StalePlanError, ConfirmationRequiredError, NotAuthorizedError, NotATestOrgError, DeclaredPlatformNotFoundError, PlatformStalePlanError, PlatformConfirmationRequiredError, planNeedsConfirmation, } from './plan/index.js';
|
|
16
|
+
export { readLiveResources, readLivePlatformResources, computePlanDetail, toPlan, planPlatform, toPlatformPlan, JOBS_INTERNAL_URL_SCOPE_KEY, JOBS_SCHEDULER_KEY, PLATFORM_AI_KEY, PLATFORM_AI_INTERNAL_URL_SCOPE_KEY, PLATFORM_AI_MANAGEMENT_KEY_SCOPE_KEY, PLATFORM_AI_ORGANISATION_SCOPE_KEY, PLATFORM_FILES_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, PLATFORM_FILES_ZITADEL_PROJECT_SCOPE_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, FoundryPlanApply, DeclaredOrgNotFoundError, StalePlanError, ConfirmationRequiredError, NotAuthorizedError, NotATestOrgError, DeclaredPlatformNotFoundError, PlatformStalePlanError, PlatformConfirmationRequiredError, planNeedsConfirmation, } from './plan/index.js';
|
|
17
17
|
export { DEFAULT_ORG_PLATFORM_READINESS, ORG_PLATFORM_KEY_REFS, agoraHostValuesFromPlatform, heldPlatformKeyRefs, orgPlatformReadinessFrom, } from './plan/index.js';
|
|
18
18
|
export { PlatformOrgBlockedError, PlatformOrgPorts } from './plan/index.js';
|
|
19
19
|
export { FoundryStepExecutor, NotBuiltError, healthUrls, loginHostnameFor } from './plan/index.js';
|
package/dist/plan/diff.js
CHANGED
|
@@ -6,10 +6,10 @@ import { brokerEntryAppliedScopeKey, brokerEntryWarningsScopeKey, entryAuthEnv,
|
|
|
6
6
|
import { planEntryAiKeys } from './entry-ai-key.js';
|
|
7
7
|
import { planEdgeReservedLabels, planEntryEdgeKeys } from './entry-edge-key.js';
|
|
8
8
|
import { planFilesKeys } from './entry-files-key.js';
|
|
9
|
-
import { appHostname, entryHostnames, tunnelDnsHostnames } from './hostnames.js';
|
|
9
|
+
import { appHostname, entryHostnames, tunnelDnsHostnames, tunnelDomainList } from './hostnames.js';
|
|
10
10
|
import { LOGIN_BINDING_SCOPE_KEY, companyLoginTunnelHost, loginBindingState, loginHostClientIds, loginHostnameFor, pendingRemovalHosts, } from './login-host.js';
|
|
11
11
|
import { MAIL_COPY_COMPLETED_SCOPE_KEY, MAIL_COPY_DONE_SCOPE_KEY, parseMailCopyCompleted, } from './mail-copy.js';
|
|
12
|
-
import { CALLS_MAS_APPLIED_IMAGE_TAG_SCOPE_KEY, CALLS_SYNAPSE_APPLIED_IMAGE_TAG_SCOPE_KEY, ORG_MAIL_APPLIED_APEX_SCOPE_KEY, ORG_MAIL_APPLIED_HOST_SCOPE_KEY, ORG_MAIL_APPLIED_MODE_SCOPE_KEY, ORG_MAIL_APPLIED_USES_DOMAIN_SCOPE_KEY, appEnvAppliedScopeKey, appSettingsOf, appStepKey, appliedAppDomains, appliedAppSettings, dbEnvAppliedScopeKey, resolveAppEnv, serviceDbEnvAppliedScopeKey, versionNotAppliedLine, } from './platform-diff.js';
|
|
12
|
+
import { CALLS_MAS_APPLIED_IMAGE_TAG_SCOPE_KEY, CALLS_SYNAPSE_APPLIED_IMAGE_TAG_SCOPE_KEY, ORG_MAIL_APPLIED_APEX_SCOPE_KEY, ORG_MAIL_APPLIED_HOST_SCOPE_KEY, ORG_MAIL_APPLIED_MODE_SCOPE_KEY, ORG_MAIL_APPLIED_USES_DOMAIN_SCOPE_KEY, appEnvAppliedScopeKey, appFolderDiffers, appSettingsOf, appStepKey, appliedAppDomains, appliedAppSettings, dbEnvAppliedScopeKey, resolveAppEnv, serviceDbEnvAppliedScopeKey, versionNotAppliedLine, } from './platform-diff.js';
|
|
13
13
|
import { DEFAULT_ORG_PLATFORM_READINESS, } from './platform-org.js';
|
|
14
14
|
import { PLATFORM_AUTH_BROKER_MAIL_KEY_REF } from './platform-providers.js';
|
|
15
15
|
import { appLabelsAppliedScopeKey, entryLabelHash, entryLabelSpec } from './proxy-labels.js';
|
|
@@ -326,7 +326,7 @@ export function computePlanDetail(org, live, recordedIds = {}, agoraHost = NO_AG
|
|
|
326
326
|
// #173: register the org's own app with the platform's jobs scheduler.
|
|
327
327
|
// After Agora, so a failing registration can never stop Agora from
|
|
328
328
|
// provisioning (`runSteps` stops at the first failing step).
|
|
329
|
-
const jobsRegistration = planJobsRegistration(org, recordedIds, jobsAvailable || platformReadiness.jobsReady, platformReadiness.missingKeys.jobs, platformReadiness.noKeyReader);
|
|
329
|
+
const jobsRegistration = planJobsRegistration(org, recordedIds, jobsAvailable || platformReadiness.jobsReady, platformReadiness.missingKeys.jobs, platformReadiness.noKeyReader, platformReadiness.jobsUrlMissing ?? false);
|
|
330
330
|
if (jobsRegistration.step)
|
|
331
331
|
plannedSteps.push(jobsRegistration.step);
|
|
332
332
|
// F8 (#318), after Agora and Boule, not right after the zone: a route
|
|
@@ -2186,7 +2186,7 @@ function planApp(org, entryKey, byKey, claimed, recordedIds, plannedSteps, authI
|
|
|
2186
2186
|
}
|
|
2187
2187
|
// A changed hostname reaches an app that is already live: its domains are
|
|
2188
2188
|
// patched and it is redeployed.
|
|
2189
|
-
if (JSON.stringify(entryHostnames(org, entry)) !==
|
|
2189
|
+
if (JSON.stringify(tunnelDomainList(entryHostnames(org, entry))) !==
|
|
2190
2190
|
JSON.stringify(appliedAppDomains(org, recordedIds, entryKey))) {
|
|
2191
2191
|
return {
|
|
2192
2192
|
step: {
|
|
@@ -2229,7 +2229,7 @@ function planApp(org, entryKey, byKey, claimed, recordedIds, plannedSteps, authI
|
|
|
2229
2229
|
key: blockedKey('port'),
|
|
2230
2230
|
});
|
|
2231
2231
|
}
|
|
2232
|
-
if (want
|
|
2232
|
+
if (appFolderDiffers(want, have) ||
|
|
2233
2233
|
want.autoDeploy !== have.autoDeploy ||
|
|
2234
2234
|
(want.port !== null && want.port !== have.port)) {
|
|
2235
2235
|
return {
|
|
@@ -2344,7 +2344,7 @@ function planApps(org, byKey, claimed, recordedIds, plannedSteps, authIssuer, re
|
|
|
2344
2344
|
* {@link planSite}'s own `hostname` local does: `DeclaredOrg.domain` once
|
|
2345
2345
|
* `hosting.usesDomain` is true, `<slug>.oikos.host` otherwise.
|
|
2346
2346
|
*/
|
|
2347
|
-
function planJobsRegistration(org, recordedIds, jobsAvailable, missingKeyRef = null, noKeyReader = false) {
|
|
2347
|
+
function planJobsRegistration(org, recordedIds, jobsAvailable, missingKeyRef = null, noKeyReader = false, addressMissing = false) {
|
|
2348
2348
|
if (!org.hosting.enabled)
|
|
2349
2349
|
return { step: null, blocked: null };
|
|
2350
2350
|
const key = resourceKey('jobs', 'registration', org.id);
|
|
@@ -2366,11 +2366,13 @@ function planJobsRegistration(org, recordedIds, jobsAvailable, missingKeyRef = n
|
|
|
2366
2366
|
if (!jobsAvailable) {
|
|
2367
2367
|
return {
|
|
2368
2368
|
step: null,
|
|
2369
|
-
blocked:
|
|
2370
|
-
? `${org.slug}'s jobs registration is blocked, not planned: the
|
|
2371
|
-
:
|
|
2372
|
-
?
|
|
2373
|
-
:
|
|
2369
|
+
blocked: addressMissing
|
|
2370
|
+
? `${org.slug}'s jobs registration is blocked, not planned: the platform's jobs scheduler has not recorded its internal address yet; apply the platform's jobs step first`
|
|
2371
|
+
: !missingKeyRef
|
|
2372
|
+
? `${org.slug}'s jobs registration is blocked, not planned: the host supplies no jobs scheduler`
|
|
2373
|
+
: `${org.slug}'s jobs registration is blocked, not planned: ${noKeyReader
|
|
2374
|
+
? `the host gave no held-keys reader and no jobs scheduler port, so the platform's jobs admin token ${missingKeyRef} cannot be checked`
|
|
2375
|
+
: `the platform's jobs admin token ${missingKeyRef} is not held yet`}`,
|
|
2374
2376
|
};
|
|
2375
2377
|
}
|
|
2376
2378
|
const hostname = appHostname(org);
|
package/dist/plan/executor.d.ts
CHANGED
|
@@ -117,8 +117,6 @@ export interface CoolifyPort {
|
|
|
117
117
|
ports_exposes?: string;
|
|
118
118
|
/** `false` for an internal entry: Coolify makes no domain for it. */
|
|
119
119
|
autogenerate_domain?: boolean;
|
|
120
|
-
/** The folder a `static` entry serves (`HostingEntry.folder`). */
|
|
121
|
-
publish_directory?: string;
|
|
122
120
|
/** Whether a push to `git_branch` deploys (`HostingEntry.autoDeploy`). */
|
|
123
121
|
is_auto_deploy_enabled?: boolean;
|
|
124
122
|
/**
|
package/dist/plan/executor.js
CHANGED
|
@@ -20,10 +20,10 @@ import { AI_ENTRY_RENEW_WINDOW_DAYS, entryAiAppliedScopeKey, entryAiClampedScope
|
|
|
20
20
|
import { BOULE_EDGE_ENTRY_KEY, EDGE_ENTRY_KEY_DAYS, EDGE_RESERVED_LABELS_APPLIED_SCOPE_KEY, ENTRY_EDGE_KEY_DELIVERY_ATTEMPTS, edgeKeyAppRecordKey, entryEdgeMintedAtScopeKey, entryEdgeRetiredScopeKey, entryEdgeUndeliveredScopeKey, } from './entry-edge-key.js';
|
|
21
21
|
import { FILES_AGORA_KEY_SCOPE_KEY, FILES_AGORA_UNDELIVERED_SCOPE_KEY, FILES_KEY_DAYS, FILES_KEY_RENEW_WINDOW_DAYS, FILES_ORG_OWNER_SCOPE_KEY, FILES_ROOT_EXPIRES_SCOPE_KEY, FILES_ROOT_PREVIOUS_SCOPE_KEY, FILES_ROOT_UNDELIVERED_SCOPE_KEY, FILES_ROOT_UNREVOKED_SCOPE_KEY, entryFilesAppliedScopeKey, entryFilesExpiresScopeKey, entryFilesMinterScopeKey, entryFilesPendingExpiresScopeKey, entryFilesRetiredScopeKey, entryFilesUndeliveredScopeKey, entryFilesUnrevokedScopeKey, entryFilesWrittenScopeKey, filesOwnerUserId, parseIdList as parseFilesIdList, } from './entry-files-key.js';
|
|
22
22
|
import { healthCheckSettings } from './health-check-settings.js';
|
|
23
|
-
import { appHostname,
|
|
23
|
+
import { appHostname, coolifyTunnelDomains, entryHostnames, tunnelDnsHostnames, tunnelDomainList, tunnelIngressHostnames, } from './hostnames.js';
|
|
24
24
|
import { LOGIN_BINDING_SCOPE_KEY, LOGIN_HOST_PENDING_REMOVAL_SCOPE_KEY, LOGIN_HOST_SHARED_SCOPE_KEY, customersEntryKeys, loginBindingState, loginHostClientIds, pendingRemovalHosts, pendingRemovalState, } from './login-host.js';
|
|
25
25
|
import { MAIL_COPY_COMPLETED_SCOPE_KEY, MAIL_COPY_DONE_SCOPE_KEY, parseMailCopyCompleted, runMailCopyStep, } from './mail-copy.js';
|
|
26
|
-
import { CALLS_MAS_APPLIED_IMAGE_TAG_SCOPE_KEY, CALLS_SYNAPSE_APPLIED_IMAGE_TAG_SCOPE_KEY, ORG_MAIL_APPLIED_APEX_SCOPE_KEY, ORG_MAIL_APPLIED_HOST_SCOPE_KEY, ORG_MAIL_APPLIED_MODE_SCOPE_KEY, ORG_MAIL_APPLIED_USES_DOMAIN_SCOPE_KEY, appDomainsAppliedScopeKey, appEnvAppliedScopeKey, appNameFor, appSettingsAppliedScopeKey, appSettingsOf, appStepKey, appliedAppDomains, appliedAppSettings, dbEnvAppliedScopeKey, resolveAppEnv, serviceDbEnvAppliedScopeKey, } from './platform-diff.js';
|
|
26
|
+
import { CALLS_MAS_APPLIED_IMAGE_TAG_SCOPE_KEY, CALLS_SYNAPSE_APPLIED_IMAGE_TAG_SCOPE_KEY, ORG_MAIL_APPLIED_APEX_SCOPE_KEY, ORG_MAIL_APPLIED_HOST_SCOPE_KEY, ORG_MAIL_APPLIED_MODE_SCOPE_KEY, ORG_MAIL_APPLIED_USES_DOMAIN_SCOPE_KEY, appDomainsAppliedScopeKey, appEnvAppliedScopeKey, appFolderDiffers, appNameFor, appSettingsAppliedScopeKey, appSettingsOf, appStepKey, appliedAppDomains, appliedAppSettings, dbEnvAppliedScopeKey, resolveAppEnv, serviceDbEnvAppliedScopeKey, } from './platform-diff.js';
|
|
27
27
|
import { AUTH_LOGIN_PORT, PLATFORM_AUTH_BROKER_MAIL_KEY_REF, PLATFORM_AUTH_LOGIN_INTERNAL_NAME, } from './platform-providers.js';
|
|
28
28
|
import { appLabelsAppliedScopeKey, entryLabelHash, entryLabelSpec, entryProxyLabels, sameLabelSets, } from './proxy-labels.js';
|
|
29
29
|
import { entryWorkerAppliedScopeKey, entryWorkerExclusionsScopeKey, entryWorkerRetiredScopeKey, entryWorkerRouteIdScopeKey, entryWorkerRoutePatternScopeKey, entryWorkerRoutePendingScopeKey, entryWorkerRouteZoneScopeKey, entryWorkerUploadedScopeKey, } from './worker.js';
|
|
@@ -1787,11 +1787,11 @@ export class FoundryStepExecutor {
|
|
|
1787
1787
|
// An entry with no names is internal: the other entries reach it as
|
|
1788
1788
|
// `<slug>-<key>`, and it gets no domain, route or DNS record.
|
|
1789
1789
|
const names = entryHostnames(org, entry);
|
|
1790
|
+
const wantDomains = tunnelDomainList(names);
|
|
1790
1791
|
const isStatic = entry.buildPack === 'static';
|
|
1791
|
-
const folderField = isStatic ? 'publish_directory' : 'base_directory';
|
|
1792
1792
|
const settingsKey = appSettingsAppliedScopeKey(entry.key);
|
|
1793
1793
|
const domainsKey = appDomainsAppliedScopeKey(entry.key);
|
|
1794
|
-
let
|
|
1794
|
+
let haveDomains;
|
|
1795
1795
|
let have;
|
|
1796
1796
|
let appId;
|
|
1797
1797
|
// A stopped app with no settings marker was never deployed successfully
|
|
@@ -1812,7 +1812,7 @@ export class FoundryStepExecutor {
|
|
|
1812
1812
|
neverDeployed = context.recordedIds()[settingsKey] === undefined;
|
|
1813
1813
|
}
|
|
1814
1814
|
have = appliedAppSettings(context.recordedIds(), entry.key);
|
|
1815
|
-
|
|
1815
|
+
haveDomains = appliedAppDomains(org, context.recordedIds(), entry.key);
|
|
1816
1816
|
}
|
|
1817
1817
|
else {
|
|
1818
1818
|
const server = await this.resolveServer(org, context);
|
|
@@ -1827,12 +1827,15 @@ export class FoundryStepExecutor {
|
|
|
1827
1827
|
git_branch: entry.branch,
|
|
1828
1828
|
build_pack: entry.buildPack,
|
|
1829
1829
|
name: appNameFor(org, entry),
|
|
1830
|
-
domains: names.length > 0 ?
|
|
1830
|
+
domains: names.length > 0 ? coolifyTunnelDomains(names) : undefined,
|
|
1831
1831
|
autogenerate_domain: names.length > 0 ? undefined : false,
|
|
1832
1832
|
// A static site is served by Coolify's own web server on port 80.
|
|
1833
1833
|
ports_exposes: isStatic ? '80' : entry.port === null ? undefined : String(entry.port),
|
|
1834
1834
|
is_auto_deploy_enabled: entry.autoDeploy,
|
|
1835
|
-
|
|
1835
|
+
// Also the folder a static entry serves: Coolify's static build
|
|
1836
|
+
// copies `base_directory`, and reads `publish_directory` only for a
|
|
1837
|
+
// built app.
|
|
1838
|
+
base_directory: entry.folder === null ? undefined : `/${entry.folder}`,
|
|
1836
1839
|
// Only the shared standard app builds from this repo; a bespoke
|
|
1837
1840
|
// app's Dockerfile is at its own repo's root.
|
|
1838
1841
|
dockerfile_location: entry.repo ? undefined : DEFAULT_APP_DOCKERFILE,
|
|
@@ -1840,15 +1843,15 @@ export class FoundryStepExecutor {
|
|
|
1840
1843
|
appId = created.uuid;
|
|
1841
1844
|
// Create sent every setting, so nothing is left to patch.
|
|
1842
1845
|
have = appSettingsOf(entry);
|
|
1843
|
-
|
|
1846
|
+
haveDomains = wantDomains;
|
|
1844
1847
|
}
|
|
1845
1848
|
// Patch what differs from what the app has. A crash before the marker is
|
|
1846
1849
|
// recorded below leaves the old one, so the retry patches the same
|
|
1847
1850
|
// difference again.
|
|
1848
1851
|
const want = appSettingsOf(entry);
|
|
1849
1852
|
const patch = {};
|
|
1850
|
-
if (want
|
|
1851
|
-
patch
|
|
1853
|
+
if (appFolderDiffers(want, have))
|
|
1854
|
+
patch.base_directory = want.folder ? `/${want.folder}` : '/';
|
|
1852
1855
|
if (want.port !== have.port && want.port !== null && !isStatic) {
|
|
1853
1856
|
patch.ports_exposes = String(want.port);
|
|
1854
1857
|
}
|
|
@@ -1869,10 +1872,12 @@ export class FoundryStepExecutor {
|
|
|
1869
1872
|
Object.assign(patch, healthCheckSettings(entry.healthPath ?? (isStatic ? '/' : '/api/health'), healthCheckPort));
|
|
1870
1873
|
}
|
|
1871
1874
|
// Create names the app after `domains`; a changed name reaches a live app
|
|
1872
|
-
// here (rule 3)
|
|
1873
|
-
//
|
|
1874
|
-
|
|
1875
|
-
|
|
1875
|
+
// here (rule 3), and so does an app made with the `https://` form, whose
|
|
1876
|
+
// redirect loops behind the tunnel. The internal name is set after
|
|
1877
|
+
// create, as the calls stack's apps do.
|
|
1878
|
+
if (JSON.stringify(wantDomains) !== JSON.stringify(haveDomains)) {
|
|
1879
|
+
patch.domains = coolifyTunnelDomains(names);
|
|
1880
|
+
}
|
|
1876
1881
|
if (names.length === 0 && (!existing || patch.domains !== undefined)) {
|
|
1877
1882
|
patch.custom_internal_name = appNameFor(org, entry);
|
|
1878
1883
|
}
|
|
@@ -1901,7 +1906,6 @@ export class FoundryStepExecutor {
|
|
|
1901
1906
|
await this.ports.coolify.updateApplication(appId, patch);
|
|
1902
1907
|
// The container builds and listens from these only at its next deploy.
|
|
1903
1908
|
const settingsNeedDeploy = Boolean(patch.base_directory ||
|
|
1904
|
-
patch.publish_directory ||
|
|
1905
1909
|
patch.ports_exposes ||
|
|
1906
1910
|
patch.domains !== undefined ||
|
|
1907
1911
|
patch.custom_internal_name ||
|
|
@@ -1969,7 +1973,7 @@ export class FoundryStepExecutor {
|
|
|
1969
1973
|
// A port taken back to null was not patched, so the marker keeps the old
|
|
1970
1974
|
// one and `planApp` reports it instead of planning it.
|
|
1971
1975
|
context.recordScopeId(settingsKey, JSON.stringify({ ...want, port: want.port ?? have.port }));
|
|
1972
|
-
context.recordScopeId(domainsKey, JSON.stringify(
|
|
1976
|
+
context.recordScopeId(domainsKey, JSON.stringify(wantDomains));
|
|
1973
1977
|
if (wantLabelsHash)
|
|
1974
1978
|
context.recordScopeId(labelsKey, wantLabelsHash);
|
|
1975
1979
|
context.recordScopeId(appEnvAppliedScopeKey(entry.key), appEnv.applied);
|
|
@@ -3043,7 +3047,7 @@ export class FoundryStepExecutor {
|
|
|
3043
3047
|
git_branch: 'main',
|
|
3044
3048
|
build_pack: 'dockerfile',
|
|
3045
3049
|
name: input.name,
|
|
3046
|
-
domains:
|
|
3050
|
+
domains: coolifyTunnelDomains([input.domain]),
|
|
3047
3051
|
dockerfile_location: input.dockerfileLocation,
|
|
3048
3052
|
});
|
|
3049
3053
|
return { appId: created.uuid, existed: false };
|
|
@@ -3739,7 +3743,7 @@ export class FoundryStepExecutor {
|
|
|
3739
3743
|
git_branch: 'main',
|
|
3740
3744
|
build_pack: 'dockerfile',
|
|
3741
3745
|
name,
|
|
3742
|
-
domains:
|
|
3746
|
+
domains: coolifyTunnelDomains([callsHostnameFor(org)]),
|
|
3743
3747
|
dockerfile_location: CALLS_SERVICE_DOCKERFILE,
|
|
3744
3748
|
}));
|
|
3745
3749
|
// A fixed internal name, like Synapse and MAS above, so the org's Agora
|
|
@@ -4022,7 +4026,7 @@ export class FoundryStepExecutor {
|
|
|
4022
4026
|
git_branch: 'main',
|
|
4023
4027
|
build_pack: 'dockerfile',
|
|
4024
4028
|
name,
|
|
4025
|
-
domains:
|
|
4029
|
+
domains: coolifyTunnelDomains([agoraHostnameFor(org)]),
|
|
4026
4030
|
dockerfile_location: AGORA_APP_DOCKERFILE,
|
|
4027
4031
|
ports_exposes: AGORA_APP_PORTS_EXPOSES,
|
|
4028
4032
|
}));
|
|
@@ -4132,7 +4136,7 @@ export class FoundryStepExecutor {
|
|
|
4132
4136
|
git_branch: 'main',
|
|
4133
4137
|
build_pack: 'dockerfile',
|
|
4134
4138
|
name,
|
|
4135
|
-
domains:
|
|
4139
|
+
domains: coolifyTunnelDomains([bouleHostnameFor(org)]),
|
|
4136
4140
|
dockerfile_location: BOULE_APP_DOCKERFILE,
|
|
4137
4141
|
}));
|
|
4138
4142
|
// Every app foundry deploys keeps its own logs (#75), reused as-is here.
|
package/dist/plan/hostnames.d.ts
CHANGED
|
@@ -20,10 +20,22 @@ export declare function wildcardHostname(org: DeclaredOrg): string | null;
|
|
|
20
20
|
export declare function appHostname(org: DeclaredOrg): string;
|
|
21
21
|
/**
|
|
22
22
|
* Coolify's `domains` field: comma-separated full URLs. A bare hostname is a
|
|
23
|
-
* 422 ("Invalid URL"), so every write names each host as
|
|
23
|
+
* 422 ("Invalid URL"), so every write names each host as a URL. This helper
|
|
24
|
+
* writes `https://<host>` for the platform's apps; a company's apps are reached
|
|
25
|
+
* through `coolify-proxy:80` and use {@link coolifyTunnelDomains} (`http://`).
|
|
24
26
|
* Foundry's own records and proxy labels keep the bare names.
|
|
25
27
|
*/
|
|
26
28
|
export declare function coolifyDomains(names: readonly string[]): string;
|
|
29
|
+
/**
|
|
30
|
+
* The `domains` entries of a company's app, one URL per host. Every company
|
|
31
|
+
* tunnel delivers plain http to Traefik (`COOLIFY_PROXY_ORIGIN`), and Coolify
|
|
32
|
+
* puts a redirect-to-https on the http entry point of an `https://` domain,
|
|
33
|
+
* so a request through the tunnel would be redirected to itself forever. The
|
|
34
|
+
* `http://` form is served as it is; Cloudflare terminates TLS in front.
|
|
35
|
+
*/
|
|
36
|
+
export declare function tunnelDomainList(names: readonly string[]): string[];
|
|
37
|
+
/** {@link tunnelDomainList} as Coolify's comma-separated `domains` field. */
|
|
38
|
+
export declare function coolifyTunnelDomains(names: readonly string[]): string;
|
|
27
39
|
/** What `domains` held on an `app` entry's Coolify app before entries had hostnames. */
|
|
28
40
|
export declare function legacyAppHostname(org: DeclaredOrg): string;
|
|
29
41
|
/**
|
package/dist/plan/hostnames.js
CHANGED
|
@@ -37,12 +37,28 @@ export function appHostname(org) {
|
|
|
37
37
|
}
|
|
38
38
|
/**
|
|
39
39
|
* Coolify's `domains` field: comma-separated full URLs. A bare hostname is a
|
|
40
|
-
* 422 ("Invalid URL"), so every write names each host as
|
|
40
|
+
* 422 ("Invalid URL"), so every write names each host as a URL. This helper
|
|
41
|
+
* writes `https://<host>` for the platform's apps; a company's apps are reached
|
|
42
|
+
* through `coolify-proxy:80` and use {@link coolifyTunnelDomains} (`http://`).
|
|
41
43
|
* Foundry's own records and proxy labels keep the bare names.
|
|
42
44
|
*/
|
|
43
45
|
export function coolifyDomains(names) {
|
|
44
46
|
return names.map((name) => `https://${name}`).join(',');
|
|
45
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* The `domains` entries of a company's app, one URL per host. Every company
|
|
50
|
+
* tunnel delivers plain http to Traefik (`COOLIFY_PROXY_ORIGIN`), and Coolify
|
|
51
|
+
* puts a redirect-to-https on the http entry point of an `https://` domain,
|
|
52
|
+
* so a request through the tunnel would be redirected to itself forever. The
|
|
53
|
+
* `http://` form is served as it is; Cloudflare terminates TLS in front.
|
|
54
|
+
*/
|
|
55
|
+
export function tunnelDomainList(names) {
|
|
56
|
+
return names.map((name) => `http://${name}`);
|
|
57
|
+
}
|
|
58
|
+
/** {@link tunnelDomainList} as Coolify's comma-separated `domains` field. */
|
|
59
|
+
export function coolifyTunnelDomains(names) {
|
|
60
|
+
return tunnelDomainList(names).join(',');
|
|
61
|
+
}
|
|
46
62
|
/** What `domains` held on an `app` entry's Coolify app before entries had hostnames. */
|
|
47
63
|
export function legacyAppHostname(org) {
|
|
48
64
|
return org.hosting.usesDomain && org.domain ? org.domain : `${org.slug}.oikos.host`;
|
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_INTERNAL_URL_SCOPE_KEY, PLATFORM_EDGE_KEY_REF, PLATFORM_EDGE_CLOUDFLARE_TOKEN_REF, } from './platform-diff.js';
|
|
8
|
+
export { planPlatform, toPlatformPlan, JOBS_INTERNAL_URL_SCOPE_KEY, 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';
|
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_INTERNAL_URL_SCOPE_KEY, PLATFORM_EDGE_KEY_REF, PLATFORM_EDGE_CLOUDFLARE_TOKEN_REF, } from './platform-diff.js';
|
|
4
|
+
export { planPlatform, toPlatformPlan, JOBS_INTERNAL_URL_SCOPE_KEY, 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';
|
|
@@ -482,23 +482,36 @@ export interface AppSettings {
|
|
|
482
482
|
folder: string | null;
|
|
483
483
|
port: number | null;
|
|
484
484
|
autoDeploy: boolean;
|
|
485
|
+
/**
|
|
486
|
+
* Set for a `static` entry whose folder is written as `base_directory`,
|
|
487
|
+
* the field Coolify's static build serves from. A static marker without it
|
|
488
|
+
* was written as `publish_directory`, which that build ignores (it served
|
|
489
|
+
* the repo root), so the app is patched and redeployed once.
|
|
490
|
+
*/
|
|
491
|
+
staticFolderIn?: 'base_directory';
|
|
485
492
|
}
|
|
486
493
|
export declare function appSettingsOf(entry: HostingEntry): AppSettings;
|
|
494
|
+
/** Whether the app's folder is not yet where Coolify reads it: a changed folder, or a static one written to the old field. */
|
|
495
|
+
export declare function appFolderDiffers(want: AppSettings, have: AppSettings): boolean;
|
|
487
496
|
/** What an app that predates the marker has: Coolify's own defaults, so it plans no change. */
|
|
488
497
|
export declare const DEFAULT_APP_SETTINGS: AppSettings;
|
|
489
498
|
export declare function appSettingsAppliedScopeKey(entryKey: string): string;
|
|
490
499
|
/** The settings the app has: the marker, or the defaults when there is none. */
|
|
491
500
|
export declare function appliedAppSettings(recorded: Readonly<Record<string, string>>, entryKey: string): AppSettings;
|
|
492
501
|
/**
|
|
493
|
-
* The
|
|
494
|
-
* array (`[]` for an internal entry). `planApp`
|
|
495
|
-
* redeploy, when
|
|
502
|
+
* The URLs `coolifyApp` last put in the entry's Coolify `domains`
|
|
503
|
+
* (`tunnelDomainList`), as a JSON array (`[]` for an internal entry). `planApp`
|
|
504
|
+
* plans the step again, with a redeploy, when the wanted URLs differ from it.
|
|
505
|
+
* A marker of bare names, written before the `http://` form, never equals
|
|
506
|
+
* them, so an app made with the `https://` form is patched once.
|
|
496
507
|
*/
|
|
497
508
|
export declare function appDomainsAppliedScopeKey(entryKey: string): string;
|
|
498
509
|
/**
|
|
499
|
-
* The
|
|
500
|
-
* name it was created with (the `app` entry's address, nothing for any
|
|
501
|
-
* entry)
|
|
510
|
+
* The domains the app has: the marker, or for an app with no marker the one
|
|
511
|
+
* bare name it was created with (the `app` entry's address, nothing for any
|
|
512
|
+
* other entry). A bare name is never a URL `tunnelDomainList` writes, so an
|
|
513
|
+
* app with no marker is patched once: the scheme it was created with is
|
|
514
|
+
* unknown, bare before 4b2380f and `https://` after it.
|
|
502
515
|
*/
|
|
503
516
|
export declare function appliedAppDomains(org: DeclaredOrg, recorded: Readonly<Record<string, string>>, entryKey: string): string[];
|
|
504
517
|
/** Whether the secret sources can be filled right now: the mail key is held, and a new shared secret can be held. */
|
|
@@ -935,6 +948,13 @@ export declare function planPlatform(platform: DeclaredPlatform, live: LiveResou
|
|
|
935
948
|
* it, so the planner and the executor/reader can never key it apart).
|
|
936
949
|
*/
|
|
937
950
|
export declare const JOBS_SCHEDULER_KEY = "jobs";
|
|
951
|
+
/**
|
|
952
|
+
* The scheduler's address on the Coolify network (`http://<custom_internal_name>:8080`),
|
|
953
|
+
* the value the org steps' jobs port and Archon's `JOBS_URL` are read from.
|
|
954
|
+
* Not a secret. The scheduler's public hostname has no tunnel and no DNS
|
|
955
|
+
* record, so nothing on the platform can reach it there.
|
|
956
|
+
*/
|
|
957
|
+
export declare const JOBS_INTERNAL_URL_SCOPE_KEY = "jobs_internal_url";
|
|
938
958
|
/**
|
|
939
959
|
* #257: the six platform databases foundry itself creates, each keyed by
|
|
940
960
|
* the scope id `platform-providers.ts`'s own `createPostgresDatabase` call
|
|
@@ -520,7 +520,17 @@ export function serviceDbEnvAppliedScopeKey(service) {
|
|
|
520
520
|
return `service_db_env_applied.${service}`;
|
|
521
521
|
}
|
|
522
522
|
export function appSettingsOf(entry) {
|
|
523
|
-
return {
|
|
523
|
+
return {
|
|
524
|
+
folder: entry.folder,
|
|
525
|
+
port: entry.port,
|
|
526
|
+
autoDeploy: entry.autoDeploy,
|
|
527
|
+
...(entry.buildPack === 'static' ? { staticFolderIn: 'base_directory' } : {}),
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
/** Whether the app's folder is not yet where Coolify reads it: a changed folder, or a static one written to the old field. */
|
|
531
|
+
export function appFolderDiffers(want, have) {
|
|
532
|
+
return (want.folder !== have.folder ||
|
|
533
|
+
(want.folder !== null && want.staticFolderIn !== have.staticFolderIn));
|
|
524
534
|
}
|
|
525
535
|
/** What an app that predates the marker has: Coolify's own defaults, so it plans no change. */
|
|
526
536
|
export const DEFAULT_APP_SETTINGS = { folder: null, port: null, autoDeploy: true };
|
|
@@ -533,17 +543,21 @@ export function appliedAppSettings(recorded, entryKey) {
|
|
|
533
543
|
return marker ? JSON.parse(marker) : DEFAULT_APP_SETTINGS;
|
|
534
544
|
}
|
|
535
545
|
/**
|
|
536
|
-
* The
|
|
537
|
-
* array (`[]` for an internal entry). `planApp`
|
|
538
|
-
* redeploy, when
|
|
546
|
+
* The URLs `coolifyApp` last put in the entry's Coolify `domains`
|
|
547
|
+
* (`tunnelDomainList`), as a JSON array (`[]` for an internal entry). `planApp`
|
|
548
|
+
* plans the step again, with a redeploy, when the wanted URLs differ from it.
|
|
549
|
+
* A marker of bare names, written before the `http://` form, never equals
|
|
550
|
+
* them, so an app made with the `https://` form is patched once.
|
|
539
551
|
*/
|
|
540
552
|
export function appDomainsAppliedScopeKey(entryKey) {
|
|
541
553
|
return `app_domains_applied.${entryKey}`;
|
|
542
554
|
}
|
|
543
555
|
/**
|
|
544
|
-
* The
|
|
545
|
-
* name it was created with (the `app` entry's address, nothing for any
|
|
546
|
-
* entry)
|
|
556
|
+
* The domains the app has: the marker, or for an app with no marker the one
|
|
557
|
+
* bare name it was created with (the `app` entry's address, nothing for any
|
|
558
|
+
* other entry). A bare name is never a URL `tunnelDomainList` writes, so an
|
|
559
|
+
* app with no marker is patched once: the scheme it was created with is
|
|
560
|
+
* unknown, bare before 4b2380f and `https://` after it.
|
|
547
561
|
*/
|
|
548
562
|
export function appliedAppDomains(org, recorded, entryKey) {
|
|
549
563
|
const marker = recorded[appDomainsAppliedScopeKey(entryKey)];
|
|
@@ -1129,6 +1143,13 @@ function planApexZone(platform, byKey, claimed) {
|
|
|
1129
1143
|
* it, so the planner and the executor/reader can never key it apart).
|
|
1130
1144
|
*/
|
|
1131
1145
|
export const JOBS_SCHEDULER_KEY = 'jobs';
|
|
1146
|
+
/**
|
|
1147
|
+
* The scheduler's address on the Coolify network (`http://<custom_internal_name>:8080`),
|
|
1148
|
+
* the value the org steps' jobs port and Archon's `JOBS_URL` are read from.
|
|
1149
|
+
* Not a secret. The scheduler's public hostname has no tunnel and no DNS
|
|
1150
|
+
* record, so nothing on the platform can reach it there.
|
|
1151
|
+
*/
|
|
1152
|
+
export const JOBS_INTERNAL_URL_SCOPE_KEY = 'jobs_internal_url';
|
|
1132
1153
|
/**
|
|
1133
1154
|
* #173: the jobs scheduler (https://github.com/wtfalch/jobs) itself, as a
|
|
1134
1155
|
* platform-level Coolify application with its own database -- provision-only,
|
|
@@ -1159,8 +1180,11 @@ function planJobs(platform, byKey, claimed, observable, recordedIds) {
|
|
|
1159
1180
|
// So does a recorded pending deploy: the step wrote its env and died
|
|
1160
1181
|
// before the deploy finished.
|
|
1161
1182
|
const databaseStatus = live.config.databaseStatus;
|
|
1183
|
+
// So does a missing internal address: a scheduler applied before the
|
|
1184
|
+
// address was recorded gets it from the next apply.
|
|
1162
1185
|
if (live.config.ownerUrlMissing !== true &&
|
|
1163
1186
|
!recordedIds[JOBS_DEPLOY_PENDING_SCOPE_KEY] &&
|
|
1187
|
+
recordedIds[JOBS_INTERNAL_URL_SCOPE_KEY] &&
|
|
1164
1188
|
(typeof databaseStatus !== 'string' || databaseStatus.startsWith('running'))) {
|
|
1165
1189
|
return { step: null, blocked: null };
|
|
1166
1190
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { HttpJobsSchedulerClient } from './jobs-scheduler-http.js';
|
|
2
|
-
import { PLATFORM_AI_INTERNAL_URL_SCOPE_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, } from './platform-diff.js';
|
|
2
|
+
import { JOBS_INTERNAL_URL_SCOPE_KEY, PLATFORM_AI_INTERNAL_URL_SCOPE_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, } from './platform-diff.js';
|
|
3
3
|
import { FILES_MANAGEMENT_ISSUER, FILES_MANAGEMENT_KEY_ID, FILES_MANAGEMENT_KEY_REF, JOBS_ADMIN_TOKEN_REF, PLATFORM_AI_MANAGEMENT_KEY_REF, } from './platform-providers.js';
|
|
4
4
|
/**
|
|
5
5
|
* #177: what `platform-org.ts`'s readiness could not see between a plan and
|
|
@@ -44,8 +44,9 @@ export class PlatformOrgPorts {
|
|
|
44
44
|
if (!platform?.jobs.enabled) {
|
|
45
45
|
throw new PlatformOrgBlockedError('jobs', 'the platform does not declare a jobs scheduler');
|
|
46
46
|
}
|
|
47
|
+
const baseUrl = await this.recorded('jobs', JOBS_INTERNAL_URL_SCOPE_KEY);
|
|
47
48
|
const token = await this.held('jobs', JOBS_ADMIN_TOKEN_REF);
|
|
48
|
-
return new HttpJobsSchedulerClient(
|
|
49
|
+
return new HttpJobsSchedulerClient(baseUrl, token, this.options.fetch);
|
|
49
50
|
}
|
|
50
51
|
async authHostname() {
|
|
51
52
|
return (await this.options.store.get())?.auth.hostname ?? null;
|
|
@@ -87,8 +87,14 @@ export interface OrgPlatformReadiness {
|
|
|
87
87
|
callsReady: boolean;
|
|
88
88
|
/** The platform's own sign-in (#168), which every org's own ZITADEL org lives inside. */
|
|
89
89
|
authReady: boolean;
|
|
90
|
-
/** The jobs scheduler (#173) is enabled, applied, and its admin token is held. */
|
|
90
|
+
/** The jobs scheduler (#173) is enabled, applied, has recorded its internal address, and its admin token is held. */
|
|
91
91
|
jobsReady: boolean;
|
|
92
|
+
/**
|
|
93
|
+
* The scheduler is applied but has recorded no internal address, so the
|
|
94
|
+
* org's registration cannot reach it; `diff.ts` reports the registration
|
|
95
|
+
* blocked with that reason. Absent when false.
|
|
96
|
+
*/
|
|
97
|
+
jobsUrlMissing?: boolean;
|
|
92
98
|
/**
|
|
93
99
|
* #317 (F7) and #478: the edge service is declared `enabled`, its own
|
|
94
100
|
* `coolify:app` deploy step has applied (`provisionPlatformEdge`), and
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { JOBS_SCHEDULER_KEY, PLATFORM_AI_INTERNAL_URL_SCOPE_KEY, PLATFORM_AI_KEY, PLATFORM_AUTH_KEY, PLATFORM_EDGE_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, PLATFORM_FILES_KEY, PLATFORM_MAIL_INTERNAL_URL_SCOPE_KEY, PLATFORM_MAIL_KEY, PLATFORM_MAIL_ZITADEL_PROJECT_SCOPE_KEY, platformMailMxHost, } from './platform-diff.js';
|
|
1
|
+
import { JOBS_INTERNAL_URL_SCOPE_KEY, JOBS_SCHEDULER_KEY, PLATFORM_AI_INTERNAL_URL_SCOPE_KEY, PLATFORM_AI_KEY, PLATFORM_AUTH_KEY, PLATFORM_EDGE_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, PLATFORM_FILES_KEY, PLATFORM_MAIL_INTERNAL_URL_SCOPE_KEY, PLATFORM_MAIL_KEY, PLATFORM_MAIL_ZITADEL_PROJECT_SCOPE_KEY, platformMailMxHost, } from './platform-diff.js';
|
|
2
2
|
import { FILES_MANAGEMENT_KEY_REF, JOBS_ADMIN_TOKEN_REF, PLATFORM_AI_MANAGEMENT_KEY_REF, } from './platform-providers.js';
|
|
3
3
|
/**
|
|
4
4
|
* #443: the no-domain company mail apex -- `<slug>.oikos.host`, the same
|
|
@@ -103,6 +103,7 @@ edgePortPresent = false) {
|
|
|
103
103
|
const filesApplied = platform.files.enabled && coolifyAppApplied(platformRecordedIds, PLATFORM_FILES_KEY);
|
|
104
104
|
const aiApplied = platform.ai.enabled && coolifyAppApplied(platformRecordedIds, PLATFORM_AI_KEY);
|
|
105
105
|
const jobsApplied = platform.jobs.enabled && Boolean(platformRecordedIds[`coolify:jobs:${JOBS_SCHEDULER_KEY}`]);
|
|
106
|
+
const jobsUrl = platformRecordedIds[JOBS_INTERNAL_URL_SCOPE_KEY];
|
|
106
107
|
const edgeApplied = platform.edge.enabled && coolifyAppApplied(platformRecordedIds, PLATFORM_EDGE_KEY);
|
|
107
108
|
const missing = (applied, ref) => applied && heldRefs !== undefined && !heldRefs.has(ref) ? ref : null;
|
|
108
109
|
const missingKeys = {
|
|
@@ -129,7 +130,8 @@ edgePortPresent = false) {
|
|
|
129
130
|
aiUrl: aiApplied ? (platformRecordedIds[PLATFORM_AI_INTERNAL_URL_SCOPE_KEY] ?? null) : null,
|
|
130
131
|
callsReady: platform.sfu.enabled,
|
|
131
132
|
authReady: platform.auth.enabled && coolifyAppApplied(platformRecordedIds, PLATFORM_AUTH_KEY),
|
|
132
|
-
jobsReady: jobsApplied && missingKeys.jobs === null,
|
|
133
|
+
jobsReady: jobsApplied && jobsUrl !== undefined && missingKeys.jobs === null,
|
|
134
|
+
...(jobsApplied && jobsUrl === undefined ? { jobsUrlMissing: true } : {}),
|
|
133
135
|
edgeReady: edgeApplied && edgePortPresent,
|
|
134
136
|
edgeUrl: edgeApplied && edgePortPresent ? `https://${platform.edge.hostname}` : null,
|
|
135
137
|
missingKeys,
|
|
@@ -2,6 +2,7 @@ import type { CloudflareClient } from '@wtfalch/providers/cloudflare';
|
|
|
2
2
|
import type { DeclaredOrg } from '../org.js';
|
|
3
3
|
import type { DeclaredPlatform } from '../platform.js';
|
|
4
4
|
import type { DeclaredOrgStore, PlatformStore } from '../store.js';
|
|
5
|
+
import { type DeployHealthRuntime } from './deploy-health.js';
|
|
5
6
|
import type { StepAction } from './diff.js';
|
|
6
7
|
import type { AccessPort } from './edge-access.js';
|
|
7
8
|
import type { DnsPort } from './edge-dns.js';
|
|
@@ -117,7 +118,7 @@ export declare const JOBS_ADMIN_TOKEN_REF = "jobs:scheduler:adminToken";
|
|
|
117
118
|
* org's own app, reused here for the platform's one scheduler instead of once
|
|
118
119
|
* per org.
|
|
119
120
|
*/
|
|
120
|
-
type JobsCoolifyPort = Pick<CoolifyPort, 'findProjectByName' | 'createProject' | 'findOrCreateEnvironment' | 'listServers' | 'requireGithubApp' | 'findApplicationByName' | 'createGithubAppApplication' | 'listDatabases' | 'createPostgresDatabase' | 'getDatabase' | 'raw' | 'bulkUpsertEnvs' | 'listEnvs' | 'getApplication' | '
|
|
121
|
+
type JobsCoolifyPort = Pick<CoolifyPort, 'findProjectByName' | 'createProject' | 'findOrCreateEnvironment' | 'listServers' | 'requireGithubApp' | 'findApplicationByName' | 'createGithubAppApplication' | 'listDatabases' | 'createPostgresDatabase' | 'getDatabase' | 'raw' | 'bulkUpsertEnvs' | 'listEnvs' | 'getApplication' | 'updateApplication' | 'deployAndWait'>;
|
|
121
122
|
/**
|
|
122
123
|
* Options for {@link FoundryPlatformStepExecutor}'s constructor, replacing
|
|
123
124
|
* four positional arguments that shifted on every merge that added a
|
|
@@ -133,6 +134,8 @@ export interface FoundryPlatformStepExecutorOptions {
|
|
|
133
134
|
* an error). Needed by `coolify:jobs` for the scheduler's admin token.
|
|
134
135
|
*/
|
|
135
136
|
deliverSecret?: (ref: string, secret: string) => Promise<void>;
|
|
137
|
+
/** Clock, sleep and deployment wait for `coolify:jobs`; real ones by default. */
|
|
138
|
+
runtime?: DeployHealthRuntime;
|
|
136
139
|
sfu?: CallsSfuPlatformDeps;
|
|
137
140
|
/**
|
|
138
141
|
* #168: dependencies for `coolify:app` keyed {@link PLATFORM_AUTH_KEY} --
|
|
@@ -264,6 +267,7 @@ export declare class FoundryPlatformStepExecutor implements PlatformStepExecutor
|
|
|
264
267
|
private readonly declaredOrgStore;
|
|
265
268
|
private readonly coolify?;
|
|
266
269
|
private readonly deliverSecret?;
|
|
270
|
+
private readonly runtime;
|
|
267
271
|
private readonly sfu?;
|
|
268
272
|
private readonly auth?;
|
|
269
273
|
private readonly mail?;
|
|
@@ -7,8 +7,8 @@ import { ensureCloudflaredConnector } from './cloudflared-connector.js';
|
|
|
7
7
|
import { waitForDeploymentFinished, waitForHealthyVersion, waitForNewDeployment, } from './deploy-health.js';
|
|
8
8
|
import { ACCESS_POLICY_NAME, getDatabaseStarted, isNotFoundError, isRunningOrStarting, } from './executor.js';
|
|
9
9
|
import { healthCheckSettings } from './health-check-settings.js';
|
|
10
|
-
import { coolifyDomains, filesShareHostname } from './hostnames.js';
|
|
11
|
-
import { AUTH_ZITADEL_RUN_OPTIONS, JOBS_DEPLOY_PENDING_SCOPE_KEY, JOBS_SCHEDULER_KEY, PLATFORM_AI_APPLIED_GITREF_SCOPE_KEY, PLATFORM_AI_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_AI_DEPLOY_PENDING_SCOPE_KEY, PLATFORM_AI_INTERNAL_URL_SCOPE_KEY, PLATFORM_AI_KEY, PLATFORM_AI_MANAGEMENT_KEY_CONFLICT_SCOPE_KEY, PLATFORM_AI_MANAGEMENT_KEY_SCOPE_KEY, PLATFORM_AI_MANAGEMENT_KEY_UNDELIVERED_SCOPE_KEY, PLATFORM_AI_MIGRATE_BLOCKED_SCOPE_KEY, PLATFORM_AI_ORGANISATION_SCOPE_KEY, PLATFORM_AI_SIGNING_KEY_SET_SCOPE_KEY, PLATFORM_AI_SIGNING_KEY_UNDELIVERED_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_BROKER_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_OPERATOR_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_ROLLING_UPDATE_SCOPE_KEY, PLATFORM_ARCHON_CONNECTOR_APP_SCOPE_KEY, PLATFORM_ARCHON_COOLIFY_READ_TOKEN_REF, PLATFORM_ARCHON_DEPLOYMENT_SCOPE_KEY, PLATFORM_ARCHON_ENVS_DEPLOYED_SCOPE_KEY, PLATFORM_ARCHON_GATE_CLOSED_SCOPE_KEY, PLATFORM_ARCHON_INTEGRATIONS_KEY_SYNCED_SCOPE_KEY, PLATFORM_ARCHON_KEY, PLATFORM_ARCHON_MASTER_CLOUDFLARE_TOKEN_REF, PLATFORM_ARCHON_MASTER_COOLIFY_TOKEN_REF, PLATFORM_ARCHON_MASTER_COOLIFY_URL_REF, PLATFORM_AUTH_ADMIN_UNDELIVERED_SCOPE_KEY, PLATFORM_AUTH_ADMIN_USER_SCOPE_KEY, PLATFORM_AUTH_BROKER_DEPLOYED_SCOPE_KEY, PLATFORM_AUTH_BROKER_SCOPE_KEY, PLATFORM_AUTH_CONNECTOR_APP_SCOPE_KEY, PLATFORM_AUTH_INSTANCE_SCOPE_KEY, PLATFORM_AUTH_INSTANCE_UNDELIVERED_SCOPE_KEY, PLATFORM_AUTH_KEY, PLATFORM_AUTH_LOGIN_CLIENT_UNDELIVERED_SCOPE_KEY, PLATFORM_AUTH_LOGIN_CLIENT_USER_SCOPE_KEY, PLATFORM_AUTH_OWNER_INVITE_SCOPE_KEY, PLATFORM_AUTH_SMTP_SCOPE_KEY, PLATFORM_AUTH_ZITADEL_APPLIED_IMAGE_TAG_SCOPE_KEY, PLATFORM_AUTH_ZITADEL_RUN_OPTIONS_SCOPE_KEY, PLATFORM_BACKUP_DATABASE_SCOPE_KEYS, PLATFORM_BACKUP_SERVICE_KEYS, PLATFORM_EDGE_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_EDGE_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_EDGE_CLOUDFLARE_TOKEN_REF, PLATFORM_EDGE_INTERNAL_URL_SCOPE_KEY, PLATFORM_EDGE_KEY, PLATFORM_EDGE_KEY_REF, PLATFORM_EDGE_PLATFORM_KEY_SET_SCOPE_KEY, PLATFORM_EDGE_PLATFORM_KEY_UNDELIVERED_SCOPE_KEY, PLATFORM_FILES_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_FILES_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, PLATFORM_FILES_INTROSPECTION_SCOPE_KEY, PLATFORM_FILES_INTROSPECTION_UNDELIVERED_SCOPE_KEY, PLATFORM_FILES_KEY, PLATFORM_FILES_MANAGEMENT_KEY_SCOPE_KEY, PLATFORM_FILES_MANAGEMENT_UNDELIVERED_SCOPE_KEY, PLATFORM_FILES_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_HC_API_KEY_REF, PLATFORM_HC_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_HC_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_HC_KEY, PLATFORM_HEARTBEATS_APPLIED_SCOPE_KEY, PLATFORM_INTEGRATIONS_APPLIED_GITREF_SCOPE_KEY, PLATFORM_INTEGRATIONS_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY, PLATFORM_INTEGRATIONS_KEY_ID_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY_ROTATION_PREVIOUS_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY_ROTATION_REQUESTED_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY_ROTATION_UNDELIVERED_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY_SET_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY_UNDELIVERED_SCOPE_KEY, PLATFORM_INTEGRATIONS_RUNNER_KEYRING_DEPLOYED_SCOPE_KEY, PLATFORM_MAIL_ADMIN_KEY_SCOPE_KEY, PLATFORM_MAIL_ADMIN_UNDELIVERED_SCOPE_KEY, PLATFORM_MAIL_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_MAIL_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_MAIL_CONNECTOR_APP_SCOPE_KEY, PLATFORM_MAIL_INTERNAL_URL_SCOPE_KEY, PLATFORM_MAIL_KEY, PLATFORM_MAIL_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_UPTIME_HEARTBEAT_CHECK_NAME, archonBootstrapOperator, desiredHeartbeatCheckNames, heartbeatCheckUrlRef, parseBrokerDeployRecord, parseLoginClientRecord, platformBackupHeartbeatCheckName, platformMailMxHost, } from './platform-diff.js';
|
|
10
|
+
import { coolifyDomains, coolifyTunnelDomains, filesShareHostname } from './hostnames.js';
|
|
11
|
+
import { AUTH_ZITADEL_RUN_OPTIONS, JOBS_DEPLOY_PENDING_SCOPE_KEY, JOBS_INTERNAL_URL_SCOPE_KEY, JOBS_SCHEDULER_KEY, PLATFORM_AI_APPLIED_GITREF_SCOPE_KEY, PLATFORM_AI_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_AI_DEPLOY_PENDING_SCOPE_KEY, PLATFORM_AI_INTERNAL_URL_SCOPE_KEY, PLATFORM_AI_KEY, PLATFORM_AI_MANAGEMENT_KEY_CONFLICT_SCOPE_KEY, PLATFORM_AI_MANAGEMENT_KEY_SCOPE_KEY, PLATFORM_AI_MANAGEMENT_KEY_UNDELIVERED_SCOPE_KEY, PLATFORM_AI_MIGRATE_BLOCKED_SCOPE_KEY, PLATFORM_AI_ORGANISATION_SCOPE_KEY, PLATFORM_AI_SIGNING_KEY_SET_SCOPE_KEY, PLATFORM_AI_SIGNING_KEY_UNDELIVERED_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_BROKER_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_OPERATOR_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_ROLLING_UPDATE_SCOPE_KEY, PLATFORM_ARCHON_CONNECTOR_APP_SCOPE_KEY, PLATFORM_ARCHON_COOLIFY_READ_TOKEN_REF, PLATFORM_ARCHON_DEPLOYMENT_SCOPE_KEY, PLATFORM_ARCHON_ENVS_DEPLOYED_SCOPE_KEY, PLATFORM_ARCHON_GATE_CLOSED_SCOPE_KEY, PLATFORM_ARCHON_INTEGRATIONS_KEY_SYNCED_SCOPE_KEY, PLATFORM_ARCHON_KEY, PLATFORM_ARCHON_MASTER_CLOUDFLARE_TOKEN_REF, PLATFORM_ARCHON_MASTER_COOLIFY_TOKEN_REF, PLATFORM_ARCHON_MASTER_COOLIFY_URL_REF, PLATFORM_AUTH_ADMIN_UNDELIVERED_SCOPE_KEY, PLATFORM_AUTH_ADMIN_USER_SCOPE_KEY, PLATFORM_AUTH_BROKER_DEPLOYED_SCOPE_KEY, PLATFORM_AUTH_BROKER_SCOPE_KEY, PLATFORM_AUTH_CONNECTOR_APP_SCOPE_KEY, PLATFORM_AUTH_INSTANCE_SCOPE_KEY, PLATFORM_AUTH_INSTANCE_UNDELIVERED_SCOPE_KEY, PLATFORM_AUTH_KEY, PLATFORM_AUTH_LOGIN_CLIENT_UNDELIVERED_SCOPE_KEY, PLATFORM_AUTH_LOGIN_CLIENT_USER_SCOPE_KEY, PLATFORM_AUTH_OWNER_INVITE_SCOPE_KEY, PLATFORM_AUTH_SMTP_SCOPE_KEY, PLATFORM_AUTH_ZITADEL_APPLIED_IMAGE_TAG_SCOPE_KEY, PLATFORM_AUTH_ZITADEL_RUN_OPTIONS_SCOPE_KEY, PLATFORM_BACKUP_DATABASE_SCOPE_KEYS, PLATFORM_BACKUP_SERVICE_KEYS, PLATFORM_EDGE_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_EDGE_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_EDGE_CLOUDFLARE_TOKEN_REF, PLATFORM_EDGE_INTERNAL_URL_SCOPE_KEY, PLATFORM_EDGE_KEY, PLATFORM_EDGE_KEY_REF, PLATFORM_EDGE_PLATFORM_KEY_SET_SCOPE_KEY, PLATFORM_EDGE_PLATFORM_KEY_UNDELIVERED_SCOPE_KEY, PLATFORM_FILES_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_FILES_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, PLATFORM_FILES_INTROSPECTION_SCOPE_KEY, PLATFORM_FILES_INTROSPECTION_UNDELIVERED_SCOPE_KEY, PLATFORM_FILES_KEY, PLATFORM_FILES_MANAGEMENT_KEY_SCOPE_KEY, PLATFORM_FILES_MANAGEMENT_UNDELIVERED_SCOPE_KEY, PLATFORM_FILES_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_HC_API_KEY_REF, PLATFORM_HC_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_HC_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_HC_KEY, PLATFORM_HEARTBEATS_APPLIED_SCOPE_KEY, PLATFORM_INTEGRATIONS_APPLIED_GITREF_SCOPE_KEY, PLATFORM_INTEGRATIONS_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY, PLATFORM_INTEGRATIONS_KEY_ID_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY_ROTATION_PREVIOUS_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY_ROTATION_REQUESTED_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY_ROTATION_UNDELIVERED_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY_SET_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY_UNDELIVERED_SCOPE_KEY, PLATFORM_INTEGRATIONS_RUNNER_KEYRING_DEPLOYED_SCOPE_KEY, PLATFORM_MAIL_ADMIN_KEY_SCOPE_KEY, PLATFORM_MAIL_ADMIN_UNDELIVERED_SCOPE_KEY, PLATFORM_MAIL_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_MAIL_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_MAIL_CONNECTOR_APP_SCOPE_KEY, PLATFORM_MAIL_INTERNAL_URL_SCOPE_KEY, PLATFORM_MAIL_KEY, PLATFORM_MAIL_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_UPTIME_HEARTBEAT_CHECK_NAME, archonBootstrapOperator, desiredHeartbeatCheckNames, heartbeatCheckUrlRef, parseBrokerDeployRecord, parseLoginClientRecord, platformBackupHeartbeatCheckName, platformMailMxHost, } from './platform-diff.js';
|
|
12
12
|
import { applyCallsSfuPlatformStep } from './platform-sfu.js';
|
|
13
13
|
import { CreateInstanceError, createZitadelInstance, generateSystemApiKeypair, instanceWithoutBearerMessage, systemApiUsersEnv, } from './zitadel-system-api.js';
|
|
14
14
|
/** The key `runPlatformSteps` records the apex zone's id under (`system:kind:key`). */
|
|
@@ -71,6 +71,9 @@ export const JOBS_ADMIN_TOKEN_REF = 'jobs:scheduler:adminToken';
|
|
|
71
71
|
const PLATFORM_COOLIFY_PROJECT_NAME = 'platform';
|
|
72
72
|
/** https://github.com/wtfalch/jobs -- the scheduler's own repo, Dockerfile at its root (its own `Dockerfile`'s doc comment). */
|
|
73
73
|
const JOBS_SCHEDULER_REPO = 'wtfalch/jobs';
|
|
74
|
+
/** The scheduler's fixed `custom_internal_name` and the port it listens on (jobs' `PORT`, default 8080). */
|
|
75
|
+
const JOBS_INTERNAL_NAME = 'platform-jobs';
|
|
76
|
+
const JOBS_PORT = '8080';
|
|
74
77
|
/**
|
|
75
78
|
* The real {@link PlatformStepExecutor} for the two steps built so far:
|
|
76
79
|
* `cloudflare:zone` for the platform's apex (#167) and `coolify:jobs` for the
|
|
@@ -140,6 +143,7 @@ export class FoundryPlatformStepExecutor {
|
|
|
140
143
|
declaredOrgStore;
|
|
141
144
|
coolify;
|
|
142
145
|
deliverSecret;
|
|
146
|
+
runtime;
|
|
143
147
|
sfu;
|
|
144
148
|
auth;
|
|
145
149
|
mail;
|
|
@@ -156,6 +160,7 @@ export class FoundryPlatformStepExecutor {
|
|
|
156
160
|
this.declaredOrgStore = declaredOrgStore;
|
|
157
161
|
this.coolify = options.coolify;
|
|
158
162
|
this.deliverSecret = options.deliverSecret;
|
|
163
|
+
this.runtime = options.runtime ?? {};
|
|
159
164
|
this.sfu = options.sfu;
|
|
160
165
|
this.auth = options.auth;
|
|
161
166
|
this.mail = options.mail;
|
|
@@ -615,6 +620,12 @@ export class FoundryPlatformStepExecutor {
|
|
|
615
620
|
});
|
|
616
621
|
appId = created.uuid;
|
|
617
622
|
}
|
|
623
|
+
// Set on every run, like `platform:files`' runner: the name another
|
|
624
|
+
// container reaches the scheduler by. It only takes effect on a deploy.
|
|
625
|
+
await coolify.updateApplication(appId, {
|
|
626
|
+
custom_internal_name: JOBS_INTERNAL_NAME,
|
|
627
|
+
ports_exposes: JOBS_PORT,
|
|
628
|
+
});
|
|
618
629
|
const db = await getDatabaseStarted(coolify, dbId);
|
|
619
630
|
if (!db.internal_db_url) {
|
|
620
631
|
throw new Error(`platform step coolify:jobs: database ${dbId} has no internal_db_url yet; not deploying the scheduler without a database`);
|
|
@@ -670,12 +681,23 @@ export class FoundryPlatformStepExecutor {
|
|
|
670
681
|
// a redeploy, so deploy after a create or a new token. Also when it is not
|
|
671
682
|
// running, so a deploy that failed on an earlier run is retried.
|
|
672
683
|
const running = (await coolify.getApplication(appId)).status?.startsWith('running') ?? false;
|
|
673
|
-
|
|
674
|
-
|
|
684
|
+
// An app applied before its internal address was recorded has no fixed
|
|
685
|
+
// name yet, so it deploys once to get it; after that it deploys on the
|
|
686
|
+
// other conditions only.
|
|
687
|
+
const addressNew = !context.recordedIds()[JOBS_INTERNAL_URL_SCOPE_KEY];
|
|
688
|
+
// The address is recorded only once the deployment finished: the scheduler
|
|
689
|
+
// has no route that answers without its admin token (every other path is a
|
|
690
|
+
// 401, 404 or 405), so the deployment record is the whole check, as for
|
|
691
|
+
// the AI worker. A deploy that fails or times out throws here, records
|
|
692
|
+
// nothing and leaves the step planned.
|
|
693
|
+
if (!existingApp || tokenJustSet || pending || !running || addressNew) {
|
|
694
|
+
await waitForNewDeployment(coolify, appId, { kind: 'deployment-only' }, this.runtime, this.runtime.sleep ?? defaultSleep, 'platform step coolify:jobs', 'the scheduler keeps its old address');
|
|
695
|
+
}
|
|
675
696
|
if (pending) {
|
|
676
697
|
context.recordScopeId(JOBS_DEPLOY_PENDING_SCOPE_KEY, '');
|
|
677
698
|
await context.persist?.();
|
|
678
699
|
}
|
|
700
|
+
context.recordScopeId(JOBS_INTERNAL_URL_SCOPE_KEY, `http://${JOBS_INTERNAL_NAME}:${JOBS_PORT}`);
|
|
679
701
|
return { resourceId: appId };
|
|
680
702
|
}
|
|
681
703
|
}
|
|
@@ -3698,7 +3720,7 @@ async function collectArchonServiceInputs(platform, deps, recorded) {
|
|
|
3698
3720
|
const needs = service === 'jobs'
|
|
3699
3721
|
? {
|
|
3700
3722
|
stepKey: null,
|
|
3701
|
-
scopeKeys: [],
|
|
3723
|
+
scopeKeys: [JOBS_INTERNAL_URL_SCOPE_KEY],
|
|
3702
3724
|
refs: [JOBS_ADMIN_TOKEN_REF],
|
|
3703
3725
|
}
|
|
3704
3726
|
: ARCHON_SERVICE_INPUTS[service];
|
|
@@ -3784,7 +3806,11 @@ async function collectArchonServiceInputs(platform, deps, recorded) {
|
|
|
3784
3806
|
}, { key: 'INTEGRATIONS_MANAGEMENT_AUDIENCE', value: url });
|
|
3785
3807
|
}
|
|
3786
3808
|
else {
|
|
3787
|
-
envs.push(
|
|
3809
|
+
envs.push(
|
|
3810
|
+
// Known gap: an Archon that is already applied keeps its old `JOBS_URL`
|
|
3811
|
+
// until its step runs again, because the planner does not compare it.
|
|
3812
|
+
// Tracked in a separate issue.
|
|
3813
|
+
{ key: 'JOBS_URL', value: recorded[JOBS_INTERNAL_URL_SCOPE_KEY] }, { key: 'JOBS_ADMIN_TOKEN', value: held(JOBS_ADMIN_TOKEN_REF) });
|
|
3788
3814
|
}
|
|
3789
3815
|
}
|
|
3790
3816
|
return { envs };
|
|
@@ -7326,10 +7352,15 @@ export const PLATFORM_FILES_SHARE_DOMAINS_SCOPE_KEY = 'platform_files_share_doma
|
|
|
7326
7352
|
function normalHosts(hosts) {
|
|
7327
7353
|
return [...new Set((hosts ?? []).map((h) => h.trim().toLowerCase()).filter(Boolean))].sort();
|
|
7328
7354
|
}
|
|
7329
|
-
/**
|
|
7355
|
+
/**
|
|
7356
|
+
* The runner's Coolify `domains`, written only when the share set changed (a
|
|
7357
|
+
* runner nobody gave a share host keeps none). The share host is in the
|
|
7358
|
+
* company tunnel, which dials the proxy over plain http, so it is written
|
|
7359
|
+
* `http://`.
|
|
7360
|
+
*/
|
|
7330
7361
|
function filesShareDomainsPatch(recorded, shareDomains) {
|
|
7331
7362
|
return shareDomainsChanged(recorded, shareDomains)
|
|
7332
|
-
? { domains:
|
|
7363
|
+
? { domains: coolifyTunnelDomains(normalHosts(shareDomains)) }
|
|
7333
7364
|
: {};
|
|
7334
7365
|
}
|
|
7335
7366
|
/**
|