117 lines
3.4 KiB
TypeScript
117 lines
3.4 KiB
TypeScript
/**
|
|
* Coolify connectivity checks for ops / uptime monitors.
|
|
*
|
|
* - HTTP API (token) — provisioning via REST
|
|
* - SSH → Docker on host — required for shell.exec / dev containers
|
|
*/
|
|
|
|
import { listServers } from '@/lib/coolify';
|
|
import { runOnCoolifyHost } from '@/lib/coolify-ssh';
|
|
|
|
export interface CoolifyInfraHealthReport {
|
|
checkedAt: string;
|
|
ssh: {
|
|
configured: boolean;
|
|
missingEnvVars: string[];
|
|
reachable?: boolean;
|
|
latencyMs?: number;
|
|
dockerDaemonOk?: boolean;
|
|
/** docker Server.Version when daemon responds */
|
|
dockerVersion?: string;
|
|
error?: string;
|
|
};
|
|
api: {
|
|
configured: boolean;
|
|
reachable?: boolean;
|
|
latencyMs?: number;
|
|
serverCount?: number;
|
|
error?: string;
|
|
};
|
|
}
|
|
|
|
export function getCoolifySshConfigGap(): {
|
|
configured: boolean;
|
|
missingEnvVars: string[];
|
|
} {
|
|
const missing: string[] = [];
|
|
if (!process.env.COOLIFY_SSH_HOST?.trim()) missing.push('COOLIFY_SSH_HOST');
|
|
if (!process.env.COOLIFY_SSH_PRIVATE_KEY_B64?.trim()) {
|
|
missing.push('COOLIFY_SSH_PRIVATE_KEY_B64');
|
|
}
|
|
return { configured: missing.length === 0, missingEnvVars: missing };
|
|
}
|
|
|
|
/** True when SSH is wired and `docker` responds on the Coolify host. */
|
|
export async function runCoolifyInfraHealthProbe(): Promise<CoolifyInfraHealthReport> {
|
|
const checkedAt = new Date().toISOString();
|
|
const sshGap = getCoolifySshConfigGap();
|
|
|
|
const report: CoolifyInfraHealthReport = {
|
|
checkedAt,
|
|
ssh: {
|
|
configured: sshGap.configured,
|
|
missingEnvVars: sshGap.missingEnvVars,
|
|
},
|
|
api: {
|
|
configured: !!process.env.COOLIFY_API_TOKEN?.trim(),
|
|
},
|
|
};
|
|
|
|
if (report.api.configured) {
|
|
const t0 = Date.now();
|
|
try {
|
|
const servers = await listServers();
|
|
report.api.reachable = true;
|
|
report.api.latencyMs = Date.now() - t0;
|
|
report.api.serverCount = Array.isArray(servers) ? servers.length : 0;
|
|
} catch (e) {
|
|
report.api.reachable = false;
|
|
report.api.latencyMs = Date.now() - t0;
|
|
report.api.error = e instanceof Error ? e.message : String(e);
|
|
}
|
|
} else {
|
|
report.api.error = 'COOLIFY_API_TOKEN is not set';
|
|
}
|
|
|
|
if (!sshGap.configured) {
|
|
report.ssh.error =
|
|
`Missing: ${sshGap.missingEnvVars.join(', ')} — dev containers need SSH to the Docker host (see lib/coolify-ssh.ts).`;
|
|
return report;
|
|
}
|
|
|
|
const tSsh = Date.now();
|
|
try {
|
|
const res = await runOnCoolifyHost(`docker info --format '{{.ServerVersion}}'`, {
|
|
timeoutMs: 15_000,
|
|
maxBytes: 8192,
|
|
});
|
|
report.ssh.latencyMs = Date.now() - tSsh;
|
|
report.ssh.reachable = true;
|
|
const ver = res.stdout.trim().split(/\s+/)[0]?.trim() ?? '';
|
|
if (res.code === 0 && ver.length > 0) {
|
|
report.ssh.dockerDaemonOk = true;
|
|
report.ssh.dockerVersion = ver;
|
|
} else {
|
|
report.ssh.dockerDaemonOk = false;
|
|
report.ssh.error = `docker probe exit ${res.code}: ${(res.stderr || res.stdout || '(empty)').slice(0, 600)}`;
|
|
}
|
|
} catch (e) {
|
|
report.ssh.latencyMs = Date.now() - tSsh;
|
|
report.ssh.reachable = false;
|
|
report.ssh.dockerDaemonOk = false;
|
|
report.ssh.error = e instanceof Error ? e.message : String(e);
|
|
}
|
|
|
|
return report;
|
|
}
|
|
|
|
export function isCoolifyInfraOperational(report: CoolifyInfraHealthReport): boolean {
|
|
return (
|
|
report.ssh.configured &&
|
|
report.ssh.reachable === true &&
|
|
report.ssh.dockerDaemonOk === true &&
|
|
report.api.configured &&
|
|
report.api.reachable === true
|
|
);
|
|
}
|