/** * Proxmox preflight reachability check. * * The Proxmox Terraform provider, when given an unreachable api_url, * sits in TCP `SYN_SENT` until the kernel-level connect timeout fires * — typically 30–75s on macOS. During that window the user sees a * frozen progress display and no clue what's wrong (common cause: VPN * is down). We probe the api_url's host:port with a short timeout * before invoking terraform so we can fail fast with an actionable * message instead of letting the provider hang. * * Scoped intentionally to the proxmox container-service deploy path — * other terraform invocations (DigitalOcean, system audit) talk to * different endpoints with their own failure modes. */ import { Socket } from 'node:net'; export interface ProxmoxReachabilityResult { reachable: boolean; host: string; port: number; /** Populated when `reachable` is false. */ error?: string; } /** * Probe `host:port` with a TCP connect attempt. Resolves on connect, * timeout, or socket error — never rejects. Caller decides what to do * with the result. */ export async function checkProxmoxReachable( apiUrl: string, timeoutMs = 3000, ): Promise { let host: string; let port: number; try { const url = new URL(apiUrl); host = url.hostname; port = url.port ? Number(url.port) : url.protocol === 'https:' ? 443 : 80; } catch { return { reachable: false, host: apiUrl, port: 0, error: `Invalid Proxmox API URL: ${apiUrl}`, }; } return await new Promise((resolve) => { const socket = new Socket(); let settled = false; const finish = (result: ProxmoxReachabilityResult) => { if (settled) return; settled = true; socket.destroy(); resolve(result); }; socket.setTimeout(timeoutMs); socket.once('connect', () => finish({ reachable: true, host, port })); socket.once('timeout', () => finish({ reachable: false, host, port, error: `Connection to ${host}:${port} timed out after ${timeoutMs}ms`, }), ); socket.once('error', (err) => finish({ reachable: false, host, port, error: err.message, }), ); socket.connect(port, host); }); } /** * Format an unreachable result into a multi-line error message * suitable for display in the deploy progress panel. */ export function formatProxmoxUnreachableError(result: ProxmoxReachabilityResult): string { return [ `Proxmox unreachable at ${result.host}:${result.port}`, '', 'Check:', ' - VPN connection (if Proxmox is on a private network)', ' - Is the Proxmox host running and reachable from this machine?', ` - Try: nc -zv ${result.host} ${result.port}`, '', `(${result.error ?? 'no further detail'})`, ].join('\n'); }