RaBU1234 commited on
Commit
9fc1869
·
verified ·
1 Parent(s): cdda8ef

Create simple-sandbox-manager.js

Browse files
Files changed (1) hide show
  1. simple-sandbox-manager.js +249 -0
simple-sandbox-manager.js ADDED
@@ -0,0 +1,249 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ this.portCounter = 3000;
11
+ }
12
+
13
+ async initialize() {
14
+ await fs.mkdir(this.basePath, { recursive: true });
15
+ console.log('✅ Simple Sandbox Manager initialized');
16
+ }
17
+
18
+ async createSandbox(sandboxId) {
19
+ const sandboxPath = path.join(this.basePath, sandboxId);
20
+ await fs.mkdir(sandboxPath, { recursive: true });
21
+
22
+ const workspacePath = path.join(sandboxPath, 'workspace');
23
+ await fs.mkdir(workspacePath, { recursive: true });
24
+
25
+ const port = this.portCounter++;
26
+
27
+ const sandbox = {
28
+ sandboxId,
29
+ path: sandboxPath,
30
+ workspacePath,
31
+ port,
32
+ createdAt: Date.now(),
33
+ lastActivity: Date.now(),
34
+ status: 'ready'
35
+ };
36
+
37
+ this.sandboxes.set(sandboxId, sandbox);
38
+ console.log(`✅ Sandbox created: ${sandboxId} (port: ${port})`);
39
+
40
+ return sandbox;
41
+ }
42
+
43
+ async executeCommand(sandboxId, command, args = [], options = {}) {
44
+ const sandbox = this.sandboxes.get(sandboxId);
45
+ if (!sandbox) throw new Error('Sandbox not found');
46
+
47
+ sandbox.lastActivity = Date.now();
48
+
49
+ const { background = false, timeout = 300000 } = options;
50
+
51
+ const allowedCommands = [
52
+ 'node', 'npm', 'npx', 'python3', 'python', 'pip',
53
+ 'go', 'cat', 'ls', 'mkdir', 'echo', 'touch',
54
+ 'rm', 'cp', 'mv', 'pwd', 'which'
55
+ ];
56
+
57
+ const baseCommand = command.split(' ')[0];
58
+ if (!allowedCommands.includes(baseCommand)) {
59
+ throw new Error(`Command not allowed: ${baseCommand}`);
60
+ }
61
+
62
+ const fullCommand = `${command} ${args.join(' ')}`;
63
+
64
+ if (background) {
65
+ return this.startBackgroundProcess(sandboxId, fullCommand, sandbox.workspacePath, sandbox.port);
66
+ } else {
67
+ return this.runSyncCommand(fullCommand, sandbox.workspacePath, timeout);
68
+ }
69
+ }
70
+
71
+ startBackgroundProcess(sandboxId, command, cwd, port) {
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: port.toString(),
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
+ this.processes.set(sandboxId, {
102
+ cmdId,
103
+ process: proc,
104
+ port,
105
+ stdout,
106
+ stderr,
107
+ startedAt: Date.now()
108
+ });
109
+
110
+ setTimeout(() => {
111
+ if (proc.exitCode !== null) {
112
+ reject(new Error('Process exited immediately'));
113
+ } else {
114
+ resolve({
115
+ commandId: cmdId,
116
+ status: 'running',
117
+ pid: proc.pid,
118
+ port
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,
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
+ process.kill(-procInfo.process.pid, 'SIGTERM');
153
+ this.processes.delete(sandboxId);
154
+ console.log(`🛑 Process stopped for ${sandboxId}`);
155
+ return { stopped: true };
156
+ } catch (e) {
157
+ console.error('Failed to stop process:', e);
158
+ throw e;
159
+ }
160
+ }
161
+
162
+ async writeFiles(sandboxId, files) {
163
+ const sandbox = this.sandboxes.get(sandboxId);
164
+ if (!sandbox) throw new Error('Sandbox not found');
165
+
166
+ sandbox.lastActivity = Date.now();
167
+
168
+ for (const file of files) {
169
+ const fullPath = path.join(sandbox.workspacePath, file.path);
170
+ const dir = path.dirname(fullPath);
171
+
172
+ await fs.mkdir(dir, { recursive: true });
173
+ await fs.writeFile(fullPath, file.content, 'utf-8');
174
+
175
+ console.log(`📝 File written: ${file.path}`);
176
+ }
177
+ }
178
+
179
+ async readFile(sandboxId, filePath) {
180
+ const sandbox = this.sandboxes.get(sandboxId);
181
+ if (!sandbox) throw new Error('Sandbox not found');
182
+
183
+ const fullPath = path.join(sandbox.workspacePath, filePath);
184
+ const content = await fs.readFile(fullPath, 'utf-8');
185
+
186
+ return content;
187
+ }
188
+
189
+ async listFiles(sandboxId, dirPath = '.') {
190
+ const sandbox = this.sandboxes.get(sandboxId);
191
+ if (!sandbox) throw new Error('Sandbox not found');
192
+
193
+ const fullPath = path.join(sandbox.workspacePath, dirPath);
194
+ const files = await fs.readdir(fullPath, { withFileTypes: true });
195
+
196
+ return files.map(file => ({
197
+ name: file.name,
198
+ isDirectory: file.isDirectory(),
199
+ path: path.join(dirPath, file.name)
200
+ }));
201
+ }
202
+
203
+ async destroySandbox(sandboxId) {
204
+ const sandbox = this.sandboxes.get(sandboxId);
205
+ if (!sandbox) return;
206
+
207
+ console.log(`🧹 Destroying sandbox: ${sandboxId}`);
208
+
209
+ const procInfo = this.processes.get(sandboxId);
210
+ if (procInfo) {
211
+ try {
212
+ process.kill(-procInfo.process.pid, 'SIGTERM');
213
+ this.processes.delete(sandboxId);
214
+ } catch (e) {
215
+ console.error('Process kill error:', e.message);
216
+ }
217
+ }
218
+
219
+ try {
220
+ await fs.rm(sandbox.path, { recursive: true, force: true });
221
+ } catch (e) {
222
+ console.error('File cleanup error:', e.message);
223
+ }
224
+
225
+ this.sandboxes.delete(sandboxId);
226
+ console.log(`✅ Sandbox destroyed: ${sandboxId}`);
227
+ }
228
+
229
+ getSandbox(sandboxId) {
230
+ return this.sandboxes.get(sandboxId);
231
+ }
232
+
233
+ getProcess(sandboxId) {
234
+ return this.processes.get(sandboxId);
235
+ }
236
+
237
+ async cleanupInactive(inactiveTimeout = 15 * 60 * 1000) {
238
+ const now = Date.now();
239
+
240
+ for (const [id, sandbox] of this.sandboxes.entries()) {
241
+ if (now - sandbox.lastActivity > inactiveTimeout) {
242
+ console.log(`⏰ Cleaning up inactive sandbox: ${id}`);
243
+ await this.destroySandbox(id);
244
+ }
245
+ }
246
+ }
247
+ }
248
+
249
+ module.exports = SimpleSandboxManager;