RaBU1234 commited on
Commit
2a4979d
·
verified ·
1 Parent(s): c40ab75

Update gvisor-manager.js

Browse files
Files changed (1) hide show
  1. gvisor-manager.js +182 -146
gvisor-manager.js CHANGED
@@ -1,182 +1,177 @@
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
  }
@@ -186,11 +181,24 @@ class GVisorSandboxManager {
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) {
@@ -199,19 +207,47 @@ class GVisorSandboxManager {
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;
 
 
1
  const { spawn, exec } = require('child_process');
2
  const fs = require('fs').promises;
3
  const path = require('path');
4
 
5
+ class SimpleSandboxManager {
6
  constructor() {
 
7
  this.sandboxes = new Map();
8
+ this.processes = new Map();
9
+ this.basePath = '/tmp/sandboxes';
10
  }
11
 
12
  async initialize() {
13
+ await fs.mkdir(this.basePath, { recursive: true });
14
+ console.log('✅ Simple Sandbox Manager initialized');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  }
16
 
17
  async createSandbox(sandboxId) {
18
+ const sandboxPath = path.join(this.basePath, sandboxId);
19
+ await fs.mkdir(sandboxPath, { recursive: true });
20
 
21
+ // Create workspace directory
22
+ const workspacePath = path.join(sandboxPath, 'workspace');
23
+ await fs.mkdir(workspacePath, { recursive: true });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
 
25
  const sandbox = {
26
  sandboxId,
27
+ path: sandboxPath,
28
+ workspacePath,
29
  createdAt: Date.now(),
30
+ lastActivity: Date.now(),
31
+ status: 'ready'
32
  };
33
 
34
  this.sandboxes.set(sandboxId, sandbox);
35
+ console.log(`✅ Sandbox created: ${sandboxId}`);
36
 
 
37
  return sandbox;
38
  }
39
 
40
+ async executeCommand(sandboxId, command, args = [], options = {}) {
41
  const sandbox = this.sandboxes.get(sandboxId);
42
  if (!sandbox) throw new Error('Sandbox not found');
43
 
44
+ sandbox.lastActivity = Date.now();
45
 
46
+ const { background = false, timeout = 300000 } = options;
47
+
48
+ // Security: Whitelist commands
49
+ const allowedCommands = [
50
+ 'node', 'npm', 'npx', 'python3', 'python', 'pip',
51
+ 'go', 'cat', 'ls', 'mkdir', 'echo', 'touch',
52
+ 'rm', 'cp', 'mv', 'pwd', 'which'
53
+ ];
54
+
55
+ const baseCommand = command.split(' ')[0];
56
+ if (!allowedCommands.includes(baseCommand)) {
57
+ throw new Error(`Command not allowed: ${baseCommand}`);
58
+ }
59
 
60
+ const fullCommand = `${command} ${args.join(' ')}`;
61
+
62
+ if (background) {
63
+ // Background process (dev server)
64
+ return this.startBackgroundProcess(sandboxId, fullCommand, sandbox.workspacePath);
65
+ } else {
66
+ // Synchronous command
67
+ return this.runSyncCommand(fullCommand, sandbox.workspacePath, timeout);
68
+ }
69
+ }
70
+
71
+ startBackgroundProcess(sandboxId, command, cwd) {
72
  return new Promise((resolve, reject) => {
73
+ const cmdId = `cmd-${Date.now()}`;
74
+
75
+ const proc = spawn('sh', ['-c', command], {
76
+ cwd,
77
+ detached: true,
78
+ stdio: ['ignore', 'pipe', 'pipe'],
79
+ env: {
80
+ ...process.env,
81
+ PORT: '3000',
82
+ NODE_ENV: 'development'
83
+ }
84
+ });
85
+
86
+ proc.unref();
87
+
88
+ let stdout = '';
89
+ let stderr = '';
90
+
91
+ proc.stdout.on('data', (data) => {
92
+ stdout += data.toString();
93
+ console.log(`[${sandboxId}][stdout]: ${data.toString().trim()}`);
94
+ });
95
+
96
+ proc.stderr.on('data', (data) => {
97
+ stderr += data.toString();
98
+ console.log(`[${sandboxId}][stderr]: ${data.toString().trim()}`);
99
+ });
100
+
101
+ // Store process
102
+ this.processes.set(sandboxId, {
103
+ cmdId,
104
+ process: proc,
105
+ stdout,
106
+ stderr,
107
+ startedAt: Date.now()
108
+ });
109
+
110
+ // Wait a bit to check if it started successfully
111
+ setTimeout(() => {
112
+ if (proc.exitCode !== null) {
113
+ reject(new Error('Process exited immediately'));
114
+ } else {
115
  resolve({
116
+ commandId: cmdId,
117
+ status: 'running',
118
+ pid: proc.pid
119
  });
120
+ }
121
+ }, 2000);
122
+ });
123
+ }
124
+
125
+ runSyncCommand(command, cwd, timeout) {
126
+ return new Promise((resolve, reject) => {
127
+ exec(command, {
128
+ cwd,
129
+ timeout,
130
+ maxBuffer: 10 * 1024 * 1024, // 10MB
131
+ env: {
132
+ ...process.env,
133
+ NODE_ENV: 'sandbox'
134
+ }
135
+ }, (error, stdout, stderr) => {
136
+ resolve({
137
+ exitCode: error ? (error.code || 1) : 0,
138
+ stdout: stdout.toString(),
139
+ stderr: stderr.toString()
140
  });
141
  });
142
  });
143
  }
144
 
145
+ async stopCommand(sandboxId) {
146
+ const procInfo = this.processes.get(sandboxId);
147
+ if (!procInfo) {
148
+ throw new Error('No running process found');
149
+ }
150
+
151
+ try {
152
+ // Kill process group
153
+ process.kill(-procInfo.process.pid, 'SIGTERM');
154
+ this.processes.delete(sandboxId);
155
+ console.log(`🛑 Process stopped for ${sandboxId}`);
156
+ return { stopped: true };
157
+ } catch (e) {
158
+ console.error('Failed to stop process:', e);
159
+ throw e;
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
+ sandbox.lastActivity = Date.now();
168
+
169
  for (const file of files) {
170
+ const fullPath = path.join(sandbox.workspacePath, file.path);
171
+ const dir = path.dirname(fullPath);
 
 
 
172
 
173
+ await fs.mkdir(dir, { recursive: true });
174
+ await fs.writeFile(fullPath, file.content, 'utf-8');
 
 
 
 
175
 
176
  console.log(`📝 File written: ${file.path}`);
177
  }
 
181
  const sandbox = this.sandboxes.get(sandboxId);
182
  if (!sandbox) throw new Error('Sandbox not found');
183
 
184
+ const fullPath = path.join(sandbox.workspacePath, filePath);
185
+ const content = await fs.readFile(fullPath, 'utf-8');
 
186
 
187
+ return content;
188
+ }
189
+
190
+ async listFiles(sandboxId, dirPath = '.') {
191
+ const sandbox = this.sandboxes.get(sandboxId);
192
+ if (!sandbox) throw new Error('Sandbox not found');
193
+
194
+ const fullPath = path.join(sandbox.workspacePath, dirPath);
195
+ const files = await fs.readdir(fullPath, { withFileTypes: true });
196
+
197
+ return files.map(file => ({
198
+ name: file.name,
199
+ isDirectory: file.isDirectory(),
200
+ path: path.join(dirPath, file.name)
201
+ }));
202
  }
203
 
204
  async destroySandbox(sandboxId) {
 
207
 
208
  console.log(`🧹 Destroying sandbox: ${sandboxId}`);
209
 
210
+ // Stop any running processes
211
+ const procInfo = this.processes.get(sandboxId);
212
+ if (procInfo) {
213
+ try {
214
+ process.kill(-procInfo.process.pid, 'SIGTERM');
215
+ this.processes.delete(sandboxId);
216
+ } catch (e) {
217
+ console.error('Process kill error:', e.message);
218
+ }
219
+ }
220
+
221
+ // Remove files
222
  try {
223
+ await fs.rm(sandbox.path, { recursive: true, force: true });
 
 
 
224
  } catch (e) {
225
+ console.error('File cleanup error:', e.message);
226
  }
227
+
228
+ this.sandboxes.delete(sandboxId);
229
+ console.log(`✅ Sandbox destroyed: ${sandboxId}`);
230
  }
231
 
232
  getSandbox(sandboxId) {
233
  return this.sandboxes.get(sandboxId);
234
  }
235
+
236
+ getProcess(sandboxId) {
237
+ return this.processes.get(sandboxId);
238
+ }
239
+
240
+ // Cleanup inactive sandboxes
241
+ async cleanupInactive(inactiveTimeout = 15 * 60 * 1000) {
242
+ const now = Date.now();
243
+
244
+ for (const [id, sandbox] of this.sandboxes.entries()) {
245
+ if (now - sandbox.lastActivity > inactiveTimeout) {
246
+ console.log(`⏰ Cleaning up inactive sandbox: ${id}`);
247
+ await this.destroySandbox(id);
248
+ }
249
+ }
250
+ }
251
  }
252
 
253
+ module.exports = SimpleSandboxManager;