const { spawn, exec } = require('child_process'); const fs = require('fs').promises; const path = require('path'); const { EventEmitter } = require('events'); class FirecrackerManager extends EventEmitter { constructor() { super(); this.vms = new Map(); this.basePath = '/tmp/sandboxes'; this.kernelPath = '/tmp/vmlinux'; this.rootfsPath = '/tmp/rootfs.ext4'; this.firecrackerAvailable = false; } async initialize() { await fs.mkdir(this.basePath, { recursive: true }); // Check if Firecracker is available try { await new Promise((resolve, reject) => { exec('firecracker --version', (error, stdout) => { if (error) { console.warn('⚠️ Firecracker not available'); this.firecrackerAvailable = false; resolve(); } else { console.log('✅ Firecracker available:', stdout.trim()); this.firecrackerAvailable = true; resolve(); } }); }); } catch (e) { this.firecrackerAvailable = false; } // Check for KVM device try { await fs.access('/dev/kvm'); console.log('✅ KVM device available'); } catch (e) { console.warn('⚠️ /dev/kvm not found - Firecracker requires KVM'); this.firecrackerAvailable = false; } console.log(`Firecracker Manager initialized (available: ${this.firecrackerAvailable})`); } async createVM(vmId, options = {}) { if (!this.firecrackerAvailable) { throw new Error('Firecracker not available'); } const vmPath = path.join(this.basePath, vmId); await fs.mkdir(vmPath, { recursive: true }); const socketPath = path.join(vmPath, 'firecracker.sock'); const configPath = path.join(vmPath, 'config.json'); // Create Firecracker VM configuration const config = { 'boot-source': { kernel_image_path: this.kernelPath, boot_args: 'console=ttyS0 reboot=k panic=1 pci=off' }, 'drives': [{ drive_id: 'rootfs', path_on_host: this.rootfsPath, is_root_device: true, is_read_only: false }], 'machine-config': { vcpu_count: 1, mem_size_mib: 512, ht_enabled: false }, 'network-interfaces': [{ iface_id: 'eth0', guest_mac: '06:00:AC:10:00:02', host_dev_name: 'tap0' }] }; await fs.writeFile(configPath, JSON.stringify(config, null, 2)); const vm = { vmId, path: vmPath, socketPath, configPath, process: null, createdAt: Date.now(), status: 'created' }; this.vms.set(vmId, vm); return vm; } async startVM(vmId) { const vm = this.vms.get(vmId); if (!vm) throw new Error('VM not found'); return new Promise((resolve, reject) => { // Start Firecracker process const proc = spawn('firecracker', [ '--api-sock', vm.socketPath, '--config-file', vm.configPath ], { stdio: ['ignore', 'pipe', 'pipe'] }); vm.process = proc; vm.status = 'running'; proc.stdout.on('data', (data) => { console.log(`[${vmId}][stdout]: ${data.toString().trim()}`); }); proc.stderr.on('data', (data) => { console.log(`[${vmId}][stderr]: ${data.toString().trim()}`); }); proc.on('exit', (code) => { console.log(`[${vmId}] VM exited with code: ${code}`); vm.status = 'stopped'; }); // Wait for socket to be ready setTimeout(() => { resolve(vm); }, 2000); }); } async executeInVM(vmId, command) { const vm = this.vms.get(vmId); if (!vm) throw new Error('VM not found'); if (vm.status !== 'running') throw new Error('VM not running'); // Send command via Firecracker API // This would use the socket API to communicate with the VM // Implementation depends on your rootfs setup return { vmId, command, status: 'executed' }; } async stopVM(vmId) { const vm = this.vms.get(vmId); if (!vm) return; if (vm.process) { vm.process.kill('SIGTERM'); } try { await fs.rm(vm.path, { recursive: true, force: true }); } catch (e) { console.error('Cleanup error:', e.message); } this.vms.delete(vmId); } } module.exports = FirecrackerManager;