superchatai commited on
Commit
7af55cb
·
verified ·
1 Parent(s): 504ee23

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +16 -358
app.py CHANGED
@@ -1,358 +1,16 @@
1
- import asyncio
2
- import logging
3
- import subprocess
4
- import os
5
- import uuid
6
- import tempfile
7
- import sys
8
- from datetime import datetime, timedelta
9
-
10
- def install_dependencies():
11
- required_packages = ['fastapi', 'uvicorn']
12
- missing_packages = []
13
-
14
- for package in required_packages:
15
- try:
16
- __import__(package.replace('-', '_'))
17
- except ImportError:
18
- missing_packages.append(package)
19
-
20
- if missing_packages:
21
- print(f"Installing missing dependencies: {', '.join(missing_packages)}")
22
- try:
23
- subprocess.check_call([sys.executable, '-m', 'pip', 'install', '--user'] + missing_packages)
24
- print("Dependencies installed successfully!")
25
- except subprocess.CalledProcessError as e:
26
- print(f"Failed to install dependencies: {e}")
27
- sys.exit(1)
28
-
29
- install_dependencies()
30
-
31
- from fastapi import FastAPI, Request, HTTPException
32
- from fastapi.responses import PlainTextResponse, StreamingResponse, HTMLResponse
33
- from fastapi.middleware.cors import CORSMiddleware
34
-
35
- logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
36
-
37
- app = FastAPI()
38
-
39
- app.add_middleware(
40
- CORSMiddleware,
41
- allow_origins=["*"],
42
- allow_credentials=True,
43
- allow_methods=["*"],
44
- allow_headers=["*"],
45
- )
46
-
47
- def validate_command(command: str):
48
- if '..' in command or command.startswith('/') or '../' in command:
49
- raise HTTPException(status_code=403, detail="Access denied: cannot access files outside files directory")
50
-
51
- dangerous_commands = ['rm', 'del', 'format', 'fdisk', 'mkfs', 'dd', 'wget', 'curl', 'nc', 'netcat', 'ssh', 'scp', 'chmod', 'chown', 'sudo', 'su']
52
- for cmd in dangerous_commands:
53
- if cmd in command.lower():
54
- raise HTTPException(status_code=403, detail=f"Dangerous command not allowed: {cmd}")
55
-
56
- allowed_commands = ['python3', 'node', 'npm', 'pip', 'ls', 'cat', 'head', 'tail', 'grep', 'find', 'wc', 'sort', 'echo', 'whoami']
57
- command_parts = command.split()
58
- if command_parts and command_parts[0] not in allowed_commands:
59
- if not (command_parts[0].startswith('python3') or command_parts[0] in ['node', 'npm']):
60
- raise HTTPException(status_code=403, detail=f"Command not allowed: {command_parts[0]}")
61
-
62
- files = []
63
-
64
- ip_timestamps = {}
65
-
66
- async def check_stale_ips():
67
- while True:
68
- current_time = datetime.now()
69
- stale_ips = []
70
- for ip, timestamp in ip_timestamps.items():
71
- time_diff = current_time - timestamp
72
- if time_diff > timedelta(seconds=15):
73
- stale_ips.append(ip)
74
-
75
- if stale_ips:
76
- logging.info(f"Resetting files array due to stale IPs: {stale_ips}")
77
- files.clear()
78
- for ip in stale_ips:
79
- del ip_timestamps[ip]
80
-
81
- await asyncio.sleep(5)
82
-
83
- @app.on_event("startup")
84
- async def startup_event():
85
- asyncio.create_task(check_stale_ips())
86
-
87
- HTML_CONTENT = """<!DOCTYPE html>
88
- <html>
89
- <head>
90
- <title>Forge Code</title>
91
- </head>
92
- <body style="margin:20px;font-family:monospace;">
93
- <h1>Forge Code</h1>
94
-
95
- <div style="margin:20px 0;">
96
- <h3>app.py</h3>
97
- <div style="margin:10px 0;">
98
- <button onclick="loadTimeTemplate()">Time Printer</button>
99
- <button onclick="loadServerTemplate()">Web Server</button>
100
- </div>
101
- <textarea id="filecontent" placeholder="Write your Python code here..." style="width:100%;height:300px;margin:5px;"></textarea><br>
102
- <button onclick="saveFile()">Save</button>
103
- <button onclick="runApp()">Run App</button>
104
- </div>
105
-
106
- <div style="margin:20px 0;">
107
- <h3>Terminal Output</h3>
108
- <button onclick="clearTerminal()">Clear</button><br>
109
- <pre id="terminal" style="background:#000;color:#0f0;padding:10px;height:300px;overflow:auto;margin:5px 0;"></pre>
110
- </div>
111
-
112
- <script>
113
- function loadFile() {
114
- const stored = localStorage.getItem('app_py');
115
- if (stored) {
116
- document.getElementById('filecontent').value = stored;
117
- }
118
- }
119
-
120
- function saveFile() {
121
- const content = document.getElementById('filecontent').value;
122
- localStorage.setItem('app_py', content);
123
- log('Saved app.py');
124
- }
125
-
126
- function loadTimeTemplate() {
127
- const template = [
128
- 'import time',
129
- 'import datetime',
130
- '',
131
- 'while True:',
132
- ' now = datetime.datetime.now()',
133
- ' print(f"Current time: {now.strftime(\\'%Y-%m-%d %H:%M:%S\\')}")',
134
- ' time.sleep(1)'
135
- ].join('\\n');
136
- document.getElementById('filecontent').value = template;
137
- log('Loaded Time Printer template');
138
- }
139
-
140
- function loadServerTemplate() {
141
- const template = [
142
- 'from flask import Flask',
143
- 'import time',
144
- '',
145
- 'app = Flask(__name__)',
146
- '',
147
- '@app.route(\\'/\\')',
148
- 'def home():',
149
- ' return f"Hello from Forge Code! Time: {time.strftime(\\'%H:%M:%S\\')}"',
150
- '',
151
- 'if __name__ == \\'__main__\\':',
152
- ' print("Starting server on port 5000...")',
153
- ' print("Visit: http://localhost:5000")',
154
- ' app.run(host=\\'0.0.0.0\\', port=5000, debug=True)'
155
- ].join('\\n');
156
- document.getElementById('filecontent').value = template;
157
- log('Loaded Web Server template');
158
- }
159
-
160
- function runApp() {
161
- const content = document.getElementById('filecontent').value;
162
- if (!content.trim()) return alert('Write some code first!');
163
-
164
- document.getElementById('terminal').textContent = '';
165
-
166
- const formData = new FormData();
167
- formData.append('command', 'python3 app.py');
168
- formData.append('files', new Blob([content]), 'app.py');
169
-
170
- fetch('/execute', {
171
- method: 'POST',
172
- body: formData
173
- })
174
- .then(response => {
175
- const reader = response.body.getReader();
176
- const decoder = new TextDecoder();
177
-
178
- function readStream() {
179
- reader.read().then(({done, value}) => {
180
- if (done) {
181
- return;
182
- }
183
- const chunk = decoder.decode(value);
184
- const lines = chunk.split('\\n');
185
- lines.forEach(line => {
186
- if (line.startsWith('data: ')) {
187
- log(line.replace('data: ', ''));
188
- }
189
- });
190
- readStream();
191
- });
192
- }
193
- readStream();
194
- })
195
- .catch(e => log('Error: ' + e.message));
196
- }
197
-
198
- function log(msg) {
199
- const terminal = document.getElementById('terminal');
200
- terminal.textContent += msg + '\\n';
201
- terminal.scrollTop = terminal.scrollHeight;
202
- }
203
-
204
- function clearTerminal() {
205
- document.getElementById('terminal').textContent = '';
206
- }
207
-
208
- window.onload = loadFile;
209
- </script>
210
- </body>
211
- </html>"""
212
-
213
- @app.get("/", response_class=HTMLResponse)
214
- async def root():
215
- return HTMLResponse(content=HTML_CONTENT)
216
-
217
- @app.get("/execute")
218
- async def execute_get(command: str, files=None):
219
- if not command or not command.strip():
220
- raise HTTPException(status_code=400, detail="Command cannot be empty")
221
-
222
- if command.startswith('python '):
223
- command = 'python3 ' + command[7:]
224
- validate_command(command)
225
-
226
- container_id = str(uuid.uuid4())[:8]
227
- logging.info(f"Creating container {container_id} for command: {command}")
228
-
229
- if files is None:
230
- files = []
231
-
232
- async def generate():
233
- try:
234
- import asyncio
235
- import time
236
- import shutil
237
-
238
- start_time = time.time()
239
- timeout_seconds = 30.0
240
-
241
- with tempfile.TemporaryDirectory() as temp_dir:
242
- workspace_dir = os.path.join(temp_dir, 'workspace')
243
- os.makedirs(workspace_dir)
244
-
245
- if files:
246
- for file in files:
247
- content = await file.read()
248
- file_path = os.path.join(workspace_dir, file.filename)
249
- with open(file_path, 'wb') as f:
250
- f.write(content)
251
-
252
- temp_user = f"forge_temp_{container_id}"
253
- user_created = False
254
- try:
255
- subprocess.run(['useradd', '--no-create-home', '--shell', '/bin/bash', temp_user],
256
- check=True, capture_output=True)
257
- user_created = True
258
-
259
- subprocess.run(['chown', '-R', temp_user, workspace_dir],
260
- check=True, capture_output=True)
261
-
262
- script_path = os.path.join(temp_dir, 'script.sh')
263
- script_content = f"""#!/bin/bash
264
- cd {workspace_dir}
265
- {command}
266
- """
267
- with open(script_path, 'w') as f:
268
- f.write(script_content)
269
- os.chmod(script_path, 0o755)
270
-
271
- subprocess.run(['chown', temp_user, script_path],
272
- check=True, capture_output=True)
273
-
274
- cmd = ['sudo', '-u', temp_user, 'bash', script_path]
275
-
276
- process = subprocess.Popen(
277
- cmd,
278
- stdout=subprocess.PIPE,
279
- stderr=subprocess.STDOUT,
280
- text=True,
281
- bufsize=1,
282
- cwd=workspace_dir,
283
- env={'PYTHONUNBUFFERED': '1', 'PATH': '/usr/local/bin:/usr/bin:/bin'}
284
- )
285
- except subprocess.CalledProcessError as e:
286
- yield f"data: Failed to setup execution environment: {e}\n\n"
287
- return
288
-
289
- while True:
290
- if time.time() - start_time > timeout_seconds:
291
- try:
292
- process.kill()
293
- except:
294
- pass
295
- yield f"data: Command timed out after 30 seconds\n\n"
296
- break
297
-
298
- try:
299
- line = await asyncio.wait_for(
300
- asyncio.get_event_loop().run_in_executor(None, process.stdout.readline),
301
- timeout=1.0
302
- )
303
- if line:
304
- yield f"data: {line.rstrip()}\n\n"
305
- else:
306
- break
307
- except asyncio.TimeoutError:
308
- continue
309
-
310
- process.wait()
311
-
312
- if process.returncode != 0:
313
- yield f"data: Command failed with exit code {process.returncode}\n\n"
314
-
315
- except Exception as e:
316
- yield f"data: Error: {str(e)}\n\n"
317
- finally:
318
- try:
319
- if user_created:
320
- subprocess.run(['userdel', '-r', temp_user], timeout=5, capture_output=True)
321
- except:
322
- pass
323
-
324
- return StreamingResponse(
325
- generate(),
326
- media_type="text/event-stream",
327
- headers={"Cache-Control": "no-cache", "Connection": "keep-alive"}
328
- )
329
-
330
- @app.post("/execute")
331
- async def execute_post(request: Request):
332
- form = await request.form()
333
- command = form.get("command")
334
- files = []
335
-
336
- for field_name, field_value in form.items():
337
- if field_name == "command":
338
- command = field_value
339
- elif hasattr(field_value, 'filename'):
340
- files.append(field_value)
341
-
342
- if not command:
343
- raise HTTPException(status_code=400, detail="Command is required")
344
-
345
- return await execute_get(command, files)
346
-
347
- @app.get("/ping")
348
- async def ping(request: Request):
349
- client_ip = request.client.host
350
- current_time = datetime.now()
351
-
352
- ip_timestamps[client_ip] = current_time
353
-
354
- return {"message": "pong", "files_count": len(files)}
355
-
356
- if __name__ == "__main__":
357
- import uvicorn
358
- uvicorn.run(app, host="0.0.0.0", port=7860)
 
1
+ import subprocess,os,tempfile
2
+ from flask import Flask,request,Response
3
+ from flask_cors import CORS
4
+ app=Flask(__name__);CORS(app)
5
+ def run_python_code(code):
6
+ try:
7
+ with tempfile.NamedTemporaryFile(mode='w',suffix='.py',delete=False)as f:f.write(code);tf=f.name
8
+ r=subprocess.run(['python3',tf],capture_output=True,text=True,timeout=30)
9
+ os.unlink(tf)
10
+ return r.stdout+r.stderr
11
+ except:return"Error"
12
+ @app.route('/')
13
+ def index():return"Python API"
14
+ @app.route('/vm/default/python',methods=['POST'])
15
+ def run_python():d=request.get_json();c=d.get('code','');return Response(run_python_code(c),mimetype='text/plain')
16
+ if __name__=='__main__':app.run(host='0.0.0.0',port=7860)