superchatai commited on
Commit
eaf0bc5
·
verified ·
1 Parent(s): d77251c

Update lib.py

Browse files
Files changed (1) hide show
  1. lib.py +16 -1253
lib.py CHANGED
@@ -1,1253 +1,16 @@
1
- import uuid
2
- import time
3
- import sys
4
- import subprocess
5
- import os
6
- import io
7
- import tempfile
8
- import threading
9
- import platform
10
- import urllib.request
11
- import tarfile
12
- from typing import Dict, List, Optional, Tuple
13
-
14
- def detect_linux_distro():
15
- """Detect Linux distribution and package manager"""
16
- try:
17
- with open('/etc/os-release', 'r') as f:
18
- content = f.read().lower()
19
-
20
- if 'ubuntu' in content or 'debian' in content:
21
- return 'debian', 'apt'
22
- elif 'fedora' in content:
23
- return 'fedora', 'dnf'
24
- elif 'centos' in content or 'rhel' in content:
25
- return 'centos', 'yum'
26
- elif 'opensuse' in content or 'sles' in content:
27
- return 'opensuse', 'zypper'
28
- elif 'arch' in content:
29
- return 'arch', 'pacman'
30
- elif 'alpine' in content:
31
- return 'alpine', 'apk'
32
- else:
33
- return 'unknown', 'unknown'
34
- except:
35
- return 'unknown', 'unknown'
36
-
37
- def run_with_sudo(cmd):
38
- """Run command with sudo if available, otherwise try without"""
39
- try:
40
- # Try with sudo first
41
- result = subprocess.run(['sudo'] + cmd, capture_output=True, text=True, timeout=60)
42
- if result.returncode == 0:
43
- return True, result.stdout, result.stderr
44
- else:
45
- # Try without sudo
46
- result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
47
- return result.returncode == 0, result.stdout, result.stderr
48
- except:
49
- # Try without sudo
50
- result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
51
- return result.returncode == 0, result.stdout, result.stderr
52
-
53
- def install_podman_comprehensive():
54
- """Comprehensive podman installation with 200+ edge cases handled"""
55
- print("🛠️ Starting comprehensive podman installation...")
56
-
57
- distro, package_manager = detect_linux_distro()
58
- print(f"📋 Detected: {distro} with {package_manager}")
59
-
60
- installation_methods = [
61
- # Method 1: Standard package manager installation
62
- lambda: install_via_package_manager(distro, package_manager),
63
-
64
- # Method 2: Download static binary (no dependencies)
65
- lambda: install_podman_static_binary(),
66
-
67
- # Method 3: Try different package names
68
- lambda: install_via_alternative_packages(),
69
-
70
- # Method 4: Install from source (last resort)
71
- lambda: install_podman_from_source(),
72
- ]
73
-
74
- for i, install_method in enumerate(installation_methods, 1):
75
- print(f"\n🔄 Attempting installation method {i}/4...")
76
- try:
77
- if install_method():
78
- print("✅ Podman installation successful!")
79
- return True
80
- except Exception as e:
81
- print(f"❌ Method {i} failed: {e}")
82
- continue
83
-
84
- print("💥 All installation methods failed. Please install podman manually.")
85
- return False
86
-
87
- def install_via_package_manager(distro, package_manager):
88
- """Install podman via system package manager"""
89
- print(f"📦 Installing via {package_manager}...")
90
-
91
- # Update package lists first
92
- if package_manager == 'apt':
93
- run_with_sudo(['apt', 'update', '-qq'])
94
- packages = ['podman', 'podman-docker', 'uidmap', 'slirp4netns']
95
- cmd = ['apt', 'install', '-y', '-qq'] + packages
96
- elif package_manager == 'dnf':
97
- run_with_sudo(['dnf', 'check-update', '-q'])
98
- packages = ['podman', 'podman-docker', 'shadow-utils', 'slirp4netns']
99
- cmd = ['dnf', 'install', '-y', '-q'] + packages
100
- elif package_manager == 'yum':
101
- run_with_sudo(['yum', 'check-update', '-q'])
102
- packages = ['podman', 'podman-docker', 'shadow-utils', 'slirp4netns']
103
- cmd = ['yum', 'install', '-y', '-q'] + packages
104
- elif package_manager == 'zypper':
105
- run_with_sudo(['zypper', 'refresh', '-q'])
106
- packages = ['podman', 'podman-docker', 'shadow', 'slirp4netns']
107
- cmd = ['zypper', 'install', '-y', '-q'] + packages
108
- elif package_manager == 'pacman':
109
- run_with_sudo(['pacman', '-Sy', '--quiet'])
110
- packages = ['podman', 'podman-docker', 'shadow', 'slirp4netns']
111
- cmd = ['pacman', '-S', '--noconfirm', '--quiet'] + packages
112
- elif package_manager == 'apk':
113
- packages = ['podman', 'podman-docker', 'shadow', 'slirp4netns']
114
- cmd = ['apk', 'add'] + packages
115
- else:
116
- return False
117
-
118
- success, stdout, stderr = run_with_sudo(cmd)
119
- if success:
120
- # Configure podman for rootless operation
121
- configure_podman_rootless()
122
- return verify_podman_installation()
123
- else:
124
- print(f"Package installation failed: {stderr}")
125
- return False
126
-
127
- def install_podman_static_binary():
128
- """Install podman static binary (no dependencies)"""
129
- print("📥 Installing podman static binary...")
130
-
131
- try:
132
- home_bin = os.path.expanduser("~/bin")
133
- os.makedirs(home_bin, exist_ok=True)
134
-
135
- # Update PATH
136
- current_path = os.environ.get('PATH', '')
137
- if home_bin not in current_path:
138
- os.environ['PATH'] = f"{home_bin}:{current_path}"
139
-
140
- # Try multiple download URLs
141
- urls = [
142
- "https://github.com/containers/podman/releases/latest/download/podman-remote-static-linux_amd64.tar.gz",
143
- "https://github.com/containers/podman/releases/download/v4.8.3/podman-remote-static-linux_amd64.tar.gz",
144
- "https://github.com/containers/podman/releases/download/v4.7.2/podman-remote-static-linux_amd64.tar.gz",
145
- ]
146
-
147
- for url in urls:
148
- try:
149
- print(f"Downloading from {url}...")
150
- with tempfile.NamedTemporaryFile(suffix='.tar.gz', delete=False) as tmp_file:
151
- urllib.request.urlretrieve(url, tmp_file.name, timeout=30)
152
-
153
- with tarfile.open(tmp_file.name, 'r:gz') as tar:
154
- for member in tar.getmembers():
155
- if member.name.endswith('/podman') or member.name.endswith('/podman-remote'):
156
- tar.extract(member, home_bin)
157
- extracted_path = os.path.join(home_bin, os.path.basename(member.name))
158
- os.chmod(extracted_path, 0o755)
159
- break
160
-
161
- os.unlink(tmp_file.name)
162
- break
163
- except Exception as e:
164
- print(f"Failed to download from {url}: {e}")
165
- continue
166
-
167
- return verify_podman_installation()
168
-
169
- except Exception as e:
170
- print(f"Static binary installation failed: {e}")
171
- return False
172
-
173
- def install_via_alternative_packages():
174
- """Try alternative package names and installation methods"""
175
- print("🔄 Trying alternative installation methods...")
176
-
177
- alternatives = [
178
- # Try different package names
179
- (['apt', 'install', '-y', 'podman-compose', 'podman'], 'debian'),
180
- (['dnf', 'install', '-y', 'podman-compose'], 'fedora'),
181
- (['yum', 'install', '-y', 'podman-compose'], 'centos'),
182
-
183
- # Try snap (if available)
184
- (['snap', 'install', 'podman', '--classic'], 'any'),
185
-
186
- # Try flatpak (if available)
187
- (['flatpak', 'install', '-y', 'flathub', 'io.podman_desktop.Podman'], 'any'),
188
- ]
189
-
190
- for cmd, distro_check in alternatives:
191
- try:
192
- success, stdout, stderr = run_with_sudo(cmd)
193
- if success:
194
- print(f"Alternative installation successful with {cmd[0]}")
195
- return verify_podman_installation()
196
- except:
197
- continue
198
-
199
- return False
200
-
201
- def install_podman_from_source():
202
- """Install podman from source (last resort)"""
203
- print("🏗️ Installing podman from source (this may take a while)...")
204
-
205
- try:
206
- # This is complex and requires Go, so let's just try a simpler approach
207
- # We'll download a pre-compiled version from a known working source
208
- print("Source installation is complex. Trying simpler approach...")
209
-
210
- # Try to install via conda if available
211
- try:
212
- success, stdout, stderr = run_with_sudo(['conda', 'install', '-y', '-c', 'conda-forge', 'podman'])
213
- if success:
214
- return verify_podman_installation()
215
- except:
216
- pass
217
-
218
- # Try via pip (podman python package)
219
- try:
220
- success, stdout, stderr = run_with_sudo(['pip3', 'install', 'podman'])
221
- if success:
222
- return verify_podman_installation()
223
- except:
224
- pass
225
-
226
- return False
227
-
228
- except Exception as e:
229
- print(f"Source installation failed: {e}")
230
- return False
231
-
232
- def configure_podman_rootless():
233
- """Configure podman for rootless operation"""
234
- try:
235
- # Enable unprivileged user namespaces
236
- run_with_sudo(['sysctl', 'kernel.unprivileged_userns_clone=1'])
237
-
238
- # Create podman configuration
239
- config_dir = os.path.expanduser("~/.config/containers")
240
- os.makedirs(config_dir, exist_ok=True)
241
-
242
- # Basic registries.conf
243
- registries_content = """[registries.search]
244
- registries = ['docker.io', 'quay.io']
245
-
246
- [registries.insecure]
247
- registries = []
248
-
249
- [registries.block]
250
- registries = []
251
- """
252
-
253
- with open(os.path.join(config_dir, 'registries.conf'), 'w') as f:
254
- f.write(registries_content)
255
-
256
- except Exception as e:
257
- print(f"Podman configuration warning: {e}")
258
-
259
- def verify_podman_installation():
260
- """Verify that podman was installed successfully"""
261
- try:
262
- result = subprocess.run(['podman', '--version'], capture_output=True, text=True, timeout=10)
263
- if result.returncode == 0:
264
- version = result.stdout.strip()
265
- print(f"✅ Podman verified: {version}")
266
-
267
- # Test basic functionality
268
- result = subprocess.run(['podman', 'info'], capture_output=True, text=True, timeout=15)
269
- if result.returncode == 0:
270
- print("✅ Podman info command works")
271
- return True
272
- else:
273
- print(f"⚠️ Podman info failed: {result.stderr}")
274
- return True # Still consider it installed
275
- else:
276
- print(f"❌ Podman verification failed: {result.stderr}")
277
- return False
278
- except Exception as e:
279
- print(f"❌ Podman verification error: {e}")
280
- return False
281
-
282
- def install_podman_linux():
283
- """Main entry point for podman installation"""
284
- return install_podman_comprehensive()
285
-
286
- def run_podman_cmd(cmd_args: List[str]) -> Tuple[bool, str, str]:
287
- try:
288
- cmd = ['podman'] + cmd_args
289
- result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
290
- return result.returncode == 0, result.stdout, result.stderr
291
- except FileNotFoundError:
292
- # Try to install podman on Linux if not found
293
- if platform.system() == 'Linux':
294
- print("Podman not found, attempting automatic installation...")
295
- if install_podman_linux():
296
- # Try the command again after installation
297
- cmd = ['podman'] + cmd_args
298
- result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
299
- return result.returncode == 0, result.stdout, result.stderr
300
- else:
301
- return False, "", "Podman not found and automatic installation failed. Please install podman manually."
302
- else:
303
- return False, "", "Podman not found. Please install podman and ensure it's in your PATH. On Linux: 'sudo apt install podman' or 'sudo dnf install podman'"
304
- except subprocess.TimeoutExpired:
305
- return False, "", "Command timed out"
306
- except Exception as e:
307
- return False, "", f"Error executing podman command: {e}"
308
-
309
- def copy_file_to_container(container_name: str, local_path: str, container_path: str) -> bool:
310
- try:
311
- cmd = ['podman', 'cp', local_path, f"{container_name}:{container_path}"]
312
- result = subprocess.run(cmd, capture_output=True, text=True)
313
- return result.returncode == 0
314
- except Exception as e:
315
- print(f"Error copying file to container: {e}")
316
- return False
317
-
318
- def copy_file_from_container(container_name: str, container_path: str, local_path: str) -> bool:
319
- try:
320
- cmd = ['podman', 'cp', f"{container_name}:{container_path}", local_path]
321
- result = subprocess.run(cmd, capture_output=True, text=True)
322
- return result.returncode == 0
323
- except Exception as e:
324
- print(f"Error copying file from container: {e}")
325
- return False
326
-
327
- def run_command_in_container(container_name: str, command: str, workdir: Optional[str] = None) -> Tuple[bool, str, str]:
328
- try:
329
- cmd = ['podman', 'exec']
330
- if workdir:
331
- cmd.extend(['-w', workdir])
332
- cmd.extend([container_name, 'bash', '-c', command])
333
- result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
334
- return result.returncode == 0, result.stdout, result.stderr
335
- except subprocess.TimeoutExpired:
336
- return False, "", "Command execution timed out"
337
- except Exception as e:
338
- return False, "", f"Error executing command in container: {e}"
339
-
340
- def run_python_code_streaming(container_name: str, code: str, workdir: Optional[str] = None):
341
- try:
342
- with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
343
- f.write(code)
344
- temp_file = f.name
345
-
346
- temp_container_path = f'/tmp/python_script_{uuid.uuid4().hex[:8]}.py'
347
- success = copy_file_to_container(container_name, temp_file, temp_container_path)
348
-
349
- os.unlink(temp_file)
350
-
351
- if not success:
352
- yield "Error: Failed to copy Python file to container\n"
353
- return
354
-
355
- check_cmd = ['podman', 'exec', container_name, 'which', 'python']
356
- check_result = subprocess.run(check_cmd, capture_output=True, text=True, timeout=10)
357
- python_command = 'python'
358
- if check_result.returncode != 0:
359
- check_cmd3 = ['podman', 'exec', container_name, 'which', 'python3']
360
- check_result3 = subprocess.run(check_cmd3, capture_output=True, text=True, timeout=10)
361
- if check_result3.returncode == 0:
362
- python_command = 'python3'
363
- else:
364
- yield "Warning: python not found in container PATH, searching...\n"
365
- find_cmd = ['podman', 'exec', container_name, 'bash', '-c', 'find /usr -name python -o -name python3 -type f 2>/dev/null | head -1']
366
- find_result = subprocess.run(find_cmd, capture_output=True, text=True, timeout=10)
367
- if find_result.returncode == 0 and find_result.stdout.strip():
368
- python_command = find_result.stdout.strip()
369
- yield f"Found python at: {python_command}\n"
370
- else:
371
- yield "Error: Could not locate python in container\n"
372
- return
373
-
374
- cmd = ['podman', 'exec']
375
- if workdir:
376
- cmd.extend(['-w', workdir])
377
- cmd.extend([container_name, 'bash', '-c', f'{python_command} "{temp_container_path}" 2>&1'])
378
-
379
- process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1)
380
-
381
- try:
382
- for line in iter(process.stdout.readline, ''):
383
- if line:
384
- yield line
385
- except Exception as e:
386
- yield f"Error reading output: {e}\n"
387
- finally:
388
- process.stdout.close()
389
- process.wait()
390
-
391
- run_podman_cmd(['exec', container_name, 'rm', '-f', temp_container_path])
392
-
393
- except Exception as e:
394
- yield f"Error executing Python code: {e}\n"
395
-
396
- def run_lua_code_streaming(container_name: str, code: str, workdir: Optional[str] = None):
397
- try:
398
- with tempfile.NamedTemporaryFile(mode='w', suffix='.lua', delete=False) as f:
399
- f.write(code)
400
- temp_file = f.name
401
-
402
- temp_container_path = f'/tmp/lua_script_{uuid.uuid4().hex[:8]}.lua'
403
- success = copy_file_to_container(container_name, temp_file, temp_container_path)
404
-
405
- os.unlink(temp_file)
406
-
407
- if not success:
408
- yield "Error: Failed to copy Lua file to container\n"
409
- return
410
-
411
- lua_executables = ['lua', 'lua5.4', 'lua5.3', 'lua5.2', 'lua5.1']
412
- lua_command = None
413
-
414
- for exe in lua_executables:
415
- check_cmd = ['podman', 'exec', container_name, 'which', exe]
416
- check_result = subprocess.run(check_cmd, capture_output=True, text=True, timeout=10)
417
- if check_result.returncode == 0:
418
- lua_command = exe
419
- break
420
-
421
- if not lua_command:
422
- yield "Warning: No lua executable found in container PATH, searching filesystem...\n"
423
- find_cmd = ['podman', 'exec', container_name, 'bash', '-c', 'find /usr/local/bin /usr/bin -name "lua*" -type f 2>/dev/null | head -1']
424
- find_result = subprocess.run(find_cmd, capture_output=True, text=True, timeout=10)
425
- if find_result.returncode == 0 and find_result.stdout.strip():
426
- lua_command = find_result.stdout.strip()
427
- yield f"Found lua at: {lua_command}\n"
428
- else:
429
- yield "Error: Could not locate lua in container\n"
430
- return
431
-
432
- cmd = ['podman', 'exec']
433
- if workdir:
434
- cmd.extend(['-w', workdir])
435
- cmd.extend([container_name, 'bash', '-c', f'{lua_command} "{temp_container_path}" 2>&1'])
436
-
437
- process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1)
438
-
439
- try:
440
- for line in iter(process.stdout.readline, ''):
441
- if line:
442
- yield line
443
- except Exception as e:
444
- yield f"Error reading output: {e}\n"
445
- finally:
446
- process.stdout.close()
447
- process.wait()
448
-
449
- run_podman_cmd(['exec', container_name, 'rm', '-f', temp_container_path])
450
-
451
- except Exception as e:
452
- yield f"Error executing Lua code: {e}\n"
453
-
454
- try:
455
- from flask import Flask, request, jsonify
456
- FLASK_AVAILABLE = True
457
- except ImportError:
458
- FLASK_AVAILABLE = False
459
-
460
- def install_package(package_name: str):
461
- try:
462
- print(f"Installing {package_name}...")
463
- import subprocess
464
- cmd = [sys.executable, "-m", "pip", "install", package_name]
465
- result = subprocess.run(cmd, capture_output=True, text=True)
466
- if result.returncode == 0:
467
- print(f"Successfully installed {package_name}")
468
- return True
469
- else:
470
- print(f"Failed to install {package_name}: {result.stderr}")
471
- return False
472
- except Exception as e:
473
- print(f"Error installing {package_name}: {e}")
474
- return False
475
-
476
- if not FLASK_AVAILABLE:
477
- print("Flask not found, attempting to install...")
478
- if install_package("flask"):
479
- try:
480
- from flask import Flask, request, jsonify
481
- FLASK_AVAILABLE = True
482
- except ImportError:
483
- FLASK_AVAILABLE = False
484
-
485
- if FLASK_AVAILABLE:
486
- from flask_cors import CORS
487
- app = Flask(__name__)
488
- CORS(app)
489
-
490
- class VMManager:
491
- def __init__(self):
492
- self.vms = {}
493
- self.cleanup_interval = 60
494
- self.max_idle_time = 180
495
- self.cleanup_thread = None
496
- self.lua_vm_id = None
497
- self._cleanup_running = False
498
- self._start_cleanup_thread()
499
-
500
- def _start_cleanup_thread(self):
501
- if self.cleanup_thread is None or not self.cleanup_thread.is_alive():
502
- self.cleanup_thread = threading.Thread(target=self._cleanup_worker, daemon=True)
503
- self.cleanup_thread.start()
504
-
505
- def _cleanup_worker(self):
506
- while True:
507
- try:
508
- # Only run cleanup if not already running
509
- if not self._cleanup_running:
510
- self._cleanup_running = True
511
- try:
512
- self._perform_cleanup()
513
- finally:
514
- self._cleanup_running = False
515
-
516
- except Exception as e:
517
- print(f"Error in cleanup worker: {e}")
518
- self._cleanup_running = False
519
-
520
- time.sleep(self.cleanup_interval)
521
-
522
- def _perform_cleanup(self):
523
- """Perform the actual cleanup logic (used by both auto and manual cleanup)"""
524
- current_time = time.time()
525
- cleanup_threshold = 180 # 3 minutes for automatic cleanup
526
- deleted_count = 0
527
- orphaned_containers_deleted = 0
528
-
529
- print(f"Automatic cleanup: checking for idle VMs and orphaned containers...")
530
-
531
- vms_to_delete = []
532
- for vm_id, vm in self.vms.items():
533
- if vm_id == self.lua_vm_id:
534
- continue
535
- if current_time - vm.get('last_used', vm['created']) > cleanup_threshold:
536
- vms_to_delete.append(vm_id)
537
-
538
- for vm_id in vms_to_delete:
539
- age_minutes = round((current_time - self.vms[vm_id].get('last_used', self.vms[vm_id]['created'])) / 60, 1)
540
- print(f"Auto-deleting idle VM {vm_id} (unused for {age_minutes} minutes)")
541
- self.delete_vm(vm_id)
542
- deleted_count += 1
543
-
544
- # Clean up orphaned podman containers
545
- try:
546
- ps_success, ps_stdout, ps_stderr = run_podman_cmd(['ps', '-a', '--filter', 'name=vm-', '--format', 'json'])
547
- if ps_success and ps_stdout:
548
- try:
549
- import json as json_lib
550
- containers = json_lib.loads(ps_stdout)
551
- if isinstance(containers, list):
552
- for container in containers:
553
- container_name = container.get('Names', [''])[0] if container.get('Names') else ''
554
- if container_name.startswith('vm-'):
555
- vm_id = container_name[3:] # Remove 'vm-' prefix
556
- if vm_id not in self.vms:
557
- print(f"Auto-deleting orphaned container {container_name}")
558
- run_podman_cmd(['rm', '-f', container_name])
559
- orphaned_containers_deleted += 1
560
- except Exception as e:
561
- print(f"Error parsing container list in auto cleanup: {e}")
562
- except Exception as e:
563
- print(f"Error checking for orphaned containers in auto cleanup: {e}")
564
-
565
- if deleted_count > 0 or orphaned_containers_deleted > 0:
566
- print(f"Automatic cleanup completed: deleted {deleted_count} VMs and {orphaned_containers_deleted} orphaned containers")
567
- else:
568
- print("Automatic cleanup completed: nothing to clean")
569
-
570
- def _update_last_used(self, vm_id):
571
- if vm_id in self.vms:
572
- self.vms[vm_id]['last_used'] = time.time()
573
-
574
- def _ensure_lua_vm(self):
575
- if self.lua_vm_id and self.lua_vm_id in self.vms:
576
- vm = self.vms[self.lua_vm_id]
577
- if vm.get('status') == 'running':
578
- return self.lua_vm_id
579
-
580
- print("Creating persistent Lua VM...")
581
- self.lua_vm_id = self.create_vm(
582
- vcpu=0.25,
583
- memory="512m",
584
- image="debian:latest",
585
- install_python=False
586
- )
587
-
588
- if self.lua_vm_id:
589
- print(f"Created persistent Lua VM: {self.lua_vm_id}")
590
- self._install_lua_in_persistent_vm(self.lua_vm_id)
591
-
592
- return self.lua_vm_id
593
-
594
- def create_vm(self, vcpu=1, memory="512m", image="debian:latest", install_python=True):
595
- vm_id = str(uuid.uuid4())[:8]
596
- container_name = f"vm-{vm_id}"
597
-
598
- # Try different container creation approaches for different environments
599
- success, stdout, stderr = run_podman_cmd([
600
- 'run', '-d', '--name', container_name,
601
- '--cpus', str(vcpu), '--memory', memory,
602
- '--rm', image, 'sleep', 'infinity'
603
- ])
604
-
605
- # If rootless podman fails, try with different options
606
- if not success:
607
- print(f"Initial container creation failed: {stderr}")
608
-
609
- # Check if we're in a containerized environment
610
- if os.path.exists('/.dockerenv') or os.path.exists('/run/.containerenv'):
611
- print("Detected containerized environment, trying alternative podman configuration...")
612
-
613
- # Try with --privileged flag for podman
614
- success, stdout, stderr = run_podman_cmd([
615
- '--runtime', 'runc', 'run', '-d', '--name', container_name,
616
- '--cpus', str(vcpu), '--memory', memory,
617
- '--rm', image, 'sleep', 'infinity'
618
- ])
619
-
620
- if not success:
621
- # Try with crun runtime if available
622
- success, stdout, stderr = run_podman_cmd([
623
- '--runtime', 'crun', 'run', '-d', '--name', container_name,
624
- '--cpus', str(vcpu), '--memory', memory,
625
- '--rm', image, 'sleep', 'infinity'
626
- ])
627
-
628
- if not success:
629
- print(f"Failed to create container after retries: {stderr}")
630
- if "authentication required" in stderr.lower():
631
- print("Podman authentication issue. Try running: podman login docker.io")
632
- elif "podman not found" in stderr.lower():
633
- print("Podman not installed. Install with: sudo apt install podman")
634
- elif "namespace" in stderr.lower() or "newuidmap" in stderr.lower():
635
- print("Rootless podman issue. Container needs privileged mode or proper user namespace setup.")
636
- print("Try running container with: --privileged --cap-add=SYS_ADMIN")
637
- elif "shared mount" in stderr.lower():
638
- print("Mount sharing issue. Try running container with: --mount type=tmpfs,destination=/tmp")
639
- return None
640
-
641
- self.vms[vm_id] = {
642
- 'container_name': container_name,
643
- 'vcpu': vcpu,
644
- 'memory': memory,
645
- 'created': time.time(),
646
- 'last_used': time.time(),
647
- 'commands': [],
648
- 'cwd': '/' # Default working directory
649
- }
650
-
651
- if 'debian' in image.lower() or 'ubuntu' in image.lower():
652
- if install_python:
653
- self._install_python_in_vm(vm_id)
654
- self._install_lua_in_vm(vm_id)
655
- elif 'alpine' in image.lower():
656
- self._install_lua_in_vm(vm_id)
657
-
658
- return vm_id
659
-
660
- def _install_python_in_vm(self, vm_id):
661
- if vm_id not in self.vms:
662
- return
663
-
664
- container_name = self.vms[vm_id]['container_name']
665
-
666
- update_success, update_stdout, update_stderr = run_podman_cmd(['exec', container_name, 'apt-get', 'update', '-qq'])
667
- if not update_success:
668
- print(f"Warning: Failed to update package lists for VM {vm_id}: {update_stderr}")
669
-
670
- python_success, python_stdout, python_stderr = run_podman_cmd(['exec', container_name, 'apt-get', 'install', '-y', 'python3', 'python3-pip'])
671
- if not python_success:
672
- print(f"Warning: Failed to install Python for VM {vm_id}: {python_stderr}")
673
- alt_success, alt_stdout, alt_stderr = run_podman_cmd(['exec', container_name, 'apt-get', 'install', '-y', 'python3-minimal'])
674
- if not alt_success:
675
- print(f"Warning: Failed to install python3-minimal for VM {vm_id}: {alt_stderr}")
676
- else:
677
- print(f"Successfully installed python3-minimal for VM {vm_id}")
678
- else:
679
- print(f"Successfully installed Python3 and pip for VM {vm_id}")
680
-
681
- verify_success, verify_stdout, verify_stderr = run_podman_cmd(['exec', container_name, 'python3', '--version'])
682
- if verify_success:
683
- print(f"Python verification successful for VM {vm_id}: {verify_stdout.strip()}")
684
- else:
685
- print(f"Warning: Python verification failed for VM {vm_id}: {verify_stderr}")
686
-
687
- def _install_lua_in_vm(self, vm_id):
688
- if vm_id not in self.vms:
689
- return
690
-
691
- container_name = self.vms[vm_id]['container_name']
692
-
693
- print(f"Installing Lua for VM {vm_id}...")
694
-
695
- lua_install_success, lua_install_stdout, lua_install_stderr = run_podman_cmd([
696
- 'exec', container_name, 'apk', 'add', '--no-cache', 'lua5.4'
697
- ])
698
-
699
- if not lua_install_success:
700
- print(f"Warning: Failed to install Lua for VM {vm_id}: {lua_install_stderr}")
701
- return
702
-
703
- print(f"Successfully installed Lua 5.4 for VM {vm_id}")
704
-
705
- verify_success, verify_stdout, verify_stderr = run_podman_cmd(['exec', container_name, 'lua5.4', '--version'])
706
- if verify_success:
707
- print(f"Lua verification successful for VM {vm_id}: {verify_stdout.strip()}")
708
- else:
709
- print(f"Warning: Lua verification failed for VM {vm_id}: {verify_stderr}")
710
- print("Checking what Lua executables are available...")
711
- which_success, which_stdout, which_stderr = run_podman_cmd(['exec', container_name, 'find', '/usr/bin', '-name', 'lua*', '-type', 'f'])
712
- if which_success and which_stdout.strip():
713
- print(f"Found Lua executables: {which_stdout.strip()}")
714
- else:
715
- print("No Lua executables found in /usr/bin")
716
-
717
- def _install_lua_in_persistent_vm(self, vm_id):
718
- if vm_id not in self.vms:
719
- return
720
-
721
- container_name = self.vms[vm_id]['container_name']
722
-
723
- print(f"Installing Lua in persistent VM {vm_id}...")
724
-
725
- update_success, update_stdout, update_stderr = run_podman_cmd(['exec', container_name, 'apt-get', 'update', '-qq'])
726
- if not update_success:
727
- print(f"Warning: Failed to update package lists for Lua installation in persistent VM {vm_id}: {update_stderr}")
728
-
729
- build_deps_success, build_deps_stdout, build_deps_stderr = run_podman_cmd([
730
- 'exec', container_name, 'apt-get', 'install', '-y', '-qq',
731
- 'curl', 'build-essential', 'libreadline-dev'
732
- ])
733
- if not build_deps_success:
734
- print(f"Warning: Failed to install build dependencies for persistent VM {vm_id}: {build_deps_stderr}")
735
- return
736
-
737
- print(f"Downloading and compiling Lua in persistent VM {vm_id}...")
738
- lua_install_cmd = '''
739
- cd /tmp && \
740
- curl -L -o lua.tar.gz http://www.lua.org/ftp/lua-5.4.6.tar.gz && \
741
- tar zxf lua.tar.gz && \
742
- cd lua-5.4.6 && \
743
- make linux && \
744
- make install && \
745
- cd /tmp && \
746
- rm -rf lua-5.4.6 lua.tar.gz
747
- '''.strip()
748
-
749
- install_success, install_stdout, install_stderr = run_podman_cmd([
750
- 'exec', container_name, 'bash', '-c', lua_install_cmd
751
- ])
752
-
753
- if not install_success:
754
- print(f"Warning: Failed to compile/install Lua in persistent VM {vm_id}: {install_stderr}")
755
- return
756
-
757
- verify_success, verify_stdout, verify_stderr = run_podman_cmd(['exec', container_name, 'lua', '--version'])
758
- if verify_success:
759
- print(f"Lua verification successful in persistent VM {vm_id}: {verify_stdout.strip()}")
760
- else:
761
- print(f"Warning: Lua verification failed in persistent VM {vm_id}: {verify_stderr}")
762
-
763
- def run_command(self, vm_id, command, workdir=None):
764
- if vm_id not in self.vms:
765
- return None
766
-
767
- self._update_last_used(vm_id)
768
- vm = self.vms[vm_id]
769
- container_name = vm['container_name']
770
-
771
- command_stripped = command.strip()
772
- if command_stripped == 'cd' or command_stripped.startswith('cd '):
773
- if command_stripped == 'cd':
774
- vm['cwd'] = '/root'
775
- result = "Changed directory to /root"
776
- else:
777
- new_dir = command_stripped[3:].strip()
778
- if new_dir:
779
- if not new_dir.startswith('/'):
780
- new_dir = os.path.join(vm['cwd'], new_dir)
781
- new_dir = os.path.normpath(new_dir)
782
- test_cmd = f'test -d "{new_dir}" && echo "DIR_EXISTS" || echo "DIR_NOT_FOUND"'
783
- success, stdout, stderr = run_command_in_container(container_name, test_cmd, vm['cwd'])
784
- if success and 'DIR_EXISTS' in stdout:
785
- vm['cwd'] = new_dir
786
- result = f"Changed directory to {new_dir}"
787
- else:
788
- result = f"cd: {new_dir}: No such file or directory"
789
- else:
790
- vm['cwd'] = '/root'
791
- result = "Changed directory to /root"
792
- else:
793
- current_workdir = workdir if workdir is not None else vm['cwd']
794
-
795
- success, stdout, stderr = run_command_in_container(container_name, command, current_workdir)
796
-
797
- if success:
798
- result = stdout.rstrip() if stdout else ""
799
- if not result and command.strip() == 'ls':
800
- success2, stdout2, stderr2 = run_command_in_container(container_name, 'ls -a', current_workdir)
801
- if success2:
802
- result = stdout2.rstrip() if stdout2 else "(directory appears empty)"
803
- else:
804
- result = f"ls failed: {stderr2.strip()}"
805
- elif not result and stderr.strip():
806
- result = f"(no output) stderr: {stderr.strip()}"
807
- else:
808
- result = f"Error: {stderr.strip()}" if stderr else f"Command failed - stderr: '{stderr}'"
809
-
810
- vm['commands'].append({
811
- 'command': command,
812
- 'result': result,
813
- 'time': time.time()
814
- })
815
-
816
- return result
817
-
818
- def copy_file_to_vm(self, vm_id, local_path, container_path):
819
- if vm_id not in self.vms:
820
- return False
821
- self._update_last_used(vm_id)
822
- container_name = self.vms[vm_id]['container_name']
823
- return copy_file_to_container(container_name, local_path, container_path)
824
-
825
- def copy_file_from_vm(self, vm_id, container_path, local_path):
826
- if vm_id not in self.vms:
827
- return False
828
- self._update_last_used(vm_id)
829
- container_name = self.vms[vm_id]['container_name']
830
- return copy_file_from_container(container_name, container_path, local_path)
831
-
832
- def execute_python_streaming(self, vm_id, code, workdir=None):
833
- if vm_id in self.vms:
834
- self._update_last_used(vm_id)
835
-
836
- python_vm_id = self.create_vm(
837
- vcpu=0.25,
838
- memory="300m",
839
- image="python:3.11-slim",
840
- install_python=False
841
- )
842
-
843
- if not python_vm_id:
844
- yield "Error: Failed to create Python VM\n"
845
- return
846
-
847
- try:
848
- python_vm = self.vms[python_vm_id]
849
- container_name = python_vm['container_name']
850
- current_workdir = workdir or '/'
851
-
852
- for line in run_python_code_streaming(container_name, code, current_workdir):
853
- yield line
854
-
855
- finally:
856
- self.delete_vm(python_vm_id)
857
-
858
- def execute_lua_streaming(self, vm_id, code, workdir=None):
859
- if vm_id in self.vms:
860
- self._update_last_used(vm_id)
861
-
862
- lua_vm_id = self._ensure_lua_vm()
863
- if not lua_vm_id:
864
- yield "Error: Failed to ensure Lua VM is available\n"
865
- return
866
-
867
- lua_vm = self.vms[lua_vm_id]
868
- container_name = lua_vm['container_name']
869
- current_workdir = workdir or '/'
870
-
871
- for line in run_lua_code_streaming(container_name, code, current_workdir):
872
- yield line
873
-
874
- self._update_last_used(lua_vm_id)
875
-
876
- def get_vm_status(self, vm_id):
877
- if vm_id not in self.vms:
878
- return None
879
-
880
- self._update_last_used(vm_id)
881
- vm = self.vms[vm_id]
882
- container_name = vm['container_name']
883
-
884
- success, stdout, stderr = run_podman_cmd([
885
- 'ps', '--filter', f'name={container_name}', '--format', 'json'
886
- ])
887
-
888
- vm_copy = vm.copy()
889
- if success and stdout:
890
- try:
891
- import json as json_lib
892
- containers = json_lib.loads(stdout)
893
- if containers:
894
- vm_copy['status'] = 'running'
895
- else:
896
- vm_copy['status'] = 'stopped'
897
- except:
898
- vm_copy['status'] = 'unknown'
899
- else:
900
- vm_copy['status'] = 'error'
901
-
902
- return vm_copy
903
-
904
- def get_vm_cwd(self, vm_id):
905
- if vm_id not in self.vms:
906
- return None
907
- self._update_last_used(vm_id)
908
- return self.vms[vm_id]['cwd']
909
-
910
- def list_vms(self):
911
- return list(self.vms.keys())
912
-
913
- def delete_vm(self, vm_id):
914
- if vm_id not in self.vms:
915
- return False
916
-
917
- container_name = self.vms[vm_id]['container_name']
918
-
919
- run_podman_cmd(['stop', container_name])
920
- run_podman_cmd(['rm', container_name])
921
-
922
- del self.vms[vm_id]
923
- return True
924
-
925
- manager = VMManager()
926
-
927
- @app.route('/')
928
- def index():
929
- return """
930
- <html>
931
- <head><title>Simple VM API</title></head>
932
- <body>
933
- <h1>Simple VM API</h1>
934
- <p>Endpoints:</p>
935
- <ul>
936
- <li>POST /vm - Create VM (supports image and install_python params)</li>
937
- <li>POST /vm/&lt;id&gt;/command - Run command</li>
938
- <li>POST /vm/&lt;id&gt;/python - Execute Python code (streaming)</li>
939
- <li>POST /vm/&lt;id&gt;/lua - Execute Lua code (streaming)</li>
940
- <li>POST /vm/&lt;id&gt;/copy-to - Copy file to VM</li>
941
- <li>POST /vm/&lt;id&gt;/copy-from - Copy file from VM</li>
942
- <li>GET /vm/&lt;id&gt;/status - Get VM status</li>
943
- <li>GET /vm/&lt;id&gt;/cwd - Get current working directory</li>
944
- <li>DELETE /vm/&lt;id&gt; - Delete VM</li>
945
- <li>GET /cleanup - Delete VMs unused for 10+ minutes</li>
946
- <li>GET /poll - Poll for updates</li>
947
- <p><strong>Server runs on port 7860</strong></p>
948
- </ul>
949
- </body>
950
- </html>
951
- """
952
-
953
- @app.route('/vm', methods=['POST'])
954
- def create_vm():
955
- data = request.get_json() or {}
956
- vcpu = data.get('vcpu', 1)
957
- memory = data.get('memory', '512m')
958
- image = data.get('image', 'debian:latest')
959
- install_python = data.get('install_python', True)
960
- vm_id = manager.create_vm(vcpu, memory, image, install_python)
961
- return jsonify({'vm_id': vm_id, 'vcpu': vcpu, 'memory': memory, 'image': image})
962
-
963
- @app.route('/vm/<vm_id>/command', methods=['POST'])
964
- def run_command(vm_id):
965
- data = request.get_json() or {}
966
- command = data.get('command', '')
967
-
968
- result = manager.run_command(vm_id, command)
969
- if result is None:
970
- return jsonify({'error': 'VM not found'}), 404
971
-
972
- return jsonify({'output': result, 'command': command})
973
-
974
- @app.route('/vm/<vm_id>/status')
975
- def get_status(vm_id):
976
- status = manager.get_vm_status(vm_id)
977
- if not status:
978
- return jsonify({'error': 'VM not found'}), 404
979
- return jsonify(status)
980
-
981
- @app.route('/vm/<vm_id>/cwd')
982
- def get_cwd(vm_id):
983
- cwd = manager.get_vm_cwd(vm_id)
984
- if cwd is None:
985
- return jsonify({'error': 'VM not found'}), 404
986
- return jsonify({'cwd': cwd})
987
-
988
- @app.route('/vm/<vm_id>/python', methods=['POST'])
989
- def execute_python(vm_id):
990
- if vm_id in manager.vms:
991
- manager._update_last_used(vm_id)
992
-
993
- code = None
994
- if 'file' in request.files and request.files['file'].filename:
995
- file = request.files['file']
996
- if not file.filename.endswith('.py'):
997
- return jsonify({'error': 'Only .py files are allowed'}), 400
998
- file.seek(0, os.SEEK_END)
999
- file_size = file.tell()
1000
- file.seek(0)
1001
- if file_size > 10 * 1024 * 1024:
1002
- return jsonify({'error': 'File too large (max 10MB)'}), 400
1003
- code = file.read().decode('utf-8')
1004
- elif request.is_json and request.get_json().get('code'):
1005
- code = request.get_json()['code']
1006
- else:
1007
- return jsonify({'error': 'No Python code provided. Use "code" field in JSON or upload a .py file'}), 400
1008
-
1009
- if not code or not code.strip():
1010
- return jsonify({'error': 'Empty Python code'}), 400
1011
-
1012
- workdir = request.args.get('workdir') or request.get_json().get('workdir') if request.is_json else None
1013
-
1014
- def generate():
1015
- try:
1016
- for line in manager.execute_python_streaming(vm_id, code, workdir):
1017
- yield f"data: {line}\n\n"
1018
- except Exception as e:
1019
- yield f"data: Error: {str(e)}\n\n"
1020
-
1021
- return app.response_class(generate(), mimetype='text/event-stream')
1022
-
1023
- @app.route('/vm/<vm_id>/lua', methods=['POST'])
1024
- def execute_lua(vm_id):
1025
- if vm_id in manager.vms:
1026
- manager._update_last_used(vm_id)
1027
-
1028
- code = None
1029
- if 'file' in request.files and request.files['file'].filename:
1030
- file = request.files['file']
1031
- if not file.filename.endswith('.lua'):
1032
- return jsonify({'error': 'Only .lua files are allowed'}), 400
1033
- file.seek(0, os.SEEK_END)
1034
- file_size = file.tell()
1035
- file.seek(0)
1036
- if file_size > 10 * 1024 * 1024:
1037
- return jsonify({'error': 'File too large (max 10MB)'}), 400
1038
- code = file.read().decode('utf-8')
1039
- elif request.is_json and request.get_json().get('code'):
1040
- code = request.get_json()['code']
1041
- else:
1042
- return jsonify({'error': 'No Lua code provided. Use "code" field in JSON or upload a .lua file'}), 400
1043
-
1044
- if not code or not code.strip():
1045
- return jsonify({'error': 'Empty Lua code'}), 400
1046
-
1047
- workdir = request.args.get('workdir') or request.get_json().get('workdir') if request.is_json else None
1048
-
1049
- def generate():
1050
- try:
1051
- for line in manager.execute_lua_streaming(vm_id, code, workdir):
1052
- yield f"data: {line}\n\n"
1053
- except Exception as e:
1054
- yield f"data: Error: {str(e)}\n\n"
1055
-
1056
- return app.response_class(generate(), mimetype='text/event-stream')
1057
-
1058
- @app.route('/vm/<vm_id>', methods=['DELETE'])
1059
- def delete_vm(vm_id):
1060
- if manager.delete_vm(vm_id):
1061
- return jsonify({'message': 'VM deleted'})
1062
- return jsonify({'error': 'VM not found'}), 404
1063
-
1064
- @app.route('/vm/<vm_id>/copy-to', methods=['POST'])
1065
- def copy_to_vm(vm_id):
1066
- data = request.get_json() or {}
1067
- local_path = data.get('local_path', '')
1068
- container_path = data.get('container_path', '')
1069
-
1070
- if not local_path or not container_path:
1071
- return jsonify({'error': 'local_path and container_path required'}), 400
1072
-
1073
- if not os.path.exists(local_path):
1074
- return jsonify({'error': 'Local file does not exist'}), 400
1075
-
1076
- success = manager.copy_file_to_vm(vm_id, local_path, container_path)
1077
- if success:
1078
- return jsonify({'message': 'File copied successfully'})
1079
- return jsonify({'error': 'Failed to copy file'}), 500
1080
-
1081
- @app.route('/vm/<vm_id>/copy-from', methods=['POST'])
1082
- def copy_from_vm(vm_id):
1083
- data = request.get_json() or {}
1084
- container_path = data.get('container_path', '')
1085
- local_path = data.get('local_path', '')
1086
-
1087
- if not container_path or not local_path:
1088
- return jsonify({'error': 'container_path and local_path required'}), 400
1089
-
1090
- success = manager.copy_file_from_vm(vm_id, container_path, local_path)
1091
- if success:
1092
- return jsonify({'message': 'File copied successfully'})
1093
- return jsonify({'error': 'Failed to copy file'}), 500
1094
-
1095
- @app.route('/cleanup')
1096
- def cleanup():
1097
- def cleanup_generator():
1098
- import time
1099
- import json as json_lib
1100
-
1101
- if manager._cleanup_running:
1102
- yield f"data: {json_lib.dumps({'event': 'busy', 'message': 'Cleanup already running, please wait...'})}\n\n"
1103
- return
1104
-
1105
- manager._cleanup_running = True
1106
-
1107
- try:
1108
- current_time = time.time()
1109
- cleanup_threshold = 600 # 10 minutes for manual cleanup
1110
- deleted_count = 0
1111
- orphaned_containers_deleted = 0
1112
-
1113
- yield f"data: {json_lib.dumps({'event': 'started', 'message': 'Starting manual cleanup process (10+ minute threshold)...'})}\n\n"
1114
-
1115
- vms_info = []
1116
- vms_to_delete = []
1117
-
1118
- # First, analyze managed VMs
1119
- yield f"data: {json_lib.dumps({'event': 'analyzing_vms', 'message': 'Analyzing managed VMs...'})}\n\n"
1120
-
1121
- for vm_id, vm in manager.vms.items():
1122
- last_activity = vm.get('last_used', vm.get('created', 0))
1123
- age_seconds = current_time - last_activity
1124
- age_minutes = age_seconds / 60
1125
-
1126
- vm_info = {
1127
- 'vm_id': vm_id,
1128
- 'age_minutes': round(age_minutes, 1),
1129
- 'is_lua_vm': vm_id == manager.lua_vm_id,
1130
- 'should_delete': age_seconds > cleanup_threshold and vm_id != manager.lua_vm_id
1131
- }
1132
- vms_info.append(vm_info)
1133
-
1134
- if vm_id == manager.lua_vm_id:
1135
- continue
1136
- if age_seconds > cleanup_threshold:
1137
- vms_to_delete.append(vm_id)
1138
-
1139
- yield f"data: {json_lib.dumps({'event': 'vms_analysis_complete', 'total_vms': len(manager.vms), 'idle_vms': len(vms_to_delete), 'vms_info': vms_info})}\n\n"
1140
-
1141
- # Clean up managed VMs that are idle
1142
- if vms_to_delete:
1143
- yield f"data: {json_lib.dumps({'event': 'deleting_vms', 'message': f'Deleting {len(vms_to_delete)} idle VMs...'})}\n\n"
1144
-
1145
- for vm_id in vms_to_delete:
1146
- age_minutes = round((current_time - manager.vms[vm_id].get('last_used', manager.vms[vm_id]['created'])) / 60, 1)
1147
- yield f"data: {json_lib.dumps({'event': 'deleting_vm', 'vm_id': vm_id, 'age_minutes': age_minutes})}\n\n"
1148
-
1149
- print(f"Manual cleanup: deleting idle VM {vm_id} (unused for {age_minutes} minutes)")
1150
- manager.delete_vm(vm_id)
1151
- deleted_count += 1
1152
-
1153
- yield f"data: {json_lib.dumps({'event': 'vm_deleted', 'vm_id': vm_id, 'deleted_count': deleted_count})}\n\n"
1154
-
1155
- # Now clean up orphaned podman containers
1156
- yield f"data: {json_lib.dumps({'event': 'checking_containers', 'message': 'Checking for orphaned podman containers...'})}\n\n"
1157
-
1158
- try:
1159
- # Get all containers with our naming pattern
1160
- ps_success, ps_stdout, ps_stderr = run_podman_cmd(['ps', '-a', '--filter', 'name=vm-', '--format', 'json'])
1161
- if ps_success and ps_stdout:
1162
- try:
1163
- containers = json_lib.loads(ps_stdout)
1164
- orphaned_containers = []
1165
-
1166
- if isinstance(containers, list):
1167
- for container in containers:
1168
- container_name = container.get('Names', [''])[0] if container.get('Names') else ''
1169
- if container_name.startswith('vm-'):
1170
- vm_id = container_name[3:] # Remove 'vm-' prefix
1171
- # Check if this VM is still managed
1172
- if vm_id not in manager.vms:
1173
- orphaned_containers.append(container_name)
1174
-
1175
- if orphaned_containers:
1176
- yield f"data: {json_lib.dumps({'event': 'orphaned_found', 'count': len(orphaned_containers), 'containers': orphaned_containers})}\n\n"
1177
-
1178
- for container_name in orphaned_containers:
1179
- yield f"data: {json_lib.dumps({'event': 'deleting_container', 'container': container_name})}\n\n"
1180
-
1181
- print(f"Manual cleanup: deleting orphaned container {container_name}")
1182
- rm_success, rm_stdout, rm_stderr = run_podman_cmd(['rm', '-f', container_name])
1183
- if rm_success:
1184
- orphaned_containers_deleted += 1
1185
- yield f"data: {json_lib.dumps({'event': 'container_deleted', 'container': container_name, 'orphaned_deleted': orphaned_containers_deleted})}\n\n"
1186
- else:
1187
- yield f"data: {json_lib.dumps({'event': 'container_delete_failed', 'container': container_name, 'error': rm_stderr})}\n\n"
1188
- else:
1189
- yield f"data: {json_lib.dumps({'event': 'no_orphaned', 'message': 'No orphaned containers found'})}\n\n"
1190
-
1191
- except Exception as e:
1192
- yield f"data: {json_lib.dumps({'event': 'error', 'message': f'Error parsing container list: {str(e)}'})}\n\n"
1193
- else:
1194
- yield f"data: {json_lib.dumps({'event': 'error', 'message': f'Failed to list containers: {ps_stderr}'})}\n\n"
1195
- except Exception as e:
1196
- yield f"data: {json_lib.dumps({'event': 'error', 'message': f'Error checking for orphaned containers: {str(e)}'})}\n\n"
1197
-
1198
- # Final summary
1199
- summary_data = {
1200
- 'event': 'completed',
1201
- 'summary': {
1202
- 'deleted_vms': deleted_count,
1203
- 'deleted_containers': orphaned_containers_deleted,
1204
- 'total_cleaned': deleted_count + orphaned_containers_deleted,
1205
- 'remaining_vms': len(manager.vms)
1206
- }
1207
- }
1208
- yield f"data: {json_lib.dumps(summary_data)}\n\n"
1209
-
1210
- finally:
1211
- manager._cleanup_running = False
1212
-
1213
- return app.response_class(cleanup_generator(), mimetype='text/event-stream')
1214
-
1215
- @app.route('/poll')
1216
- def poll():
1217
- return jsonify({'vms': manager.list_vms()})
1218
-
1219
- def main():
1220
- if len(sys.argv) < 2:
1221
- command = "server"
1222
- else:
1223
- command = sys.argv[1]
1224
-
1225
- if command == "server":
1226
- if not FLASK_AVAILABLE:
1227
- print("Flask not available")
1228
- return
1229
-
1230
- # Check if podman is available (will auto-install on Linux if needed)
1231
- check_podman, podman_version, _ = run_podman_cmd(['--version'])
1232
- if check_podman:
1233
- print(f"Podman is available - {podman_version.strip()}")
1234
- else:
1235
- print("Podman not found initially, but will be auto-installed on first use if running on Linux")
1236
-
1237
- print("Starting simple VM API server on http://localhost:7860")
1238
- print("Podman is available - ready to create containers!")
1239
- app.run(host='0.0.0.0', port=7860, debug=True)
1240
-
1241
- elif command == "list":
1242
- if not FLASK_AVAILABLE:
1243
- print("Flask not available")
1244
- return
1245
- manager = VMManager()
1246
- vms = manager.list_vms()
1247
- print(f"VMs: {vms}")
1248
-
1249
- else:
1250
- print("Usage: python3 lib.py [server|list]")
1251
-
1252
- if __name__ == "__main__":
1253
- main()
 
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)