|
|
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 }); |
|
|
|
|
|
|
|
|
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; |
|
|
} |
|
|
|
|
|
|
|
|
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'); |
|
|
|
|
|
|
|
|
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) => { |
|
|
|
|
|
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'; |
|
|
}); |
|
|
|
|
|
|
|
|
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'); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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; |