Track HiveGrid Cloud planning reference
This commit is contained in:
1 parent
b3d5ea6764
commit
e341c5938a
5784 files changed
+1104929
-3
No files matched your search
@@ -0,0 +1,492 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
export const GROUPS = [
|
||||
{ name: "general", healthPort: "9101", adminPort: "9201", concurrencyKey: "WORKER_CONCURRENCY_GENERAL", concurrency: "10" },
|
||||
{ name: "mail", healthPort: "9102", adminPort: "9202", concurrencyKey: "WORKER_CONCURRENCY_MAIL", concurrency: "5" },
|
||||
{ name: "import", healthPort: "9103", adminPort: "9203", concurrencyKey: "WORKER_CONCURRENCY_IMPORT", concurrency: "1" },
|
||||
{ name: "webhook", healthPort: "9104", adminPort: "9204", concurrencyKey: "WORKER_CONCURRENCY_WEBHOOK", concurrency: "20" },
|
||||
{ name: "plugin", healthPort: "9105", adminPort: "9205", concurrencyKey: "WORKER_CONCURRENCY_PLUGIN", concurrency: "5" },
|
||||
];
|
||||
|
||||
const [, , environment = "staging"] = process.argv;
|
||||
const prefix = environment === "production" ? "COOLIFY" : "COOLIFY_STAGING";
|
||||
|
||||
const apiUrl = (process.env.SELECTED_COOLIFY_API_URL || process.env[`${prefix}_API_URL`] || "").replace(/\/+$/, "");
|
||||
const token = process.env.SELECTED_COOLIFY_TOKEN || process.env[`${prefix}_TOKEN`] || "";
|
||||
const imageTag = process.env.IMAGE_TAG || (environment === "production" ? "main" : "staging");
|
||||
const registry = process.env.REGISTRY || "";
|
||||
const imageNamespace = process.env.IMAGE_NAMESPACE || "";
|
||||
const imagePrefix = process.env.IMAGE_PREFIX || "";
|
||||
const workerImage = process.env.WORKER_IMAGE || [registry, imageNamespace, imagePrefix, "worker"].filter(Boolean).join("/");
|
||||
const existingResourceUuids = process.env[`${prefix}_RESOURCE_UUIDS`] || "";
|
||||
const resolvedEnvName = process.env.COOLIFY_RESOLVED_RESOURCE_UUIDS_ENV_NAME || (environment === "production" ? "PRODUCTION_RESOLVED_RESOURCE_UUIDS" : "STAGING_RESOLVED_RESOURCE_UUIDS");
|
||||
const provisionEnabled = process.env[`${prefix}_PROVISION_WORKER_GROUPS`] === "true";
|
||||
const configuredWorkerEnvSourceUuid = process.env[`${prefix}_WORKER_ENV_SOURCE_UUID`] || process.env.WORKER_ENV_SOURCE_UUID || "";
|
||||
export const WORKER_ENV_OVERRIDE_KEYS = new Set([
|
||||
"WORKER_ENABLED",
|
||||
"WORKER_QUEUE_GROUP",
|
||||
"WORKER_CONCURRENCY",
|
||||
"WORKER_HEALTH_PORT",
|
||||
"WORKER_ADMIN_PORT",
|
||||
"WORKER_CONCURRENCY_GENERAL",
|
||||
"WORKER_CONCURRENCY_MAIL",
|
||||
"WORKER_CONCURRENCY_IMPORT",
|
||||
"WORKER_CONCURRENCY_WEBHOOK",
|
||||
"WORKER_CONCURRENCY_PLUGIN",
|
||||
"MARKETING_SEQUENCES_ENABLED",
|
||||
"TRANSACTIONAL_MAIL_OUTBOX_REQUIRED",
|
||||
"OUTBOX_DISPATCHER_ENABLED",
|
||||
"OUTBOX_DISPATCHER_REQUIRED",
|
||||
"PLUGIN_HOOK_FANOUT_CONCURRENCY",
|
||||
"PLUGIN_HOOK_FANOUT_ENABLED",
|
||||
"PLUGIN_HOOK_FANOUT_REQUIRED",
|
||||
"PLUGIN_RUNTIME_PRODUCTION_ENABLED",
|
||||
"WORKFLOW_TRIGGER_EXECUTOR_MODE",
|
||||
"WORKFLOW_TRIGGER_OUTBOX_ENABLED",
|
||||
"WORKFLOW_TRIGGER_OUTBOX_CONCURRENCY",
|
||||
"WORKFLOW_TRIGGER_OUTBOX_REQUIRED",
|
||||
]);
|
||||
|
||||
function fail(message) {
|
||||
process.stderr.write(`${message}\n`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function requireEnv(name) {
|
||||
const value = process.env[name] || "";
|
||||
if (!value) {
|
||||
fail(`${name} is required to provision missing Coolify worker applications`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
async function coolify(path, options = {}) {
|
||||
if (!apiUrl || !token) {
|
||||
fail("Coolify API URL and token are required");
|
||||
}
|
||||
const response = await fetch(`${apiUrl}${path}`, {
|
||||
...options,
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
Accept: "application/json",
|
||||
...(options.body ? { "Content-Type": "application/json" } : {}),
|
||||
...options.headers,
|
||||
},
|
||||
});
|
||||
|
||||
const text = await response.text();
|
||||
let payload = {};
|
||||
if (text.trim()) {
|
||||
try {
|
||||
payload = JSON.parse(text);
|
||||
} catch {
|
||||
payload = { message: text.trim() };
|
||||
}
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
fail(`Coolify API request failed with HTTP ${response.status}`);
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
function asArray(payload) {
|
||||
if (Array.isArray(payload)) return payload;
|
||||
if (Array.isArray(payload?.data)) return payload.data;
|
||||
return [];
|
||||
}
|
||||
|
||||
function appText(app) {
|
||||
return [
|
||||
app.name,
|
||||
app.description,
|
||||
app.docker_registry_image_name,
|
||||
app.fqdn,
|
||||
app.domain,
|
||||
app.domains,
|
||||
JSON.stringify(app.environment_variables || app.envs || app.environment || ""),
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
function matchesWorkerGroup(app, group) {
|
||||
if (app.docker_registry_image_name !== workerImage) return false;
|
||||
const text = appText(app);
|
||||
const envPattern = new RegExp(`WORKER_QUEUE_GROUP\\s*(?:=|:)\\s*["']?${group}\\b`, "i");
|
||||
const namePattern = new RegExp(`(?:^|[\\s/_-])worker[\\s_-]*${group}(?:$|[\\s:_-])`, "i");
|
||||
return envPattern.test(text) || namePattern.test(text);
|
||||
}
|
||||
|
||||
function isGroupWorker(app) {
|
||||
return GROUPS.some((group) => matchesWorkerGroup(app, group.name));
|
||||
}
|
||||
|
||||
function resolveWorkerEnvSourceUuid(apps, allowed) {
|
||||
if (configuredWorkerEnvSourceUuid) {
|
||||
const app = apps.find((candidate) => candidate.uuid === configuredWorkerEnvSourceUuid);
|
||||
if (!app) {
|
||||
fail(`${prefix}_WORKER_ENV_SOURCE_UUID does not match an existing Coolify application`);
|
||||
}
|
||||
return configuredWorkerEnvSourceUuid;
|
||||
}
|
||||
|
||||
const candidates = apps
|
||||
.filter((candidate) => candidate.docker_registry_image_name === workerImage)
|
||||
.filter((candidate) => allowed.size === 0 || allowed.has(candidate.uuid))
|
||||
.filter((candidate) => !isGroupWorker(candidate));
|
||||
|
||||
candidates.sort((a, b) => {
|
||||
const aName = String(a.name || "");
|
||||
const bName = String(b.name || "");
|
||||
if (aName === "boilerplate-worker") return -1;
|
||||
if (bName === "boilerplate-worker") return 1;
|
||||
if (/worker/i.test(aName) && !/worker/i.test(bName)) return -1;
|
||||
if (/worker/i.test(bName) && !/worker/i.test(aName)) return 1;
|
||||
return aName.localeCompare(bName);
|
||||
});
|
||||
|
||||
return candidates[0]?.uuid || "";
|
||||
}
|
||||
|
||||
function envKey(entry) {
|
||||
return typeof entry?.key === "string" ? entry.key : "";
|
||||
}
|
||||
|
||||
function envValue(entry) {
|
||||
let value = typeof entry?.value === "string" ? entry.value : "";
|
||||
while (value.length >= 2 && ((value.startsWith("'") && value.endsWith("'")) || (value.startsWith('"') && value.endsWith('"')))) {
|
||||
value = value.slice(1, -1);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export function desiredWorkerEnv(group) {
|
||||
const env = {
|
||||
WORKER_ENABLED: "true",
|
||||
WORKER_QUEUE_GROUP: group.name,
|
||||
WORKER_HEALTH_PORT: group.healthPort,
|
||||
WORKER_ADMIN_PORT: group.adminPort,
|
||||
[group.concurrencyKey]: group.concurrency,
|
||||
};
|
||||
if (group.name === "general") {
|
||||
Object.assign(env, {
|
||||
OUTBOX_DISPATCHER_ENABLED: "true",
|
||||
OUTBOX_DISPATCHER_REQUIRED: "true",
|
||||
WORKFLOW_TRIGGER_EXECUTOR_MODE: "http",
|
||||
WORKFLOW_TRIGGER_OUTBOX_ENABLED: "true",
|
||||
WORKFLOW_TRIGGER_OUTBOX_CONCURRENCY: "5",
|
||||
WORKFLOW_TRIGGER_OUTBOX_REQUIRED: "true",
|
||||
});
|
||||
}
|
||||
if (group.name === "mail") {
|
||||
Object.assign(env, {
|
||||
MARKETING_SEQUENCES_ENABLED: "false",
|
||||
TRANSACTIONAL_MAIL_OUTBOX_REQUIRED: "true",
|
||||
});
|
||||
}
|
||||
return env;
|
||||
}
|
||||
|
||||
export function envEntriesToRecord(entries) {
|
||||
const record = {};
|
||||
for (const entry of entries || []) {
|
||||
const key = envKey(entry);
|
||||
if (!key) continue;
|
||||
record[key] = envValue(entry);
|
||||
}
|
||||
return record;
|
||||
}
|
||||
|
||||
export function normalizeWorkflowTriggerExecutorMode(value) {
|
||||
const normalized = typeof value === "string" ? value.trim().toLowerCase() : "";
|
||||
return normalized === "http" || normalized === "hybrid" || normalized === "native" ? normalized : "";
|
||||
}
|
||||
|
||||
export function getWorkerEnvDrift(app, group, entries) {
|
||||
const actual = envEntriesToRecord(entries);
|
||||
const exactDrift = Object.entries(desiredWorkerEnv(group))
|
||||
.filter(([key, expected]) => {
|
||||
if (key === "WORKFLOW_TRIGGER_EXECUTOR_MODE") {
|
||||
return !normalizeWorkflowTriggerExecutorMode(actual[key]);
|
||||
}
|
||||
return actual[key] !== expected;
|
||||
})
|
||||
.map(([key, expected]) => ({
|
||||
key,
|
||||
expected: key === "WORKFLOW_TRIGGER_EXECUTOR_MODE" ? "http|hybrid|native" : expected,
|
||||
actual: actual[key] ?? "<missing>",
|
||||
appUuid: app?.uuid ?? "",
|
||||
appName: app?.name ?? `worker-${group.name}`,
|
||||
group: group.name,
|
||||
}));
|
||||
const sharedRuntimeDrift = getSharedWorkerRuntimeDrift(app, group, actual);
|
||||
const groupRuntimeDrift = group.name === "general" ? getGeneralWorkerRuntimeDrift(app, group, actual) : [];
|
||||
return [...exactDrift, ...sharedRuntimeDrift, ...groupRuntimeDrift];
|
||||
}
|
||||
|
||||
export function getWorkerAppConfigDrift(app, group) {
|
||||
const appName = app?.name ?? `worker-${group.name}`;
|
||||
const appUuid = app?.uuid ?? "";
|
||||
const item = (key, expected, actual) => ({
|
||||
key,
|
||||
expected,
|
||||
actual,
|
||||
appUuid,
|
||||
appName,
|
||||
group: group.name,
|
||||
});
|
||||
const expected = {
|
||||
ports_exposes: group.healthPort,
|
||||
health_check_enabled: "true",
|
||||
health_check_path: "/readyz",
|
||||
health_check_port: group.healthPort,
|
||||
health_check_host: "127.0.0.1",
|
||||
};
|
||||
const actual = {
|
||||
ports_exposes: normalizeCoolifyConfigValue(app?.ports_exposes),
|
||||
health_check_enabled: normalizeCoolifyBoolean(app?.health_check_enabled),
|
||||
health_check_path: normalizeCoolifyConfigValue(app?.health_check_path),
|
||||
health_check_port: normalizeCoolifyConfigValue(app?.health_check_port),
|
||||
health_check_host: normalizeCoolifyConfigValue(app?.health_check_host),
|
||||
};
|
||||
return Object.entries(expected)
|
||||
.filter(([key, expectedValue]) => actual[key] !== expectedValue)
|
||||
.map(([key, expectedValue]) => item(key, expectedValue, actual[key] || "<missing>"));
|
||||
}
|
||||
|
||||
function normalizeCoolifyBoolean(value) {
|
||||
return value === true || value === "true" || value === 1 || value === "1" ? "true" : value === false || value === "false" || value === 0 || value === "0" ? "false" : "";
|
||||
}
|
||||
|
||||
function normalizeCoolifyConfigValue(value) {
|
||||
if (value === null || value === undefined) return "";
|
||||
return String(value).trim();
|
||||
}
|
||||
|
||||
function hasEnvValue(actual, key) {
|
||||
return typeof actual[key] === "string" && actual[key].trim().length > 0;
|
||||
}
|
||||
|
||||
function getSharedWorkerRuntimeDrift(app, group, actual) {
|
||||
const drift = [];
|
||||
const item = (key, expected, actualValue) => ({
|
||||
key,
|
||||
expected,
|
||||
actual: actualValue,
|
||||
appUuid: app?.uuid ?? "",
|
||||
appName: app?.name ?? `worker-${group.name}`,
|
||||
group: group.name,
|
||||
});
|
||||
|
||||
if (!hasEnvValue(actual, "DATABASE_URL")) {
|
||||
drift.push(item("DATABASE_URL", "configured DATABASE_URL", "<missing>"));
|
||||
}
|
||||
if (!hasEnvValue(actual, "BULLMQ_REDIS_URL")) {
|
||||
drift.push(item("BULLMQ_REDIS_URL", "configured BULLMQ_REDIS_URL", actual.REDIS_URL ? "<missing; REDIS_URL fallback not accepted for managed workers>" : "<missing>"));
|
||||
}
|
||||
|
||||
return drift;
|
||||
}
|
||||
|
||||
function getGeneralWorkerRuntimeDrift(app, group, actual) {
|
||||
const drift = [];
|
||||
const item = (key, expected, actualValue) => ({
|
||||
key,
|
||||
expected,
|
||||
actual: actualValue,
|
||||
appUuid: app?.uuid ?? "",
|
||||
appName: app?.name ?? `worker-${group.name}`,
|
||||
group: group.name,
|
||||
});
|
||||
|
||||
const mode = normalizeWorkflowTriggerExecutorMode(actual.WORKFLOW_TRIGGER_EXECUTOR_MODE);
|
||||
if (mode === "http" || mode === "hybrid") {
|
||||
if (!hasEnvValue(actual, "WORKFLOW_TRIGGER_EXECUTOR_URL") && !hasEnvValue(actual, "APP_URL") && !hasEnvValue(actual, "WEB_ORIGIN")) {
|
||||
drift.push(item("WORKFLOW_TRIGGER_EXECUTOR_URL", "configured WORKFLOW_TRIGGER_EXECUTOR_URL, APP_URL, or WEB_ORIGIN", "<missing>"));
|
||||
}
|
||||
if (!hasEnvValue(actual, "API_ACCESS_TOKEN")) {
|
||||
drift.push(item("API_ACCESS_TOKEN", "configured API_ACCESS_TOKEN matching the web runtime", "<missing>"));
|
||||
} else if (hasEnvValue(actual, "WORKFLOW_TRIGGER_EXECUTOR_TOKEN")) {
|
||||
drift.push(item("WORKFLOW_TRIGGER_EXECUTOR_TOKEN", "unset; API_ACCESS_TOKEN is the supported workflow bridge credential", "<configured>"));
|
||||
}
|
||||
}
|
||||
|
||||
if (actual.PLUGIN_RUNTIME_PRODUCTION_ENABLED === "true") {
|
||||
if (actual.PLUGIN_HOOK_FANOUT_ENABLED === "false") {
|
||||
drift.push(item("PLUGIN_HOOK_FANOUT_ENABLED", "unset or true when PLUGIN_RUNTIME_PRODUCTION_ENABLED=true", "false"));
|
||||
}
|
||||
if (actual.PLUGIN_HOOK_FANOUT_REQUIRED !== "true") {
|
||||
drift.push(item("PLUGIN_HOOK_FANOUT_REQUIRED", "true when PLUGIN_RUNTIME_PRODUCTION_ENABLED=true", actual.PLUGIN_HOOK_FANOUT_REQUIRED ?? "<missing>"));
|
||||
}
|
||||
}
|
||||
|
||||
return drift;
|
||||
}
|
||||
|
||||
async function cloneWorkerEnv(sourceUuid, targetUuid) {
|
||||
if (!sourceUuid) return;
|
||||
|
||||
const [sourceEnvs, targetEnvs] = await Promise.all([
|
||||
coolify(`/applications/${encodeURIComponent(sourceUuid)}/envs`).then(asArray),
|
||||
coolify(`/applications/${encodeURIComponent(targetUuid)}/envs`).then(asArray),
|
||||
]);
|
||||
const targetKeys = new Set(targetEnvs.map(envKey).filter(Boolean));
|
||||
|
||||
for (const entry of sourceEnvs) {
|
||||
const key = envKey(entry);
|
||||
if (!key || targetKeys.has(key) || WORKER_ENV_OVERRIDE_KEYS.has(key)) continue;
|
||||
|
||||
await coolify(`/applications/${encodeURIComponent(targetUuid)}/envs`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
key,
|
||||
value: envValue(entry),
|
||||
is_literal: entry.is_literal ?? true,
|
||||
is_preview: entry.is_preview ?? false,
|
||||
is_multiline: entry.is_multiline ?? false,
|
||||
is_shown_once: entry.is_shown_once ?? false,
|
||||
}),
|
||||
});
|
||||
targetKeys.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
async function createWorkerApplication(group, workerEnvSourceUuid) {
|
||||
if (!provisionEnabled) {
|
||||
fail(`Missing Coolify worker-${group.name} application. Set ${prefix}_PROVISION_WORKER_GROUPS=true with project/server/destination variables to create it automatically.`);
|
||||
}
|
||||
|
||||
const projectUuid = requireEnv(`${prefix}_PROJECT_UUID`);
|
||||
const serverUuid = requireEnv(`${prefix}_SERVER_UUID`);
|
||||
const destinationUuid = requireEnv(`${prefix}_DESTINATION_UUID`);
|
||||
const environmentUuid = process.env[`${prefix}_ENVIRONMENT_UUID`] || "";
|
||||
const environmentName = process.env[`${prefix}_ENVIRONMENT_NAME`] || environment;
|
||||
const body = buildWorkerApplicationBody({
|
||||
group,
|
||||
projectUuid,
|
||||
serverUuid,
|
||||
destinationUuid,
|
||||
environmentName,
|
||||
environmentUuid,
|
||||
workerImage,
|
||||
imageTag,
|
||||
});
|
||||
|
||||
const created = await coolify("/applications/dockerimage", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
const uuid = typeof created.uuid === "string" ? created.uuid : "";
|
||||
if (!uuid) {
|
||||
fail(`Coolify did not return a UUID for worker-${group.name}`);
|
||||
}
|
||||
|
||||
await cloneWorkerEnv(workerEnvSourceUuid, uuid);
|
||||
await ensureWorkerEnv(uuid, group);
|
||||
process.stdout.write(`Created Coolify worker-${group.name} application\n`);
|
||||
return uuid;
|
||||
}
|
||||
|
||||
export function buildWorkerApplicationBody({ group, projectUuid, serverUuid, destinationUuid, environmentName, environmentUuid = "", workerImage, imageTag }) {
|
||||
const body = {
|
||||
project_uuid: projectUuid,
|
||||
server_uuid: serverUuid,
|
||||
environment_name: environmentName,
|
||||
docker_registry_image_name: workerImage,
|
||||
docker_registry_image_tag: imageTag,
|
||||
destination_uuid: destinationUuid,
|
||||
name: `worker-${group.name}`,
|
||||
description: `Managed by deploy workflow. WORKER_QUEUE_GROUP=${group.name}`,
|
||||
ports_exposes: group.healthPort,
|
||||
health_check_enabled: true,
|
||||
health_check_path: "/readyz",
|
||||
health_check_port: group.healthPort,
|
||||
health_check_host: "127.0.0.1",
|
||||
health_check_method: "GET",
|
||||
health_check_return_code: 200,
|
||||
health_check_scheme: "http",
|
||||
health_check_interval: 30,
|
||||
health_check_timeout: 5,
|
||||
health_check_retries: 3,
|
||||
health_check_start_period: 20,
|
||||
instant_deploy: false,
|
||||
connect_to_docker_network: true,
|
||||
};
|
||||
if (environmentUuid) {
|
||||
body.environment_uuid = environmentUuid;
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
async function ensureWorkerEnv(uuid, group) {
|
||||
for (const [key, value] of Object.entries(desiredWorkerEnv(group))) {
|
||||
await coolify(`/applications/${encodeURIComponent(uuid)}/envs`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
key,
|
||||
value,
|
||||
is_literal: true,
|
||||
is_preview: false,
|
||||
is_multiline: false,
|
||||
is_shown_once: false,
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function assertWorkerEnvMatches(uuid, app, group) {
|
||||
const envs = asArray(await coolify(`/applications/${encodeURIComponent(uuid)}/envs`));
|
||||
const drift = [...getWorkerAppConfigDrift(app, group), ...getWorkerEnvDrift(app, group, envs)];
|
||||
if (drift.length > 0) {
|
||||
const summary = drift.map((item) => `${item.key}: expected ${item.expected}, got ${item.actual}`).join("; ");
|
||||
fail(`Coolify worker-${group.name} drift detected for ${app?.name || uuid}: ${summary}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
if (!["staging", "production"].includes(environment)) {
|
||||
fail("Usage: coolify-ensure-worker-groups.mjs staging|production");
|
||||
}
|
||||
if (!workerImage || workerImage.split("/").length < 4) {
|
||||
fail("Worker image path could not be resolved from REGISTRY, IMAGE_NAMESPACE, IMAGE_PREFIX, and worker target");
|
||||
}
|
||||
|
||||
const allowed = new Set(existingResourceUuids.split(",").filter(Boolean));
|
||||
const apps = await coolify("/applications");
|
||||
const workerEnvSourceUuid = resolveWorkerEnvSourceUuid(apps, allowed);
|
||||
const resolvedWorkers = [];
|
||||
|
||||
for (const group of GROUPS) {
|
||||
let app = apps.find((candidate) => matchesWorkerGroup(candidate, group.name) && (allowed.size === 0 || allowed.has(candidate.uuid)));
|
||||
if (!app && provisionEnabled) {
|
||||
const uuid = await createWorkerApplication(group, workerEnvSourceUuid);
|
||||
// Re-fetch the freshly created application so config drift checks
|
||||
// (ports_exposes/health_check_*) validate the persisted resource, not a
|
||||
// bare { uuid } stub that always reports those fields as <missing>.
|
||||
const created = await coolify(`/applications/${encodeURIComponent(uuid)}`);
|
||||
app = created && created.uuid ? created : { uuid };
|
||||
}
|
||||
if (!app?.uuid) {
|
||||
fail(`Missing Coolify worker-${group.name} application`);
|
||||
}
|
||||
await assertWorkerEnvMatches(app.uuid, app, group);
|
||||
resolvedWorkers.push(app.uuid);
|
||||
}
|
||||
|
||||
const resolved = [...allowed, ...resolvedWorkers].filter(Boolean).join(",");
|
||||
if (process.env.GITHUB_ENV) {
|
||||
const fs = await import("node:fs");
|
||||
fs.appendFileSync(process.env.GITHUB_ENV, `${resolvedEnvName}=${resolved}\n`);
|
||||
}
|
||||
process.stdout.write(`Coolify ${environment} worker group applications are present\n`);
|
||||
}
|
||||
|
||||
const invokedPath = process.argv[1] ? path.resolve(process.argv[1]) : "";
|
||||
if (invokedPath && fileURLToPath(import.meta.url) === invokedPath) {
|
||||
await main();
|
||||
}
|
||||
Reference in new issue
Block a user