@wtfalch/foundry 0.39.1 → 0.39.2
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/plan/executor.js
CHANGED
|
@@ -4431,18 +4431,27 @@ export class FoundryStepExecutor {
|
|
|
4431
4431
|
ecKeyPem: secrets.value.masEcKeyPem,
|
|
4432
4432
|
}));
|
|
4433
4433
|
const introspection = await this.ensureChatIntrospection(org, zitadelOrgId, project.id, service.appId, context);
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4434
|
+
// The internal token mounts chat-service's `/_chat/internal/*` routes, which the platform uptime
|
|
4435
|
+
// step calls whether or not calls are on. A usable one (also the one calls-chat shares) is kept;
|
|
4436
|
+
// otherwise one is made. Its value never reaches a log, a scope id or an error.
|
|
4437
|
+
const heldInternalToken = (await this.ports.coolify.listEnvs(service.appId)).find((e) => e.key === 'CHAT_INTERNAL_TOKEN' && !e.is_preview)?.value;
|
|
4438
|
+
const internalTokenUsable = typeof heldInternalToken === 'string' && /^\S{32,}$/.test(heldInternalToken);
|
|
4439
|
+
const internalToken = internalTokenUsable ? heldInternalToken : randomBytes(32).toString('hex');
|
|
4440
|
+
await this.ports.coolify.bulkUpsertEnvs(service.appId, [
|
|
4441
|
+
...chatServiceEnvs({
|
|
4442
|
+
orgSlug: org.slug,
|
|
4443
|
+
companyName: org.displayName,
|
|
4444
|
+
issuer,
|
|
4445
|
+
audience: project.id,
|
|
4446
|
+
zitadelOrgId,
|
|
4447
|
+
agoraHost,
|
|
4448
|
+
masClientId: secrets.value.masClientId,
|
|
4449
|
+
masClientSecret: secrets.value.masClientSecret,
|
|
4450
|
+
introspectionClientId: introspection.clientId,
|
|
4451
|
+
introspectionClientSecret: introspection.clientSecret,
|
|
4452
|
+
}),
|
|
4453
|
+
{ key: 'CHAT_INTERNAL_TOKEN', value: internalToken },
|
|
4454
|
+
]);
|
|
4446
4455
|
// Split the same way `coolifyApp`'s and `cmsSite`'s own databases are
|
|
4447
4456
|
// (#239, #456): `DATABASE_URL_OWNER` is the superuser string,
|
|
4448
4457
|
// `DATABASE_URL` is a minted `chat_rt` role -- `chat` is the literal
|
|
@@ -4459,7 +4468,8 @@ export class FoundryStepExecutor {
|
|
|
4459
4468
|
for (const app of [mas, synapse, service]) {
|
|
4460
4469
|
if (!app.existed ||
|
|
4461
4470
|
secrets.changed ||
|
|
4462
|
-
(app === service &&
|
|
4471
|
+
(app === service &&
|
|
4472
|
+
(serviceDbPush.changed || introspection.changed || !internalTokenUsable)) ||
|
|
4463
4473
|
!isRunningOrStarting((await this.ports.coolify.getApplication(app.appId)).status)) {
|
|
4464
4474
|
// #543: `'ready'` for the service (it has a real route, probed
|
|
4465
4475
|
// through Agora); `'deployment-only'` for Synapse/MAS, same as this
|
|
@@ -884,6 +884,8 @@ export declare const PLATFORM_HC_KEY = "platform:hc";
|
|
|
884
884
|
* read back, never regenerated -- see `ensureGeneratedEnv`).
|
|
885
885
|
*/
|
|
886
886
|
export declare const PLATFORM_HC_APPLIED_HOSTNAME_SCOPE_KEY = "hc_applied_hostname";
|
|
887
|
+
/** The id of the Cloudflare Access app in front of hc. Unrecorded (or empty) plans the hc step, so an already-applied hc gets the gate. */
|
|
888
|
+
export declare const PLATFORM_HC_ACCESS_SCOPE_KEY = "cloudflare_platform_hc_access_app";
|
|
887
889
|
export declare const PLATFORM_HC_APPLIED_GIT_REF_SCOPE_KEY = "hc_applied_git_ref";
|
|
888
890
|
/**
|
|
889
891
|
* #540 round 2: the held-key ref a human puts hc's first project API key
|
|
@@ -987,6 +987,8 @@ export const PLATFORM_HC_KEY = 'platform:hc';
|
|
|
987
987
|
* read back, never regenerated -- see `ensureGeneratedEnv`).
|
|
988
988
|
*/
|
|
989
989
|
export const PLATFORM_HC_APPLIED_HOSTNAME_SCOPE_KEY = 'hc_applied_hostname';
|
|
990
|
+
/** The id of the Cloudflare Access app in front of hc. Unrecorded (or empty) plans the hc step, so an already-applied hc gets the gate. */
|
|
991
|
+
export const PLATFORM_HC_ACCESS_SCOPE_KEY = 'cloudflare_platform_hc_access_app';
|
|
990
992
|
export const PLATFORM_HC_APPLIED_GIT_REF_SCOPE_KEY = 'hc_applied_git_ref';
|
|
991
993
|
/**
|
|
992
994
|
* #540 round 2: the held-key ref a human puts hc's first project API key
|
|
@@ -2595,7 +2597,8 @@ function planHc(platform, recordedIds, executorBlocked) {
|
|
|
2595
2597
|
const { hostname, gitRef } = platform.hc;
|
|
2596
2598
|
const applied = Boolean(recordedIds[resourceKey('coolify', 'app', PLATFORM_HC_KEY)]) &&
|
|
2597
2599
|
recordedIds[PLATFORM_HC_APPLIED_HOSTNAME_SCOPE_KEY] === hostname &&
|
|
2598
|
-
recordedIds[PLATFORM_HC_APPLIED_GIT_REF_SCOPE_KEY] === gitRef
|
|
2600
|
+
recordedIds[PLATFORM_HC_APPLIED_GIT_REF_SCOPE_KEY] === gitRef &&
|
|
2601
|
+
Boolean(recordedIds[PLATFORM_HC_ACCESS_SCOPE_KEY]);
|
|
2599
2602
|
if (applied)
|
|
2600
2603
|
return { step: null, blocked: null };
|
|
2601
2604
|
if (executorBlocked)
|
|
@@ -1501,6 +1501,12 @@ export interface PlatformAccessProviderDeps {
|
|
|
1501
1501
|
deliverAttempts?: number;
|
|
1502
1502
|
};
|
|
1503
1503
|
}
|
|
1504
|
+
/**
|
|
1505
|
+
* Healthchecks' own status route (`hc/api/urls.py`, `views.status`): 200 `OK` once
|
|
1506
|
+
* the database answers. `/` redirects an anonymous visitor to the login page (302),
|
|
1507
|
+
* which Coolify's health check, wanting 200, counts as unhealthy.
|
|
1508
|
+
*/
|
|
1509
|
+
export declare const HC_HEALTH_CHECK_PATH = "/api/v3/status/";
|
|
1504
1510
|
/**
|
|
1505
1511
|
* The only paths the AI host's public route serves; everything else answers 404 at the tunnel. A company entry's key
|
|
1506
1512
|
* holds `ai.runs:create` and `ai.offerings:invoke` (`AI_ENTRY_PERMISSIONS`), and `@wtfalch/ai`'s client reaches them
|
|
@@ -1530,9 +1536,15 @@ export interface PlatformHcDeps {
|
|
|
1530
1536
|
dns: PlatformDnsFactory;
|
|
1531
1537
|
/** F4.tunnels: every tunnel call, through edge at its recorded address (see {@link PlatformTunnelsFactory}). */
|
|
1532
1538
|
tunnels: PlatformTunnelsFactory;
|
|
1539
|
+
/** F4.access: every Access app call, through edge at its recorded address (see {@link PlatformAccessFactory}). */
|
|
1540
|
+
access: PlatformAccessFactory;
|
|
1541
|
+
/** The emails the Access app in front of the web UI lets through ({@link PlatformArchonDeps.accessEmails}). */
|
|
1542
|
+
accessEmails: readonly string[];
|
|
1533
1543
|
/** Clock, sleep and fetch for the readiness wait; real ones by default. */
|
|
1534
1544
|
runtime?: PlatformHcRuntime;
|
|
1535
1545
|
}
|
|
1546
|
+
export declare const PLATFORM_HC_ACCESS_APP_NAME = "foundry:hc";
|
|
1547
|
+
export declare const hcPublicAccessScopeKey: (name: string) => string;
|
|
1536
1548
|
export declare function filesKeys(deps: PlatformFilesDeps | undefined): {
|
|
1537
1549
|
workerUrl: string;
|
|
1538
1550
|
credential: string;
|
|
@@ -21,7 +21,7 @@ import { MAIL_APP_SCOPE_KEY, MAIL_DATABASE_NAME, MAIL_DATABASE_PASSWORD_ENV, MAI
|
|
|
21
21
|
import { OIKOS_INGRESS_BLOCKED, PLATFORM_OIKOS_INGRESS_KEY, oikosIngressApplied, oikosIngressZoneBlocked, provisionOikosIngress, } from './oikos-ingress.js';
|
|
22
22
|
import { recordOwnDnsHostname } from './own-dns.js';
|
|
23
23
|
import { platformNetwork, platformTarget, scopeFromPlatform, } from './own-services.js';
|
|
24
|
-
import { AUTH_ZITADEL_RUN_OPTIONS, JOBS_DEPLOY_PENDING_SCOPE_KEY, JOBS_INTERNAL_URL_SCOPE_KEY, JOBS_SCHEDULER_KEY, PLATFORM_ACCESS_PROVIDER_APPLIED_AUTH_DOMAIN_SCOPE_KEY, PLATFORM_ACCESS_PROVIDER_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_ACCESS_PROVIDER_UNDELIVERED_SCOPE_KEY, PLATFORM_ACCESS_ZITADEL_APP_SCOPE_KEY, PLATFORM_ACCESS_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_AI_APPLIED_GITREF_SCOPE_KEY, PLATFORM_AI_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_AI_CONNECTOR_APP_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_AI_TUNNEL_SCOPE_KEY, PLATFORM_ARCHON_ACCESS_PIN_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_AUTH_HOSTNAME_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_AUTH_ORG_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_BROKER_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_CI_ORG_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_FILES_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_FIRST_OWNER_LINK_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_ROLLING_UPDATE_SCOPE_KEY, PLATFORM_ARCHON_APP_KEY_ORG_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_ID_TOKEN_USERINFO_APPLIED_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_ARCHON_ORG_DOMAIN_BLOCKED_SCOPE_KEY, PLATFORM_ARCHON_ORG_DOMAIN_SCOPE_KEY, PLATFORM_ARCHON_ORG_POLICY_SCOPE_KEY, PLATFORM_ARCHON_ORG_REGISTRATION_CLOSED_SCOPE_KEY, PLATFORM_ARCHON_ORG_SCOPE_KEY, PLATFORM_ARCHON_OWNER_INVITE_ORG_SCOPE_KEY, PLATFORM_ARCHON_OWNER_INVITE_SCOPE_KEY, PLATFORM_ARCHON_RETIRED_CLIENT_SCOPE_KEY, PLATFORM_ARCHON_UPTIME_TOKEN_SYNCED_SCOPE_KEY, PLATFORM_ARCHON_ZITADEL_APP_SCOPE_KEY, PLATFORM_ARCHON_ZITADEL_PROJECT_SCOPE_KEY, 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_ORG_SCOPE_KEY, PLATFORM_AUTH_OWNER_INVITE_SCOPE_KEY, PLATFORM_AUTH_REGISTRATION_CLOSED_SCOPE_KEY, PLATFORM_AUTH_RESERVED_ADMIN_USERNAME_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_HEARTBEATS_HOSTED_API_KEY_REF, 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_HOST_PORTS_DIRECT, PLATFORM_MAIL_HOST_PORTS_SCOPE_KEY, PLATFORM_MAIL_INTERNAL_URL_SCOPE_KEY, PLATFORM_MAIL_KEY, PLATFORM_MAIL_RELAY_KEY, PLATFORM_MAIL_ROUTE_SCOPE_KEY, PLATFORM_MAIL_ROUTE_WANTED, PLATFORM_MAIL_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_OWNER_ORG_DOMAIN_BLOCKED_SCOPE_KEY, PLATFORM_OWNER_ORG_DOMAIN_SCOPE_KEY, PLATFORM_OWNER_ORG_SCOPE_KEY, PLATFORM_SFU_APPLIED_SCOPE_KEY, PLATFORM_UPTIME_ACCESS_CLIENT_ID_REF, PLATFORM_UPTIME_ACCESS_CLIENT_SECRET_REF, PLATFORM_UPTIME_ACCESS_TOKEN_ID_SCOPE_KEY, PLATFORM_UPTIME_ACCESS_TOKEN_NAME, PLATFORM_UPTIME_ACCESS_UNDELIVERED_SCOPE_KEY, PLATFORM_UPTIME_CHAT_ROOM_ID_SCOPE_KEY, PLATFORM_UPTIME_HEARTBEAT_CHECK_NAME, PLATFORM_UPTIME_KEY, PLATFORM_UPTIME_REMOVED_SCOPE_KEY, PLATFORM_UPTIME_SCRIPT_NAME, PLATFORM_UPTIME_TOKEN_ID_SCOPE_KEY, PLATFORM_UPTIME_TOKEN_REF, PLATFORM_ZITADEL_ORG_SCOPE_KEY, archonFilesMarker, archonFirstOwnerLink, archonOrgDomain, archonOrgName, desiredHeartbeatCheckNames, heartbeatCheckUrlRef, parseBrokerDeployRecord, parseLoginClientRecord, platformBackupHeartbeatCheckName, platformMailHostPorts, platformMailMxHost, uptimeAccessTokenConflict, uptimeEffectiveVars, uptimeRecorded, uptimeVarsDigest, } from './platform-diff.js';
|
|
24
|
+
import { AUTH_ZITADEL_RUN_OPTIONS, JOBS_DEPLOY_PENDING_SCOPE_KEY, JOBS_INTERNAL_URL_SCOPE_KEY, JOBS_SCHEDULER_KEY, PLATFORM_ACCESS_PROVIDER_APPLIED_AUTH_DOMAIN_SCOPE_KEY, PLATFORM_ACCESS_PROVIDER_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_ACCESS_PROVIDER_UNDELIVERED_SCOPE_KEY, PLATFORM_ACCESS_ZITADEL_APP_SCOPE_KEY, PLATFORM_ACCESS_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_AI_APPLIED_GITREF_SCOPE_KEY, PLATFORM_AI_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_AI_CONNECTOR_APP_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_AI_TUNNEL_SCOPE_KEY, PLATFORM_ARCHON_ACCESS_PIN_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_AUTH_HOSTNAME_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_AUTH_ORG_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_BROKER_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_CI_ORG_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_FILES_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_FIRST_OWNER_LINK_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_ROLLING_UPDATE_SCOPE_KEY, PLATFORM_ARCHON_APP_KEY_ORG_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_ID_TOKEN_USERINFO_APPLIED_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_ARCHON_ORG_DOMAIN_BLOCKED_SCOPE_KEY, PLATFORM_ARCHON_ORG_DOMAIN_SCOPE_KEY, PLATFORM_ARCHON_ORG_POLICY_SCOPE_KEY, PLATFORM_ARCHON_ORG_REGISTRATION_CLOSED_SCOPE_KEY, PLATFORM_ARCHON_ORG_SCOPE_KEY, PLATFORM_ARCHON_OWNER_INVITE_ORG_SCOPE_KEY, PLATFORM_ARCHON_OWNER_INVITE_SCOPE_KEY, PLATFORM_ARCHON_RETIRED_CLIENT_SCOPE_KEY, PLATFORM_ARCHON_UPTIME_TOKEN_SYNCED_SCOPE_KEY, PLATFORM_ARCHON_ZITADEL_APP_SCOPE_KEY, PLATFORM_ARCHON_ZITADEL_PROJECT_SCOPE_KEY, 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_ORG_SCOPE_KEY, PLATFORM_AUTH_OWNER_INVITE_SCOPE_KEY, PLATFORM_AUTH_REGISTRATION_CLOSED_SCOPE_KEY, PLATFORM_AUTH_RESERVED_ADMIN_USERNAME_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_ACCESS_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_HEARTBEATS_HOSTED_API_KEY_REF, 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_HOST_PORTS_DIRECT, PLATFORM_MAIL_HOST_PORTS_SCOPE_KEY, PLATFORM_MAIL_INTERNAL_URL_SCOPE_KEY, PLATFORM_MAIL_KEY, PLATFORM_MAIL_RELAY_KEY, PLATFORM_MAIL_ROUTE_SCOPE_KEY, PLATFORM_MAIL_ROUTE_WANTED, PLATFORM_MAIL_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_OWNER_ORG_DOMAIN_BLOCKED_SCOPE_KEY, PLATFORM_OWNER_ORG_DOMAIN_SCOPE_KEY, PLATFORM_OWNER_ORG_SCOPE_KEY, PLATFORM_SFU_APPLIED_SCOPE_KEY, PLATFORM_UPTIME_ACCESS_CLIENT_ID_REF, PLATFORM_UPTIME_ACCESS_CLIENT_SECRET_REF, PLATFORM_UPTIME_ACCESS_TOKEN_ID_SCOPE_KEY, PLATFORM_UPTIME_ACCESS_TOKEN_NAME, PLATFORM_UPTIME_ACCESS_UNDELIVERED_SCOPE_KEY, PLATFORM_UPTIME_CHAT_ROOM_ID_SCOPE_KEY, PLATFORM_UPTIME_HEARTBEAT_CHECK_NAME, PLATFORM_UPTIME_KEY, PLATFORM_UPTIME_REMOVED_SCOPE_KEY, PLATFORM_UPTIME_SCRIPT_NAME, PLATFORM_UPTIME_TOKEN_ID_SCOPE_KEY, PLATFORM_UPTIME_TOKEN_REF, PLATFORM_ZITADEL_ORG_SCOPE_KEY, archonFilesMarker, archonFirstOwnerLink, archonOrgDomain, archonOrgName, desiredHeartbeatCheckNames, heartbeatCheckUrlRef, parseBrokerDeployRecord, parseLoginClientRecord, platformBackupHeartbeatCheckName, platformMailHostPorts, platformMailMxHost, uptimeAccessTokenConflict, uptimeEffectiveVars, uptimeRecorded, uptimeVarsDigest, } from './platform-diff.js';
|
|
25
25
|
import { ensureSignInOrg, inviteOwnerWithLink, signInOrgDomain } from './sign-in-org.js';
|
|
26
26
|
import { SIGNIN_DB_ZITADEL_PENDING_SCOPE_KEY } from './signin-db-tls.js';
|
|
27
27
|
import { SIGNIN_DB_APP_SCOPE_KEY, SIGNIN_DB_TLS_SCOPE_KEY, dockerfileAppCreator, ensureSigninDbCaFile, provisionSigninDb, waitHealthy, withCallTimeout, } from './signin-db.js';
|
|
@@ -347,6 +347,10 @@ export class FoundryPlatformStepExecutor {
|
|
|
347
347
|
const hcTagBlocked = hcImageTagBlocked(platform.hc.gitRef);
|
|
348
348
|
if (hcTagBlocked)
|
|
349
349
|
blocked.hc = hcTagBlocked;
|
|
350
|
+
else if (this.hc.accessEmails.length === 0) {
|
|
351
|
+
blocked.hc =
|
|
352
|
+
'the heartbeat server is blocked, not planned: the host names no email the Cloudflare Access app in front of it may let through';
|
|
353
|
+
}
|
|
350
354
|
}
|
|
351
355
|
// foundry#544: `'hosted'` needs none of `hc`'s own applied state -- only
|
|
352
356
|
// the self-hosted provider's standing note depends on `hc` being
|
|
@@ -710,7 +714,8 @@ export class FoundryPlatformStepExecutor {
|
|
|
710
714
|
if (!this.hc) {
|
|
711
715
|
throw new Error('platform:hc needs FoundryPlatformStepExecutor to be constructed with its hc dependencies (#540)');
|
|
712
716
|
}
|
|
713
|
-
|
|
717
|
+
const ownerCompany = await this.declaredOrgStore.get(context.platform.ownerOrgId);
|
|
718
|
+
return provisionPlatformHc(context, this.limitedOnPlatform(this.hc, LIMITS.platformHc), ownerCompany);
|
|
714
719
|
}
|
|
715
720
|
if (action.system === 'zitadel' &&
|
|
716
721
|
action.kind === 'archon-org' &&
|
|
@@ -7393,12 +7398,11 @@ const HC_INTERNAL_NAME = 'platform-hc';
|
|
|
7393
7398
|
/** Healthchecks' own default uWSGI/gunicorn port inside the container. */
|
|
7394
7399
|
const HC_PORT = '8000';
|
|
7395
7400
|
/**
|
|
7396
|
-
* Healthchecks
|
|
7397
|
-
*
|
|
7398
|
-
*
|
|
7399
|
-
* public page for an anonymous visitor.
|
|
7401
|
+
* Healthchecks' own status route (`hc/api/urls.py`, `views.status`): 200 `OK` once
|
|
7402
|
+
* the database answers. `/` redirects an anonymous visitor to the login page (302),
|
|
7403
|
+
* which Coolify's health check, wanting 200, counts as unhealthy.
|
|
7400
7404
|
*/
|
|
7401
|
-
const HC_HEALTH_CHECK_PATH = '/';
|
|
7405
|
+
export const HC_HEALTH_CHECK_PATH = '/api/v3/status/';
|
|
7402
7406
|
const PLATFORM_HC_DB = 'hc';
|
|
7403
7407
|
const PLATFORM_HC_DB_USER = 'hc';
|
|
7404
7408
|
/** The tunnel that carries the AI host's public hostname, and the app that runs its connector. */
|
|
@@ -7450,13 +7454,17 @@ function hcImageTagBlocked(tag) {
|
|
|
7450
7454
|
* (`platform-diff.ts`'s `planHc`) is what stops this running on every
|
|
7451
7455
|
* apply, not a live comparison in here.
|
|
7452
7456
|
*/
|
|
7453
|
-
async function provisionPlatformHc(context, deps) {
|
|
7457
|
+
async function provisionPlatformHc(context, deps, ownerCompany = null) {
|
|
7454
7458
|
const { platform } = context;
|
|
7455
7459
|
const hostname = platform.hc.hostname;
|
|
7456
7460
|
const gitRef = platform.hc.gitRef;
|
|
7457
7461
|
const tagBlocked = hcImageTagBlocked(gitRef);
|
|
7458
7462
|
if (tagBlocked)
|
|
7459
7463
|
throw new Error(tagBlocked);
|
|
7464
|
+
if (deps.accessEmails.length === 0) {
|
|
7465
|
+
throw new Error('platform:hc: no Access emails given; refusing to put the heartbeat server behind an Access app nobody can pass');
|
|
7466
|
+
}
|
|
7467
|
+
const accessEmails = archonAccessEmails(deps.accessEmails, ownerCompany);
|
|
7460
7468
|
const recorded = context.recordedIds();
|
|
7461
7469
|
const runtime = deps.runtime ?? {};
|
|
7462
7470
|
const sleep = runtime.sleep ?? defaultSleep;
|
|
@@ -7557,6 +7565,8 @@ async function provisionPlatformHc(context, deps) {
|
|
|
7557
7565
|
// comment. `hostname` is public and Cloudflare-proxied, so sign-up stays
|
|
7558
7566
|
// closed until a real invite mechanism exists.
|
|
7559
7567
|
{ key: 'REGISTRATION_OPEN', value: 'False' },
|
|
7568
|
+
// Healthchecks turns Django's debug mode on unless told otherwise: error pages would show settings.
|
|
7569
|
+
{ key: 'DEBUG', value: 'False' },
|
|
7560
7570
|
];
|
|
7561
7571
|
if (secretKey)
|
|
7562
7572
|
envs.push({ key: 'SECRET_KEY', value: secretKey });
|
|
@@ -7569,8 +7579,22 @@ async function provisionPlatformHc(context, deps) {
|
|
|
7569
7579
|
await waitForNewDeployment(deps.coolify, appId, { kind: 'ready', url: `${internalUrl}${HC_HEALTH_CHECK_PATH}` }, runtime, sleep, 'platform:hc', `hc stays at ${recorded[PLATFORM_HC_APPLIED_GIT_REF_SCOPE_KEY] ?? 'no recorded ref'}`);
|
|
7570
7580
|
}
|
|
7571
7581
|
await record(PLATFORM_HC_INTERNAL_URL_SCOPE_KEY, internalUrl);
|
|
7572
|
-
// ----
|
|
7582
|
+
// ---- Cloudflare Access, before the hostname resolves ------------------------
|
|
7573
7583
|
const tunnels = await platformTunnels(deps, context.recordedIds(), 'platform:hc');
|
|
7584
|
+
const access = await platformAccess(deps, context.recordedIds(), 'platform:hc');
|
|
7585
|
+
const accessAppId = await ensureHcAccess(access, hostname, accessEmails, async () => {
|
|
7586
|
+
// The app is about to be rebuilt: the step is planned again until it is recorded
|
|
7587
|
+
// again, and the hostname answers 404 meanwhile, so it is never served without the gate.
|
|
7588
|
+
await record(PLATFORM_HC_ACCESS_SCOPE_KEY, '');
|
|
7589
|
+
const open = await tunnels.findTunnelByName(HC_TUNNEL_NAME);
|
|
7590
|
+
if (open)
|
|
7591
|
+
await tunnels.putTunnelConfig(open.id, { ingress: [{ service: 'http_status:404' }] });
|
|
7592
|
+
});
|
|
7593
|
+
await ensureHcPublicAccess(access, hostname, record);
|
|
7594
|
+
if (recorded[PLATFORM_HC_ACCESS_SCOPE_KEY] !== accessAppId) {
|
|
7595
|
+
await record(PLATFORM_HC_ACCESS_SCOPE_KEY, accessAppId);
|
|
7596
|
+
}
|
|
7597
|
+
// ---- tunnel and DNS ---------------------------------------------------------
|
|
7574
7598
|
const existingTunnel = await tunnels.findTunnelByName(HC_TUNNEL_NAME);
|
|
7575
7599
|
const tunnel = existingTunnel ?? (await tunnels.createTunnel(HC_TUNNEL_NAME));
|
|
7576
7600
|
await record(PLATFORM_HC_TUNNEL_SCOPE_KEY, tunnel.id);
|
|
@@ -7613,6 +7637,82 @@ async function provisionPlatformHc(context, deps) {
|
|
|
7613
7637
|
await record(PLATFORM_HC_APPLIED_GIT_REF_SCOPE_KEY, gitRef);
|
|
7614
7638
|
return { resourceId: appId };
|
|
7615
7639
|
}
|
|
7640
|
+
/**
|
|
7641
|
+
* Paths a machine calls on hc, each its own Access app with one bypass policy:
|
|
7642
|
+
* `/api` (Archon's heartbeats client, behind Healthchecks' `X-Api-Key`) and
|
|
7643
|
+
* `/ping` (the uptime Worker and the backups, by the check's secret ping URL).
|
|
7644
|
+
*/
|
|
7645
|
+
const PLATFORM_HC_PUBLIC_PATHS = [
|
|
7646
|
+
{ name: 'api', path: '/api' },
|
|
7647
|
+
{ name: 'ping', path: '/ping' },
|
|
7648
|
+
];
|
|
7649
|
+
export const PLATFORM_HC_ACCESS_APP_NAME = 'foundry:hc';
|
|
7650
|
+
const hcPublicAppName = (name) => `${PLATFORM_HC_ACCESS_APP_NAME}:public:${name}`;
|
|
7651
|
+
export const hcPublicAccessScopeKey = (name) => `cloudflare_platform_hc_access_public_${name}`;
|
|
7652
|
+
/**
|
|
7653
|
+
* The Access app in front of hc's web UI: the one-time PIN to the declared
|
|
7654
|
+
* emails only. Adopts only an app of its own name on the hostname; one with
|
|
7655
|
+
* other policies, another provider or other emails is rebuilt after
|
|
7656
|
+
* `closeGate`.
|
|
7657
|
+
*/
|
|
7658
|
+
async function ensureHcAccess(cf, domain, emails, closeGate) {
|
|
7659
|
+
const { id: pinId } = await cf.ensureOneTimePinProvider();
|
|
7660
|
+
const onDomain = (await cf.listAccessApps()).filter((a) => a.domain === domain);
|
|
7661
|
+
const foreign = onDomain.find((a) => a.name !== PLATFORM_HC_ACCESS_APP_NAME);
|
|
7662
|
+
if (foreign) {
|
|
7663
|
+
throw new Error(`platform:hc: Access app '${foreign.name}' already holds ${domain}; foundry does not adopt it`);
|
|
7664
|
+
}
|
|
7665
|
+
const create = async () => {
|
|
7666
|
+
const created = await cf.createAccessApp({
|
|
7667
|
+
name: PLATFORM_HC_ACCESS_APP_NAME,
|
|
7668
|
+
domain,
|
|
7669
|
+
type: 'self_hosted',
|
|
7670
|
+
allowedIdps: [pinId],
|
|
7671
|
+
autoRedirectToIdentity: true,
|
|
7672
|
+
});
|
|
7673
|
+
await cf.createAccessPolicy(created.id, archonAccessPolicy(emails));
|
|
7674
|
+
return created.id;
|
|
7675
|
+
};
|
|
7676
|
+
const existing = onDomain[0];
|
|
7677
|
+
if (!existing)
|
|
7678
|
+
return create();
|
|
7679
|
+
const policies = await cf.listAccessPolicies(existing.id);
|
|
7680
|
+
if (policies.length === 1 &&
|
|
7681
|
+
policies[0]?.name === ACCESS_POLICY_NAME &&
|
|
7682
|
+
sameEmails(policies[0].include, emails) &&
|
|
7683
|
+
accessAppPinned(existing, pinId)) {
|
|
7684
|
+
return existing.id;
|
|
7685
|
+
}
|
|
7686
|
+
await closeGate();
|
|
7687
|
+
await cf.deleteAccessApp(existing.id);
|
|
7688
|
+
return create();
|
|
7689
|
+
}
|
|
7690
|
+
/** {@link ensureArchonPublicAccess} for hc's {@link PLATFORM_HC_PUBLIC_PATHS}. */
|
|
7691
|
+
async function ensureHcPublicAccess(cf, hostname, record) {
|
|
7692
|
+
for (const { name, path } of PLATFORM_HC_PUBLIC_PATHS) {
|
|
7693
|
+
const domain = `${hostname}${path}`;
|
|
7694
|
+
const appName = hcPublicAppName(name);
|
|
7695
|
+
const onDomain = (await cf.listAccessApps()).filter((a) => a.domain === domain);
|
|
7696
|
+
const foreign = onDomain.find((a) => a.name !== appName);
|
|
7697
|
+
if (foreign) {
|
|
7698
|
+
throw new Error(`platform:hc: Access app '${foreign.name}' already holds ${domain}; foundry does not adopt it`);
|
|
7699
|
+
}
|
|
7700
|
+
let id = onDomain[0]?.id;
|
|
7701
|
+
if (id && !onlyBypassEveryone(await cf.listAccessPolicies(id))) {
|
|
7702
|
+
await cf.deleteAccessApp(id);
|
|
7703
|
+
id = undefined;
|
|
7704
|
+
}
|
|
7705
|
+
if (!id) {
|
|
7706
|
+
id = (await cf.createAccessApp({ name: appName, domain, type: 'self_hosted' })).id;
|
|
7707
|
+
await cf.createAccessPolicy(id, {
|
|
7708
|
+
name: PLATFORM_ARCHON_PUBLIC_POLICY_NAME,
|
|
7709
|
+
decision: 'bypass',
|
|
7710
|
+
include: [{ everyone: {} }],
|
|
7711
|
+
});
|
|
7712
|
+
}
|
|
7713
|
+
await record(hcPublicAccessScopeKey(name), id);
|
|
7714
|
+
}
|
|
7715
|
+
}
|
|
7616
7716
|
/**
|
|
7617
7717
|
* The runtime `DATABASE_URL` the names app gets: the value already set on
|
|
7618
7718
|
* it, else a new `names_rt` credential on the database's host, the
|
|
@@ -187,8 +187,8 @@ export async function provisionPlatformUptime(context, deps) {
|
|
|
187
187
|
const hostname = platform.archon.hostname;
|
|
188
188
|
const held = await heldUptimeValues(deps);
|
|
189
189
|
// The chat bot comes before the upload: its room is one of the Worker's variables. A call that fails
|
|
190
|
-
//
|
|
191
|
-
// already have been minted).
|
|
190
|
+
// is logged and the Worker deploys without it; the next apply makes the call again (the marker below
|
|
191
|
+
// says whether a token may already have been minted).
|
|
192
192
|
const ownerChat = context.owner?.chat ?? null;
|
|
193
193
|
let chatRoomId = recorded[PLATFORM_UPTIME_CHAT_ROOM_ID_SCOPE_KEY] || '';
|
|
194
194
|
if (deps.chat && ownerChat && ownerChat.subjects.length > 0) {
|
|
@@ -207,20 +207,32 @@ export async function provisionPlatformUptime(context, deps) {
|
|
|
207
207
|
}
|
|
208
208
|
// A marker, or a room already made, means the bot exists and its token is not held: rotate.
|
|
209
209
|
const rotate = Boolean(lost) || Boolean(chatRoomId);
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
210
|
+
// A chat call that fails never stops the Worker: it deploys without the chat variables, the
|
|
211
|
+
// marker above stays, and the next apply makes the call again (foundry#748).
|
|
212
|
+
let chatToken;
|
|
213
|
+
try {
|
|
214
|
+
let bot = await callChatBot(deps.chat, ownerChat, rotate);
|
|
215
|
+
if (!bot.accessToken && !rotate)
|
|
216
|
+
bot = await callChatBot(deps.chat, ownerChat, true);
|
|
217
|
+
if (!bot.accessToken) {
|
|
218
|
+
throw new Error('chat-service returned no token for the bot');
|
|
219
|
+
}
|
|
220
|
+
chatToken = bot.accessToken;
|
|
221
|
+
chatRoomId = bot.roomId;
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
// The cause is dropped: it can carry a response body. One fixed line says what happened.
|
|
225
|
+
console.error('platform:uptime: the chat bot could not be made; the Worker deploys without chat alerts and the next apply tries again');
|
|
226
|
+
chatRoomId = '';
|
|
227
|
+
}
|
|
228
|
+
if (chatToken) {
|
|
229
|
+
context.recordScopeId(PLATFORM_UPTIME_CHAT_ROOM_ID_SCOPE_KEY, chatRoomId);
|
|
230
|
+
await context.persist?.();
|
|
231
|
+
await attempt('holding the chat bot token', () => deps.deliverSecret(PLATFORM_UPTIME_CHAT_TOKEN_REF, chatToken));
|
|
232
|
+
context.recordScopeId(PLATFORM_UPTIME_CHAT_UNDELIVERED_SCOPE_KEY, '');
|
|
233
|
+
await context.persist?.();
|
|
234
|
+
held.CHAT_ACCESS_TOKEN = chatToken;
|
|
216
235
|
}
|
|
217
|
-
chatRoomId = bot.roomId;
|
|
218
|
-
context.recordScopeId(PLATFORM_UPTIME_CHAT_ROOM_ID_SCOPE_KEY, chatRoomId);
|
|
219
|
-
await context.persist?.();
|
|
220
|
-
await attempt('holding the chat bot token', () => deps.deliverSecret(PLATFORM_UPTIME_CHAT_TOKEN_REF, chatToken));
|
|
221
|
-
context.recordScopeId(PLATFORM_UPTIME_CHAT_UNDELIVERED_SCOPE_KEY, '');
|
|
222
|
-
await context.persist?.();
|
|
223
|
-
held.CHAT_ACCESS_TOKEN = chatToken;
|
|
224
236
|
}
|
|
225
237
|
}
|
|
226
238
|
const workerVars = uptimeEffectiveVars(deps.vars, deps.chat && ownerChat && chatRoomId
|