Sendbox / firecracker-manager.js
RaBU1234's picture
Create firecracker-manager.js
f993ca2 verified
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;