RaBU1234 commited on
Commit
4bbcfdc
·
verified ·
1 Parent(s): 2a37db0

Rename firecracker-manager.js to gvisor-manager.js

Browse files
Files changed (2) hide show
  1. firecracker-manager.js +0 -240
  2. gvisor-manager.js +217 -0
firecracker-manager.js DELETED
@@ -1,240 +0,0 @@
1
- const { spawn, exec } = require('child_process');
2
- const fs = require('fs').promises;
3
- const path = require('path');
4
- const { v4: uuidv4 } = require('uuid');
5
-
6
- class FirecrackerManager {
7
- constructor() {
8
- this.vms = new Map();
9
- this.baseKernel = '/firecracker/vmlinux.bin';
10
- this.baseRootfs = '/firecracker/rootfs.ext4';
11
- this.vmPath = '/tmp/firecracker-vms';
12
- }
13
-
14
- async initialize() {
15
- // Create VM directory
16
- await fs.mkdir(this.vmPath, { recursive: true });
17
-
18
- // Setup KVM access
19
- try {
20
- await this.execCommand('chmod 666 /dev/kvm');
21
- console.log('✅ KVM access configured');
22
- } catch (e) {
23
- console.error('⚠️ KVM setup failed:', e.message);
24
- }
25
- }
26
-
27
- execCommand(cmd) {
28
- return new Promise((resolve, reject) => {
29
- exec(cmd, (error, stdout, stderr) => {
30
- if (error) reject(error);
31
- else resolve(stdout);
32
- });
33
- });
34
- }
35
-
36
- async createVM(sandboxId) {
37
- const vmId = uuidv4();
38
- const vmDir = path.join(this.vmPath, vmId);
39
- const socketPath = path.join(vmDir, 'firecracker.socket');
40
- const rootfsPath = path.join(vmDir, 'rootfs.ext4');
41
-
42
- // Create VM directory
43
- await fs.mkdir(vmDir, { recursive: true });
44
-
45
- // Copy rootfs for this VM (isolated filesystem)
46
- await fs.copyFile(this.baseRootfs, rootfsPath);
47
-
48
- // Create VM configuration
49
- const config = {
50
- 'boot-source': {
51
- 'kernel_image_path': this.baseKernel,
52
- 'boot_args': 'ro console=ttyS0 noapic reboot=k panic=1 pci=off'
53
- },
54
- 'drives': [{
55
- 'drive_id': 'rootfs',
56
- 'path_on_host': rootfsPath,
57
- 'is_root_device': true,
58
- 'is_read_only': false
59
- }],
60
- 'machine-config': {
61
- 'vcpu_count': 2,
62
- 'mem_size_mib': 512,
63
- 'ht_enabled': false
64
- },
65
- 'network-interfaces': [{
66
- 'iface_id': 'eth0',
67
- 'guest_mac': this.generateMAC(),
68
- 'host_dev_name': `tap-${vmId.substr(0, 8)}`
69
- }]
70
- };
71
-
72
- const configPath = path.join(vmDir, 'config.json');
73
- await fs.writeFile(configPath, JSON.stringify(config, null, 2));
74
-
75
- // Setup network interface
76
- await this.setupNetworking(config['network-interfaces'][0].host_dev_name);
77
-
78
- // Start Firecracker process
79
- const fcProcess = spawn('firecracker', [
80
- '--api-sock', socketPath,
81
- '--config-file', configPath
82
- ], {
83
- detached: true,
84
- stdio: ['ignore', 'pipe', 'pipe']
85
- });
86
-
87
- fcProcess.unref();
88
-
89
- const vmInfo = {
90
- vmId,
91
- sandboxId,
92
- socketPath,
93
- configPath,
94
- rootfsPath,
95
- vmDir,
96
- process: fcProcess,
97
- createdAt: Date.now(),
98
- status: 'starting'
99
- };
100
-
101
- this.vms.set(sandboxId, vmInfo);
102
-
103
- // Wait for VM to boot
104
- await this.waitForBoot(socketPath);
105
- vmInfo.status = 'running';
106
-
107
- console.log(`✅ Firecracker VM created: ${vmId} for sandbox ${sandboxId}`);
108
- return vmInfo;
109
- }
110
-
111
- async setupNetworking(tapName) {
112
- try {
113
- // Create TAP device
114
- await this.execCommand(`ip tuntap add ${tapName} mode tap`);
115
- await this.execCommand(`ip addr add 172.16.0.1/24 dev ${tapName}`);
116
- await this.execCommand(`ip link set ${tapName} up`);
117
-
118
- // Enable IP forwarding
119
- await this.execCommand('echo 1 > /proc/sys/net/ipv4/ip_forward');
120
-
121
- // Setup NAT
122
- await this.execCommand(`iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE`);
123
- await this.execCommand(`iptables -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT`);
124
- await this.execCommand(`iptables -A FORWARD -i ${tapName} -o eth0 -j ACCEPT`);
125
-
126
- console.log(`✅ Network configured: ${tapName}`);
127
- } catch (e) {
128
- console.error('⚠️ Network setup error:', e.message);
129
- }
130
- }
131
-
132
- generateMAC() {
133
- return '06:00:' +
134
- [...Array(4)].map(() =>
135
- Math.floor(Math.random() * 256).toString(16).padStart(2, '0')
136
- ).join(':');
137
- }
138
-
139
- async waitForBoot(socketPath, maxAttempts = 30) {
140
- for (let i = 0; i < maxAttempts; i++) {
141
- try {
142
- const exists = await fs.access(socketPath).then(() => true).catch(() => false);
143
- if (exists) {
144
- await new Promise(resolve => setTimeout(resolve, 500));
145
- return true;
146
- }
147
- } catch (e) {}
148
- await new Promise(resolve => setTimeout(resolve, 1000));
149
- }
150
- throw new Error('VM failed to boot');
151
- }
152
-
153
- async executeInVM(sandboxId, command) {
154
- const vm = this.vms.get(sandboxId);
155
- if (!vm) throw new Error('VM not found');
156
-
157
- // Execute command via Firecracker API
158
- const result = await this.execCommand(`
159
- curl --unix-socket ${vm.socketPath} -i \
160
- -X PUT 'http://localhost/actions' \
161
- -H 'Content-Type: application/json' \
162
- -d '{"action_type": "SendCtrlAltDel"}'
163
- `);
164
-
165
- return result;
166
- }
167
-
168
- async writeFile(sandboxId, filePath, content) {
169
- const vm = this.vms.get(sandboxId);
170
- if (!vm) throw new Error('VM not found');
171
-
172
- // Mount rootfs and write file
173
- const mountPoint = path.join(vm.vmDir, 'mount');
174
- await fs.mkdir(mountPoint, { recursive: true });
175
-
176
- try {
177
- // Pause VM
178
- await this.execCommand(`
179
- curl --unix-socket ${vm.socketPath} -i \
180
- -X PATCH 'http://localhost/vm' \
181
- -H 'Content-Type: application/json' \
182
- -d '{"state": "Paused"}'
183
- `);
184
-
185
- // Mount filesystem
186
- await this.execCommand(`mount -o loop ${vm.rootfsPath} ${mountPoint}`);
187
-
188
- // Write file
189
- const fullPath = path.join(mountPoint, filePath);
190
- await fs.mkdir(path.dirname(fullPath), { recursive: true });
191
- await fs.writeFile(fullPath, content);
192
-
193
- // Unmount
194
- await this.execCommand(`umount ${mountPoint}`);
195
-
196
- // Resume VM
197
- await this.execCommand(`
198
- curl --unix-socket ${vm.socketPath} -i \
199
- -X PATCH 'http://localhost/vm' \
200
- -H 'Content-Type: application/json' \
201
- -d '{"state": "Resumed"}'
202
- `);
203
-
204
- console.log(`✅ File written to VM: ${filePath}`);
205
- } catch (e) {
206
- console.error('❌ File write error:', e.message);
207
- throw e;
208
- }
209
- }
210
-
211
- async destroyVM(sandboxId) {
212
- const vm = this.vms.get(sandboxId);
213
- if (!vm) return;
214
-
215
- console.log(`🧹 Destroying VM: ${vm.vmId}`);
216
-
217
- try {
218
- // Stop VM
219
- vm.process.kill('SIGTERM');
220
-
221
- // Cleanup network
222
- const tapName = `tap-${vm.vmId.substr(0, 8)}`;
223
- await this.execCommand(`ip link delete ${tapName}`).catch(() => {});
224
-
225
- // Remove VM directory
226
- await fs.rm(vm.vmDir, { recursive: true, force: true });
227
-
228
- this.vms.delete(sandboxId);
229
- console.log(`✅ VM destroyed: ${vm.vmId}`);
230
- } catch (e) {
231
- console.error('⚠️ VM cleanup error:', e.message);
232
- }
233
- }
234
-
235
- getVM(sandboxId) {
236
- return this.vms.get(sandboxId);
237
- }
238
- }
239
-
240
- module.exports = FirecrackerManager;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
gvisor-manager.js ADDED
@@ -0,0 +1,217 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const { Docker } = require('dockerode');
2
+ const { spawn, exec } = require('child_process');
3
+ const fs = require('fs').promises;
4
+ const path = require('path');
5
+
6
+ class GVisorSandboxManager {
7
+ constructor() {
8
+ this.docker = new Docker({ socketPath: '/var/run/docker.sock' });
9
+ this.sandboxes = new Map();
10
+ }
11
+
12
+ async initialize() {
13
+ try {
14
+ // Start Docker daemon
15
+ this.dockerProcess = spawn('dockerd', [
16
+ '--host=unix:///var/run/docker.sock',
17
+ '--config-file=/etc/docker/daemon.json'
18
+ ], {
19
+ detached: true,
20
+ stdio: 'ignore'
21
+ });
22
+
23
+ this.dockerProcess.unref();
24
+
25
+ // Wait for Docker to be ready
26
+ await this.waitForDocker();
27
+ console.log('✅ Docker daemon started with gVisor runtime');
28
+
29
+ // Pull base image
30
+ await this.pullImage('node:18-alpine');
31
+ console.log('✅ Base image ready');
32
+
33
+ } catch (e) {
34
+ console.error('⚠️ Docker initialization error:', e.message);
35
+ }
36
+ }
37
+
38
+ async waitForDocker(maxAttempts = 30) {
39
+ for (let i = 0; i < maxAttempts; i++) {
40
+ try {
41
+ await this.docker.ping();
42
+ return true;
43
+ } catch (e) {
44
+ await new Promise(resolve => setTimeout(resolve, 1000));
45
+ }
46
+ }
47
+ throw new Error('Docker daemon failed to start');
48
+ }
49
+
50
+ async pullImage(image) {
51
+ return new Promise((resolve, reject) => {
52
+ this.docker.pull(image, (err, stream) => {
53
+ if (err) return reject(err);
54
+
55
+ this.docker.modem.followProgress(stream, (err, output) => {
56
+ if (err) reject(err);
57
+ else resolve(output);
58
+ });
59
+ });
60
+ });
61
+ }
62
+
63
+ async createSandbox(sandboxId) {
64
+ const containerName = `sandbox-${sandboxId}`;
65
+
66
+ // Create container with gVisor runtime
67
+ const container = await this.docker.createContainer({
68
+ name: containerName,
69
+ Image: 'node:18-alpine',
70
+
71
+ // Use gVisor runtime
72
+ HostConfig: {
73
+ Runtime: 'runsc',
74
+
75
+ // Resource limits
76
+ Memory: 512 * 1024 * 1024, // 512MB
77
+ MemorySwap: 512 * 1024 * 1024,
78
+ CpuQuota: 50000,
79
+ CpuPeriod: 100000,
80
+ PidsLimit: 100,
81
+
82
+ // Network isolation (optional)
83
+ NetworkMode: 'bridge',
84
+
85
+ // Security
86
+ SecurityOpt: ['no-new-privileges'],
87
+ CapDrop: ['ALL'],
88
+ CapAdd: ['NET_BIND_SERVICE'],
89
+
90
+ // Filesystem
91
+ ReadonlyRootfs: false,
92
+ Tmpfs: {
93
+ '/tmp': 'rw,noexec,nosuid,size=100m',
94
+ '/workspace': 'rw,exec,nosuid,size=500m'
95
+ }
96
+ },
97
+
98
+ WorkingDir: '/workspace',
99
+
100
+ // Keep container running
101
+ Cmd: ['sh', '-c', 'while true; do sleep 3600; done'],
102
+
103
+ // Environment
104
+ Env: [
105
+ 'NODE_ENV=sandbox',
106
+ 'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
107
+ ]
108
+ });
109
+
110
+ await container.start();
111
+
112
+ const sandbox = {
113
+ sandboxId,
114
+ container,
115
+ containerId: container.id,
116
+ createdAt: Date.now(),
117
+ status: 'running'
118
+ };
119
+
120
+ this.sandboxes.set(sandboxId, sandbox);
121
+
122
+ console.log(`✅ gVisor sandbox created: ${sandboxId}`);
123
+ return sandbox;
124
+ }
125
+
126
+ async executeCommand(sandboxId, command, args = []) {
127
+ const sandbox = this.sandboxes.get(sandboxId);
128
+ if (!sandbox) throw new Error('Sandbox not found');
129
+
130
+ const fullCommand = `${command} ${args.join(' ')}`;
131
+
132
+ const exec = await sandbox.container.exec({
133
+ Cmd: ['sh', '-c', fullCommand],
134
+ AttachStdout: true,
135
+ AttachStderr: true,
136
+ WorkingDir: '/workspace'
137
+ });
138
+
139
+ return new Promise((resolve, reject) => {
140
+ exec.start({ hijack: true, stdin: false }, (err, stream) => {
141
+ if (err) return reject(err);
142
+
143
+ let stdout = '';
144
+ let stderr = '';
145
+
146
+ stream.on('data', (chunk) => {
147
+ const str = chunk.toString();
148
+ stdout += str;
149
+ });
150
+
151
+ stream.on('end', async () => {
152
+ const inspectData = await exec.inspect();
153
+ resolve({
154
+ exitCode: inspectData.ExitCode,
155
+ stdout,
156
+ stderr
157
+ });
158
+ });
159
+ });
160
+ });
161
+ }
162
+
163
+ async writeFiles(sandboxId, files) {
164
+ const sandbox = this.sandboxes.get(sandboxId);
165
+ if (!sandbox) throw new Error('Sandbox not found');
166
+
167
+ for (const file of files) {
168
+ const filePath = path.join('/workspace', file.path);
169
+ const dir = path.dirname(filePath);
170
+
171
+ // Create directory
172
+ await this.executeCommand(sandboxId, 'mkdir', ['-p', dir]);
173
+
174
+ // Write file using echo (base64 encoded to handle special chars)
175
+ const base64Content = Buffer.from(file.content).toString('base64');
176
+ await this.executeCommand(sandboxId, 'sh', [
177
+ '-c',
178
+ `echo '${base64Content}' | base64 -d > ${filePath}`
179
+ ]);
180
+
181
+ console.log(`📝 File written: ${file.path}`);
182
+ }
183
+ }
184
+
185
+ async readFile(sandboxId, filePath) {
186
+ const sandbox = this.sandboxes.get(sandboxId);
187
+ if (!sandbox) throw new Error('Sandbox not found');
188
+
189
+ const result = await this.executeCommand(sandboxId, 'cat', [
190
+ path.join('/workspace', filePath)
191
+ ]);
192
+
193
+ return result.stdout;
194
+ }
195
+
196
+ async destroySandbox(sandboxId) {
197
+ const sandbox = this.sandboxes.get(sandboxId);
198
+ if (!sandbox) return;
199
+
200
+ console.log(`🧹 Destroying sandbox: ${sandboxId}`);
201
+
202
+ try {
203
+ await sandbox.container.stop({ t: 5 });
204
+ await sandbox.container.remove({ force: true });
205
+ this.sandboxes.delete(sandboxId);
206
+ console.log(`✅ Sandbox destroyed: ${sandboxId}`);
207
+ } catch (e) {
208
+ console.error('⚠️ Cleanup error:', e.message);
209
+ }
210
+ }
211
+
212
+ getSandbox(sandboxId) {
213
+ return this.sandboxes.get(sandboxId);
214
+ }
215
+ }
216
+
217
+ module.exports = GVisorSandboxManager;