import { runCommand } from "./command"; import { type UniDeskConfig, repoRoot } from "./config"; export const dispatchCommands = ["docker.ps", "provider.upgrade", "host.ssh", "microservice.http", "echo"] as const; export type DebugDispatchCommand = typeof dispatchCommands[number]; export function isDebugDispatchCommand(value: unknown): value is DebugDispatchCommand { return dispatchCommands.includes(value as DebugDispatchCommand); } async function readJson(url: string, init?: RequestInit): Promise { const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), 5000); try { const res = await fetch(url, { ...init, signal: controller.signal }); const text = await res.text(); return { ok: res.ok, status: res.status, body: text.length > 0 ? JSON.parse(text) : null }; } catch (error) { return { ok: false, error: error instanceof Error ? error.message : String(error) }; } finally { clearTimeout(timer); } } function coreInternalFetch(path: string, init?: { method?: string; body?: unknown }): unknown { const code = ` const res = await fetch(${JSON.stringify(`http://127.0.0.1:8080${path}`)}, ${JSON.stringify({ method: init?.method ?? "GET", headers: init?.body === undefined ? undefined : { "content-type": "application/json" }, body: init?.body === undefined ? undefined : JSON.stringify(init.body), })}); const text = await res.text(); let body = null; try { body = text ? JSON.parse(text) : null; } catch { body = { text }; } console.log(JSON.stringify({ ok: res.ok, status: res.status, body })); `; const result = runCommand(["docker", "exec", "unidesk-backend-core", "bun", "-e", code], repoRoot); if (result.exitCode !== 0) { return { ok: false, exitCode: result.exitCode, stdoutTail: result.stdout.slice(-1200), stderrTail: result.stderr.slice(-1200) }; } try { return JSON.parse(result.stdout.trim()) as unknown; } catch { return { ok: true, stdoutTail: result.stdout.slice(-1200), stderrTail: result.stderr.slice(-1200) }; } } function coreDockerStatusSummary(): unknown { const code = ` const res = await fetch('http://127.0.0.1:8080/api/nodes/docker-status'); const text = await res.text(); let body = null; try { body = text ? JSON.parse(text) : null; } catch { body = { text }; } const dockerStatuses = (body?.dockerStatuses || []).map((item) => { const status = item.dockerStatus || {}; return { providerId: item.providerId, name: item.name, nodeStatus: item.nodeStatus, updatedAt: item.updatedAt, dockerStatus: { ok: status.ok, socketPresent: status.socketPresent, collectedAt: status.collectedAt, counts: status.counts, daemon: status.daemon, containersPreview: (status.containers || []).slice(0, 8).map((container) => ({ id: container.id, name: container.name, image: container.image, state: container.state, status: container.status, ports: container.ports, })), }, }; }); console.log(JSON.stringify({ ok: res.ok, status: res.status, body: { ok: body?.ok === true, dockerStatuses } })); `; const result = runCommand(["docker", "exec", "unidesk-backend-core", "bun", "-e", code], repoRoot); if (result.exitCode !== 0) { return { ok: false, exitCode: result.exitCode, stdoutTail: result.stdout.slice(-1200), stderrTail: result.stderr.slice(-1200) }; } try { return JSON.parse(result.stdout.trim()) as unknown; } catch { return { ok: true, stdoutTail: result.stdout.slice(-1200), stderrTail: result.stderr.slice(-1200) }; } } function coreSystemStatusSummary(): unknown { const code = ` const res = await fetch('http://127.0.0.1:8080/api/nodes/system-status?limit=24'); const text = await res.text(); let body = null; try { body = text ? JSON.parse(text) : null; } catch { body = { text }; } const systemStatuses = (body?.systemStatuses || []).map((item) => { const current = item.current || {}; return { providerId: item.providerId, name: item.name, nodeStatus: item.nodeStatus, updatedAt: item.updatedAt, current: item.current ? { ok: current.ok, collectedAt: current.collectedAt, cpu: current.cpu, memory: current.memory, disk: current.disk, } : null, historyPreview: (item.history || []).slice(-8), historyCount: (item.history || []).length, }; }); console.log(JSON.stringify({ ok: res.ok, status: res.status, body: { ok: body?.ok === true, systemStatuses } })); `; const result = runCommand(["docker", "exec", "unidesk-backend-core", "bun", "-e", code], repoRoot); if (result.exitCode !== 0) { return { ok: false, exitCode: result.exitCode, stdoutTail: result.stdout.slice(-1200), stderrTail: result.stderr.slice(-1200) }; } try { return JSON.parse(result.stdout.trim()) as unknown; } catch { return { ok: true, stdoutTail: result.stdout.slice(-1200), stderrTail: result.stderr.slice(-1200) }; } } export async function debugHealth(config: UniDeskConfig): Promise { return { coreInternal: await coreInternalFetch("/health"), overviewInternal: await coreInternalFetch("/api/overview"), nodesInternal: await coreInternalFetch("/api/nodes"), systemStatusInternal: coreSystemStatusSummary(), dockerStatusInternal: coreDockerStatusSummary(), frontendPublic: await readJson(`http://127.0.0.1:${config.network.frontend.port}/health`), providerIngressPublic: await readJson(`http://127.0.0.1:${config.network.providerIngress.port}/health`), publicExposureBoundary: { coreHostPort: { port: config.network.core.port, expected: "not-exposed" }, databaseHostPort: { port: config.network.database.port, expected: "not-exposed" }, }, }; } async function waitForTask(taskId: string, timeoutMs: number): Promise { const started = Date.now(); let latest: unknown = null; while (Date.now() - started < timeoutMs) { latest = coreInternalFetch("/api/tasks?limit=100"); const tasks = (latest as { body?: { tasks?: Array<{ id?: string; status?: string; result?: unknown }> } }).body?.tasks ?? []; const task = tasks.find((item) => item.id === taskId); if (task?.status === "succeeded" || task?.status === "failed") return { ok: true, task }; await Bun.sleep(500); } return { ok: false, timeoutMs, latest }; } export async function debugTask(_config: UniDeskConfig, taskId: string): Promise { const tasksResponse = coreInternalFetch("/api/tasks?limit=100"); const tasks = (tasksResponse as { body?: { tasks?: Array<{ id?: string }> } }).body?.tasks ?? []; const task = taskId === "latest" ? tasks[0] : tasks.find((item) => item.id === taskId); return { tasksResponse, taskId, task: task ?? null }; } export async function debugDispatch( config: UniDeskConfig, providerId: string, command: DebugDispatchCommand, payload?: Record, waitMs = 0, ): Promise { const dispatchPayload = payload ?? (command === "provider.upgrade" ? { source: "cli-debug", mode: "plan" } : { source: "cli-debug" }); const dispatch = coreInternalFetch("/api/dispatch", { method: "POST", body: { providerId, command, payload: dispatchPayload }, }); const taskId = (dispatch as { body?: { taskId?: string } }).body?.taskId ?? ""; const wait = waitMs > 0 && taskId.length > 0 ? await waitForTask(taskId, waitMs) : null; return { dispatch, wait }; }