import { json } from '@sveltejs/kit'; import { authorize } from '$lib/server/authorize'; import { unixSocketRequest } from '$lib/server/docker'; import type { RequestHandler } from './$types'; const DOCKER_SOCKET = process.env.DOCKER_SOCKET || '/var/run/docker.sock'; /** Fetch from the local Docker socket directly */ function localDockerFetch(path: string): Promise { return unixSocketRequest(DOCKER_SOCKET, path); } /** * Strip Docker log stream multiplexing headers. * Docker prefixes each frame with an 8-byte header: * [stream_type(1)] [0(3)] [size(4 big-endian)] */ function stripDockerLogHeaders(raw: Uint8Array): string { const lines: string[] = []; let offset = 0; while (offset < raw.length) { // Check if we have a Docker stream header (8 bytes) if (offset + 8 <= raw.length) { const streamType = raw[offset]; // Stream type should be 0 (stdin), 1 (stdout), or 2 (stderr) if (streamType <= 2) { // Read the 4-byte big-endian size const size = (raw[offset + 4] << 24) | (raw[offset + 5] << 16) | (raw[offset + 6] << 8) | raw[offset + 7]; if (size > 0 && offset + 8 + size <= raw.length) { const frameData = new TextDecoder().decode(raw.slice(offset + 8, offset + 8 + size)); const frameLines = frameData.split('\n'); for (const line of frameLines) { if (line.trim()) { lines.push(line); } } offset += 8 + size; continue; } } } // Fallback: decode remaining as plain text const remaining = new TextDecoder().decode(raw.slice(offset)); const remainingLines = remaining.split('\n'); for (const line of remainingLines) { if (line.trim()) { lines.push(line); } } break; } return lines.join('\n'); } /** * Poll updater container logs and status for progress tracking. */ export const GET: RequestHandler = async ({ url, cookies }) => { const auth = await authorize(cookies); if (auth.authEnabled && !auth.isAdmin) { return json({ error: 'Admin access required' }, { status: 403 }); } const containerId = url.searchParams.get('id'); if (!containerId) { return json({ error: 'Container ID is required' }, { status: 400 }); } try { // Check container state const inspectResponse = await localDockerFetch(`/containers/${containerId}/json`); if (!inspectResponse.ok) { if (inspectResponse.status === 404) { // Container removed (AutoRemove after exit) return json({ logs: '', status: 'removed' }); } return json({ error: 'Failed to inspect container' }, { status: 500 }); } const info = await inspectResponse.json() as { State?: { Status: string; ExitCode: number; Running: boolean }; }; const status = info.State?.Running ? 'running' : 'exited'; const exitCode = info.State?.ExitCode ?? 0; // Fetch logs const logsResponse = await localDockerFetch( `/containers/${containerId}/logs?stdout=true&stderr=true×tamps=false` ); let logs = ''; if (logsResponse.ok && logsResponse.body) { const rawBytes = new Uint8Array(await logsResponse.arrayBuffer()); logs = stripDockerLogHeaders(rawBytes); } return json({ logs, status, exitCode }); } catch (err) { return json({ error: 'Failed to fetch progress: ' + String(err) }, { status: 500 }); } };