import { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http'; import { spawn } from 'node:child_process'; import { access } from 'node:fs/promises'; const imagePath = process.env.BASEOS_QEMU_IMAGE; const cloudlyPort = Number(process.env.BASEOS_QEMU_CLOUDLY_PORT || '18080'); const bootTimeoutMs = Number(process.env.BASEOS_QEMU_BOOT_TIMEOUT_MS || String(1000 * 60 * 5)); const commandExists = async (commandArg: string) => { const child = spawn('bash', ['-lc', `command -v ${commandArg}`], { stdio: 'ignore' }); return await new Promise((resolveArg) => { child.on('close', (codeArg) => resolveArg(codeArg === 0)); child.on('error', () => resolveArg(false)); }); }; const readJsonBody = async (reqArg: IncomingMessage) => { const chunks: Buffer[] = []; for await (const chunk of reqArg) { chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)); } const bodyText = Buffer.concat(chunks).toString('utf8').trim(); return bodyText ? JSON.parse(bodyText) as Record : {}; }; const sendJson = (resArg: ServerResponse, statusCodeArg: number, bodyArg: Record) => { resArg.statusCode = statusCodeArg; resArg.setHeader('content-type', 'application/json'); resArg.end(JSON.stringify(bodyArg)); }; const startMockCloudly = async () => { let registrationBody: Record | undefined; const server = createServer(async (req, res) => { try { const requestUrl = new URL(req.url || '/', `http://127.0.0.1:${cloudlyPort}`); if (req.method === 'POST' && requestUrl.pathname === '/baseos/v1/nodes/register') { registrationBody = await readJsonBody(req); sendJson(res, 200, { accepted: true, nodeId: 'qemu-baseos-node', nodeToken: 'qemu-node-token', desiredState: {}, }); return; } if (req.method === 'POST' && requestUrl.pathname === '/baseos/v1/nodes/heartbeat') { sendJson(res, 200, { accepted: true, desiredState: {}, }); return; } sendJson(res, 404, { errorText: 'not found' }); } catch (error) { sendJson(res, 500, { errorText: (error as Error).message }); } }); await new Promise((resolveArg) => server.listen({ port: cloudlyPort, host: '0.0.0.0' }, resolveArg)); return { server, getRegistrationBody: () => registrationBody, }; }; const stopServer = async (serverArg: Server) => { await new Promise((resolveArg, rejectArg) => { serverArg.close((errorArg) => errorArg ? rejectArg(errorArg) : resolveArg()); }); }; const waitForRegistration = async (getRegistrationBodyArg: () => Record | undefined) => { const startTime = Date.now(); while (Date.now() - startTime < bootTimeoutMs) { const registrationBody = getRegistrationBodyArg(); if (registrationBody) { return registrationBody; } await new Promise((resolveArg) => setTimeout(resolveArg, 1000)); } throw new Error(`BaseOS QEMU image did not register within ${bootTimeoutMs}ms`); }; const main = async () => { if (!imagePath) { console.log('[baseos-qemu-enrollment] Skipping: BASEOS_QEMU_IMAGE is not set'); return; } await access(imagePath); if (!(await commandExists('qemu-system-x86_64'))) { throw new Error('Missing required command for BaseOS QEMU scenario: qemu-system-x86_64'); } const mockCloudly = await startMockCloudly(); const qemu = spawn('qemu-system-x86_64', [ '-machine', 'accel=kvm:tcg', '-m', process.env.BASEOS_QEMU_MEMORY || '2048', '-smp', process.env.BASEOS_QEMU_CPUS || '2', '-nographic', '-no-reboot', '-drive', `file=${imagePath},format=raw,if=virtio`, '-netdev', 'user,id=net0', '-device', 'virtio-net-pci,netdev=net0', '-serial', 'mon:stdio', ], { stdio: ['ignore', 'pipe', 'pipe'], }); const logs: string[] = []; const collectLog = (chunkArg: Buffer) => { logs.push(chunkArg.toString('utf8')); if (logs.length > 100) { logs.splice(0, logs.length - 100); } }; qemu.stdout.on('data', collectLog); qemu.stderr.on('data', collectLog); try { const registrationBody = await Promise.race([ waitForRegistration(mockCloudly.getRegistrationBody), new Promise((_resolveArg, rejectArg) => { qemu.on('close', (codeArg) => { rejectArg(new Error(`QEMU exited before BaseOS registration with code ${codeArg}\n${logs.join('')}`)); }); }), ]); const status = registrationBody.status as { runtime?: string; nodeId?: string } | undefined; if (status?.runtime !== 'baseos' || !status.nodeId) { throw new Error(`Invalid BaseOS registration payload: ${JSON.stringify(registrationBody)}`); } console.log('[baseos-qemu-enrollment] BaseOS QEMU enrollment scenario completed successfully'); } finally { qemu.kill('SIGTERM'); setTimeout(() => qemu.kill('SIGKILL'), 5000).unref(); await stopServer(mockCloudly.server).catch(() => undefined); } }; await main();