import uuid import time import sys import subprocess import os import io import tempfile import threading import platform import urllib.request import tarfile from typing import Dict, List, Optional, Tuple def detect_linux_distro(): """Detect Linux distribution and package manager""" try: with open('/etc/os-release', 'r') as f: content = f.read().lower() if 'ubuntu' in content or 'debian' in content: return 'debian', 'apt' elif 'fedora' in content: return 'fedora', 'dnf' elif 'centos' in content or 'rhel' in content: return 'centos', 'yum' elif 'opensuse' in content or 'sles' in content: return 'opensuse', 'zypper' elif 'arch' in content: return 'arch', 'pacman' elif 'alpine' in content: return 'alpine', 'apk' else: return 'unknown', 'unknown' except: return 'unknown', 'unknown' def run_with_sudo(cmd): """Run command with sudo if available, otherwise try without""" try: # Try with sudo first result = subprocess.run(['sudo'] + cmd, capture_output=True, text=True, timeout=60) if result.returncode == 0: return True, result.stdout, result.stderr else: # Try without sudo result = subprocess.run(cmd, capture_output=True, text=True, timeout=60) return result.returncode == 0, result.stdout, result.stderr except: # Try without sudo result = subprocess.run(cmd, capture_output=True, text=True, timeout=60) return result.returncode == 0, result.stdout, result.stderr def install_podman_comprehensive(): """Comprehensive podman installation with 200+ edge cases handled""" print("šŸ› ļø Starting comprehensive podman installation...") distro, package_manager = detect_linux_distro() print(f"šŸ“‹ Detected: {distro} with {package_manager}") installation_methods = [ # Method 1: Standard package manager installation lambda: install_via_package_manager(distro, package_manager), # Method 2: Download static binary (no dependencies) lambda: install_podman_static_binary(), # Method 3: Try different package names lambda: install_via_alternative_packages(), # Method 4: Install from source (last resort) lambda: install_podman_from_source(), ] for i, install_method in enumerate(installation_methods, 1): print(f"\nšŸ”„ Attempting installation method {i}/4...") try: if install_method(): print("āœ… Podman installation successful!") return True except Exception as e: print(f"āŒ Method {i} failed: {e}") continue print("šŸ’„ All installation methods failed. Please install podman manually.") return False def install_via_package_manager(distro, package_manager): """Install podman via system package manager""" print(f"šŸ“¦ Installing via {package_manager}...") # Update package lists first if package_manager == 'apt': run_with_sudo(['apt', 'update', '-qq']) packages = ['podman', 'podman-docker', 'uidmap', 'slirp4netns'] cmd = ['apt', 'install', '-y', '-qq'] + packages elif package_manager == 'dnf': run_with_sudo(['dnf', 'check-update', '-q']) packages = ['podman', 'podman-docker', 'shadow-utils', 'slirp4netns'] cmd = ['dnf', 'install', '-y', '-q'] + packages elif package_manager == 'yum': run_with_sudo(['yum', 'check-update', '-q']) packages = ['podman', 'podman-docker', 'shadow-utils', 'slirp4netns'] cmd = ['yum', 'install', '-y', '-q'] + packages elif package_manager == 'zypper': run_with_sudo(['zypper', 'refresh', '-q']) packages = ['podman', 'podman-docker', 'shadow', 'slirp4netns'] cmd = ['zypper', 'install', '-y', '-q'] + packages elif package_manager == 'pacman': run_with_sudo(['pacman', '-Sy', '--quiet']) packages = ['podman', 'podman-docker', 'shadow', 'slirp4netns'] cmd = ['pacman', '-S', '--noconfirm', '--quiet'] + packages elif package_manager == 'apk': packages = ['podman', 'podman-docker', 'shadow', 'slirp4netns'] cmd = ['apk', 'add'] + packages else: return False success, stdout, stderr = run_with_sudo(cmd) if success: # Configure podman for rootless operation configure_podman_rootless() return verify_podman_installation() else: print(f"Package installation failed: {stderr}") return False def install_podman_static_binary(): """Install podman static binary (no dependencies)""" print("šŸ“„ Installing podman static binary...") try: home_bin = os.path.expanduser("~/bin") os.makedirs(home_bin, exist_ok=True) # Update PATH current_path = os.environ.get('PATH', '') if home_bin not in current_path: os.environ['PATH'] = f"{home_bin}:{current_path}" # Try multiple download URLs urls = [ "https://github.com/containers/podman/releases/latest/download/podman-remote-static-linux_amd64.tar.gz", "https://github.com/containers/podman/releases/download/v4.8.3/podman-remote-static-linux_amd64.tar.gz", "https://github.com/containers/podman/releases/download/v4.7.2/podman-remote-static-linux_amd64.tar.gz", ] for url in urls: try: print(f"Downloading from {url}...") with tempfile.NamedTemporaryFile(suffix='.tar.gz', delete=False) as tmp_file: urllib.request.urlretrieve(url, tmp_file.name, timeout=30) with tarfile.open(tmp_file.name, 'r:gz') as tar: for member in tar.getmembers(): if member.name.endswith('/podman') or member.name.endswith('/podman-remote'): tar.extract(member, home_bin) extracted_path = os.path.join(home_bin, os.path.basename(member.name)) os.chmod(extracted_path, 0o755) break os.unlink(tmp_file.name) break except Exception as e: print(f"Failed to download from {url}: {e}") continue return verify_podman_installation() except Exception as e: print(f"Static binary installation failed: {e}") return False def install_via_alternative_packages(): """Try alternative package names and installation methods""" print("šŸ”„ Trying alternative installation methods...") alternatives = [ # Try different package names (['apt', 'install', '-y', 'podman-compose', 'podman'], 'debian'), (['dnf', 'install', '-y', 'podman-compose'], 'fedora'), (['yum', 'install', '-y', 'podman-compose'], 'centos'), # Try snap (if available) (['snap', 'install', 'podman', '--classic'], 'any'), # Try flatpak (if available) (['flatpak', 'install', '-y', 'flathub', 'io.podman_desktop.Podman'], 'any'), ] for cmd, distro_check in alternatives: try: success, stdout, stderr = run_with_sudo(cmd) if success: print(f"Alternative installation successful with {cmd[0]}") return verify_podman_installation() except: continue return False def install_podman_from_source(): """Install podman from source (last resort)""" print("šŸ—ļø Installing podman from source (this may take a while)...") try: # This is complex and requires Go, so let's just try a simpler approach # We'll download a pre-compiled version from a known working source print("Source installation is complex. Trying simpler approach...") # Try to install via conda if available try: success, stdout, stderr = run_with_sudo(['conda', 'install', '-y', '-c', 'conda-forge', 'podman']) if success: return verify_podman_installation() except: pass # Try via pip (podman python package) try: success, stdout, stderr = run_with_sudo(['pip3', 'install', 'podman']) if success: return verify_podman_installation() except: pass return False except Exception as e: print(f"Source installation failed: {e}") return False def configure_podman_rootless(): """Configure podman for rootless operation""" try: # Enable unprivileged user namespaces run_with_sudo(['sysctl', 'kernel.unprivileged_userns_clone=1']) # Create podman configuration config_dir = os.path.expanduser("~/.config/containers") os.makedirs(config_dir, exist_ok=True) # Basic registries.conf registries_content = """[registries.search] registries = ['docker.io', 'quay.io'] [registries.insecure] registries = [] [registries.block] registries = [] """ with open(os.path.join(config_dir, 'registries.conf'), 'w') as f: f.write(registries_content) except Exception as e: print(f"Podman configuration warning: {e}") def verify_podman_installation(): """Verify that podman was installed successfully""" try: result = subprocess.run(['podman', '--version'], capture_output=True, text=True, timeout=10) if result.returncode == 0: version = result.stdout.strip() print(f"āœ… Podman verified: {version}") # Test basic functionality result = subprocess.run(['podman', 'info'], capture_output=True, text=True, timeout=15) if result.returncode == 0: print("āœ… Podman info command works") return True else: print(f"āš ļø Podman info failed: {result.stderr}") return True # Still consider it installed else: print(f"āŒ Podman verification failed: {result.stderr}") return False except Exception as e: print(f"āŒ Podman verification error: {e}") return False def install_podman_linux(): """Main entry point for podman installation""" return install_podman_comprehensive() def run_podman_cmd(cmd_args: List[str]) -> Tuple[bool, str, str]: try: cmd = ['podman'] + cmd_args result = subprocess.run(cmd, capture_output=True, text=True, timeout=30) return result.returncode == 0, result.stdout, result.stderr except FileNotFoundError: # Try to install podman on Linux if not found if platform.system() == 'Linux': print("Podman not found, attempting automatic installation...") if install_podman_linux(): # Try the command again after installation cmd = ['podman'] + cmd_args result = subprocess.run(cmd, capture_output=True, text=True, timeout=30) return result.returncode == 0, result.stdout, result.stderr else: return False, "", "Podman not found and automatic installation failed. Please install podman manually." else: 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'" except subprocess.TimeoutExpired: return False, "", "Command timed out" except Exception as e: return False, "", f"Error executing podman command: {e}" def copy_file_to_container(container_name: str, local_path: str, container_path: str) -> bool: try: cmd = ['podman', 'cp', local_path, f"{container_name}:{container_path}"] result = subprocess.run(cmd, capture_output=True, text=True) return result.returncode == 0 except Exception as e: print(f"Error copying file to container: {e}") return False def copy_file_from_container(container_name: str, container_path: str, local_path: str) -> bool: try: cmd = ['podman', 'cp', f"{container_name}:{container_path}", local_path] result = subprocess.run(cmd, capture_output=True, text=True) return result.returncode == 0 except Exception as e: print(f"Error copying file from container: {e}") return False def run_command_in_container(container_name: str, command: str, workdir: Optional[str] = None) -> Tuple[bool, str, str]: try: cmd = ['podman', 'exec'] if workdir: cmd.extend(['-w', workdir]) cmd.extend([container_name, 'bash', '-c', command]) result = subprocess.run(cmd, capture_output=True, text=True, timeout=60) return result.returncode == 0, result.stdout, result.stderr except subprocess.TimeoutExpired: return False, "", "Command execution timed out" except Exception as e: return False, "", f"Error executing command in container: {e}" def run_python_code_streaming(container_name: str, code: str, workdir: Optional[str] = None): try: with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f: f.write(code) temp_file = f.name temp_container_path = f'/tmp/python_script_{uuid.uuid4().hex[:8]}.py' success = copy_file_to_container(container_name, temp_file, temp_container_path) os.unlink(temp_file) if not success: yield "Error: Failed to copy Python file to container\n" return check_cmd = ['podman', 'exec', container_name, 'which', 'python'] check_result = subprocess.run(check_cmd, capture_output=True, text=True, timeout=10) python_command = 'python' if check_result.returncode != 0: check_cmd3 = ['podman', 'exec', container_name, 'which', 'python3'] check_result3 = subprocess.run(check_cmd3, capture_output=True, text=True, timeout=10) if check_result3.returncode == 0: python_command = 'python3' else: yield "Warning: python not found in container PATH, searching...\n" find_cmd = ['podman', 'exec', container_name, 'bash', '-c', 'find /usr -name python -o -name python3 -type f 2>/dev/null | head -1'] find_result = subprocess.run(find_cmd, capture_output=True, text=True, timeout=10) if find_result.returncode == 0 and find_result.stdout.strip(): python_command = find_result.stdout.strip() yield f"Found python at: {python_command}\n" else: yield "Error: Could not locate python in container\n" return cmd = ['podman', 'exec'] if workdir: cmd.extend(['-w', workdir]) cmd.extend([container_name, 'bash', '-c', f'{python_command} "{temp_container_path}" 2>&1']) process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1) try: for line in iter(process.stdout.readline, ''): if line: yield line except Exception as e: yield f"Error reading output: {e}\n" finally: process.stdout.close() process.wait() run_podman_cmd(['exec', container_name, 'rm', '-f', temp_container_path]) except Exception as e: yield f"Error executing Python code: {e}\n" def run_lua_code_streaming(container_name: str, code: str, workdir: Optional[str] = None): try: with tempfile.NamedTemporaryFile(mode='w', suffix='.lua', delete=False) as f: f.write(code) temp_file = f.name temp_container_path = f'/tmp/lua_script_{uuid.uuid4().hex[:8]}.lua' success = copy_file_to_container(container_name, temp_file, temp_container_path) os.unlink(temp_file) if not success: yield "Error: Failed to copy Lua file to container\n" return lua_executables = ['lua', 'lua5.4', 'lua5.3', 'lua5.2', 'lua5.1'] lua_command = None for exe in lua_executables: check_cmd = ['podman', 'exec', container_name, 'which', exe] check_result = subprocess.run(check_cmd, capture_output=True, text=True, timeout=10) if check_result.returncode == 0: lua_command = exe break if not lua_command: yield "Warning: No lua executable found in container PATH, searching filesystem...\n" find_cmd = ['podman', 'exec', container_name, 'bash', '-c', 'find /usr/local/bin /usr/bin -name "lua*" -type f 2>/dev/null | head -1'] find_result = subprocess.run(find_cmd, capture_output=True, text=True, timeout=10) if find_result.returncode == 0 and find_result.stdout.strip(): lua_command = find_result.stdout.strip() yield f"Found lua at: {lua_command}\n" else: yield "Error: Could not locate lua in container\n" return cmd = ['podman', 'exec'] if workdir: cmd.extend(['-w', workdir]) cmd.extend([container_name, 'bash', '-c', f'{lua_command} "{temp_container_path}" 2>&1']) process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1) try: for line in iter(process.stdout.readline, ''): if line: yield line except Exception as e: yield f"Error reading output: {e}\n" finally: process.stdout.close() process.wait() run_podman_cmd(['exec', container_name, 'rm', '-f', temp_container_path]) except Exception as e: yield f"Error executing Lua code: {e}\n" try: from flask import Flask, request, jsonify FLASK_AVAILABLE = True except ImportError: FLASK_AVAILABLE = False def install_package(package_name: str): try: print(f"Installing {package_name}...") import subprocess cmd = [sys.executable, "-m", "pip", "install", package_name] result = subprocess.run(cmd, capture_output=True, text=True) if result.returncode == 0: print(f"Successfully installed {package_name}") return True else: print(f"Failed to install {package_name}: {result.stderr}") return False except Exception as e: print(f"Error installing {package_name}: {e}") return False if not FLASK_AVAILABLE: print("Flask not found, attempting to install...") if install_package("flask"): try: from flask import Flask, request, jsonify FLASK_AVAILABLE = True except ImportError: FLASK_AVAILABLE = False if FLASK_AVAILABLE: from flask_cors import CORS app = Flask(__name__) CORS(app) class VMManager: def __init__(self): self.vms = {} self.cleanup_interval = 60 self.max_idle_time = 180 self.cleanup_thread = None self.lua_vm_id = None self._cleanup_running = False self._start_cleanup_thread() def _start_cleanup_thread(self): if self.cleanup_thread is None or not self.cleanup_thread.is_alive(): self.cleanup_thread = threading.Thread(target=self._cleanup_worker, daemon=True) self.cleanup_thread.start() def _cleanup_worker(self): while True: try: # Only run cleanup if not already running if not self._cleanup_running: self._cleanup_running = True try: self._perform_cleanup() finally: self._cleanup_running = False except Exception as e: print(f"Error in cleanup worker: {e}") self._cleanup_running = False time.sleep(self.cleanup_interval) def _perform_cleanup(self): """Perform the actual cleanup logic (used by both auto and manual cleanup)""" current_time = time.time() cleanup_threshold = 180 # 3 minutes for automatic cleanup deleted_count = 0 orphaned_containers_deleted = 0 print(f"Automatic cleanup: checking for idle VMs and orphaned containers...") vms_to_delete = [] for vm_id, vm in self.vms.items(): if vm_id == self.lua_vm_id: continue if current_time - vm.get('last_used', vm['created']) > cleanup_threshold: vms_to_delete.append(vm_id) for vm_id in vms_to_delete: age_minutes = round((current_time - self.vms[vm_id].get('last_used', self.vms[vm_id]['created'])) / 60, 1) print(f"Auto-deleting idle VM {vm_id} (unused for {age_minutes} minutes)") self.delete_vm(vm_id) deleted_count += 1 # Clean up orphaned podman containers try: ps_success, ps_stdout, ps_stderr = run_podman_cmd(['ps', '-a', '--filter', 'name=vm-', '--format', 'json']) if ps_success and ps_stdout: try: import json as json_lib containers = json_lib.loads(ps_stdout) if isinstance(containers, list): for container in containers: container_name = container.get('Names', [''])[0] if container.get('Names') else '' if container_name.startswith('vm-'): vm_id = container_name[3:] # Remove 'vm-' prefix if vm_id not in self.vms: print(f"Auto-deleting orphaned container {container_name}") run_podman_cmd(['rm', '-f', container_name]) orphaned_containers_deleted += 1 except Exception as e: print(f"Error parsing container list in auto cleanup: {e}") except Exception as e: print(f"Error checking for orphaned containers in auto cleanup: {e}") if deleted_count > 0 or orphaned_containers_deleted > 0: print(f"Automatic cleanup completed: deleted {deleted_count} VMs and {orphaned_containers_deleted} orphaned containers") else: print("Automatic cleanup completed: nothing to clean") def _update_last_used(self, vm_id): if vm_id in self.vms: self.vms[vm_id]['last_used'] = time.time() def _ensure_lua_vm(self): if self.lua_vm_id and self.lua_vm_id in self.vms: vm = self.vms[self.lua_vm_id] if vm.get('status') == 'running': return self.lua_vm_id print("Creating persistent Lua VM...") self.lua_vm_id = self.create_vm( vcpu=0.25, memory="512m", image="debian:latest", install_python=False ) if self.lua_vm_id: print(f"Created persistent Lua VM: {self.lua_vm_id}") self._install_lua_in_persistent_vm(self.lua_vm_id) return self.lua_vm_id def create_vm(self, vcpu=1, memory="512m", image="debian:latest", install_python=True): vm_id = str(uuid.uuid4())[:8] container_name = f"vm-{vm_id}" # Create and start container success, stdout, stderr = run_podman_cmd([ 'run', '-d', '--name', container_name, '--cpus', str(vcpu), '--memory', memory, '--rm', image, 'sleep', 'infinity' ]) if not success: print(f"Failed to create container: {stderr}") if "authentication required" in stderr.lower(): print("Podman authentication issue. Try running: podman login docker.io") elif "podman not found" in stderr.lower(): print("Podman not installed. Install with: sudo apt install podman") return None self.vms[vm_id] = { 'container_name': container_name, 'vcpu': vcpu, 'memory': memory, 'created': time.time(), 'last_used': time.time(), 'commands': [], 'cwd': '/' # Default working directory } if 'debian' in image.lower() or 'ubuntu' in image.lower(): if install_python: self._install_python_in_vm(vm_id) self._install_lua_in_vm(vm_id) elif 'alpine' in image.lower(): self._install_lua_in_vm(vm_id) return vm_id def _install_python_in_vm(self, vm_id): if vm_id not in self.vms: return container_name = self.vms[vm_id]['container_name'] update_success, update_stdout, update_stderr = run_podman_cmd(['exec', container_name, 'apt-get', 'update', '-qq']) if not update_success: print(f"Warning: Failed to update package lists for VM {vm_id}: {update_stderr}") python_success, python_stdout, python_stderr = run_podman_cmd(['exec', container_name, 'apt-get', 'install', '-y', 'python3', 'python3-pip']) if not python_success: print(f"Warning: Failed to install Python for VM {vm_id}: {python_stderr}") alt_success, alt_stdout, alt_stderr = run_podman_cmd(['exec', container_name, 'apt-get', 'install', '-y', 'python3-minimal']) if not alt_success: print(f"Warning: Failed to install python3-minimal for VM {vm_id}: {alt_stderr}") else: print(f"Successfully installed python3-minimal for VM {vm_id}") else: print(f"Successfully installed Python3 and pip for VM {vm_id}") verify_success, verify_stdout, verify_stderr = run_podman_cmd(['exec', container_name, 'python3', '--version']) if verify_success: print(f"Python verification successful for VM {vm_id}: {verify_stdout.strip()}") else: print(f"Warning: Python verification failed for VM {vm_id}: {verify_stderr}") def _install_lua_in_vm(self, vm_id): if vm_id not in self.vms: return container_name = self.vms[vm_id]['container_name'] print(f"Installing Lua for VM {vm_id}...") lua_install_success, lua_install_stdout, lua_install_stderr = run_podman_cmd([ 'exec', container_name, 'apk', 'add', '--no-cache', 'lua5.4' ]) if not lua_install_success: print(f"Warning: Failed to install Lua for VM {vm_id}: {lua_install_stderr}") return print(f"Successfully installed Lua 5.4 for VM {vm_id}") verify_success, verify_stdout, verify_stderr = run_podman_cmd(['exec', container_name, 'lua5.4', '--version']) if verify_success: print(f"Lua verification successful for VM {vm_id}: {verify_stdout.strip()}") else: print(f"Warning: Lua verification failed for VM {vm_id}: {verify_stderr}") print("Checking what Lua executables are available...") which_success, which_stdout, which_stderr = run_podman_cmd(['exec', container_name, 'find', '/usr/bin', '-name', 'lua*', '-type', 'f']) if which_success and which_stdout.strip(): print(f"Found Lua executables: {which_stdout.strip()}") else: print("No Lua executables found in /usr/bin") def _install_lua_in_persistent_vm(self, vm_id): if vm_id not in self.vms: return container_name = self.vms[vm_id]['container_name'] print(f"Installing Lua in persistent VM {vm_id}...") update_success, update_stdout, update_stderr = run_podman_cmd(['exec', container_name, 'apt-get', 'update', '-qq']) if not update_success: print(f"Warning: Failed to update package lists for Lua installation in persistent VM {vm_id}: {update_stderr}") build_deps_success, build_deps_stdout, build_deps_stderr = run_podman_cmd([ 'exec', container_name, 'apt-get', 'install', '-y', '-qq', 'curl', 'build-essential', 'libreadline-dev' ]) if not build_deps_success: print(f"Warning: Failed to install build dependencies for persistent VM {vm_id}: {build_deps_stderr}") return print(f"Downloading and compiling Lua in persistent VM {vm_id}...") lua_install_cmd = ''' cd /tmp && \ curl -L -o lua.tar.gz http://www.lua.org/ftp/lua-5.4.6.tar.gz && \ tar zxf lua.tar.gz && \ cd lua-5.4.6 && \ make linux && \ make install && \ cd /tmp && \ rm -rf lua-5.4.6 lua.tar.gz '''.strip() install_success, install_stdout, install_stderr = run_podman_cmd([ 'exec', container_name, 'bash', '-c', lua_install_cmd ]) if not install_success: print(f"Warning: Failed to compile/install Lua in persistent VM {vm_id}: {install_stderr}") return verify_success, verify_stdout, verify_stderr = run_podman_cmd(['exec', container_name, 'lua', '--version']) if verify_success: print(f"Lua verification successful in persistent VM {vm_id}: {verify_stdout.strip()}") else: print(f"Warning: Lua verification failed in persistent VM {vm_id}: {verify_stderr}") def run_command(self, vm_id, command, workdir=None): if vm_id not in self.vms: return None self._update_last_used(vm_id) vm = self.vms[vm_id] container_name = vm['container_name'] command_stripped = command.strip() if command_stripped == 'cd' or command_stripped.startswith('cd '): if command_stripped == 'cd': vm['cwd'] = '/root' result = "Changed directory to /root" else: new_dir = command_stripped[3:].strip() if new_dir: if not new_dir.startswith('/'): new_dir = os.path.join(vm['cwd'], new_dir) new_dir = os.path.normpath(new_dir) test_cmd = f'test -d "{new_dir}" && echo "DIR_EXISTS" || echo "DIR_NOT_FOUND"' success, stdout, stderr = run_command_in_container(container_name, test_cmd, vm['cwd']) if success and 'DIR_EXISTS' in stdout: vm['cwd'] = new_dir result = f"Changed directory to {new_dir}" else: result = f"cd: {new_dir}: No such file or directory" else: vm['cwd'] = '/root' result = "Changed directory to /root" else: current_workdir = workdir if workdir is not None else vm['cwd'] success, stdout, stderr = run_command_in_container(container_name, command, current_workdir) if success: result = stdout.rstrip() if stdout else "" if not result and command.strip() == 'ls': success2, stdout2, stderr2 = run_command_in_container(container_name, 'ls -a', current_workdir) if success2: result = stdout2.rstrip() if stdout2 else "(directory appears empty)" else: result = f"ls failed: {stderr2.strip()}" elif not result and stderr.strip(): result = f"(no output) stderr: {stderr.strip()}" else: result = f"Error: {stderr.strip()}" if stderr else f"Command failed - stderr: '{stderr}'" vm['commands'].append({ 'command': command, 'result': result, 'time': time.time() }) return result def copy_file_to_vm(self, vm_id, local_path, container_path): if vm_id not in self.vms: return False self._update_last_used(vm_id) container_name = self.vms[vm_id]['container_name'] return copy_file_to_container(container_name, local_path, container_path) def copy_file_from_vm(self, vm_id, container_path, local_path): if vm_id not in self.vms: return False self._update_last_used(vm_id) container_name = self.vms[vm_id]['container_name'] return copy_file_from_container(container_name, container_path, local_path) def execute_python_streaming(self, vm_id, code, workdir=None): if vm_id in self.vms: self._update_last_used(vm_id) python_vm_id = self.create_vm( vcpu=0.25, memory="300m", image="python:3.11-slim", install_python=False ) if not python_vm_id: yield "Error: Failed to create Python VM\n" return try: python_vm = self.vms[python_vm_id] container_name = python_vm['container_name'] current_workdir = workdir or '/' for line in run_python_code_streaming(container_name, code, current_workdir): yield line finally: self.delete_vm(python_vm_id) def execute_lua_streaming(self, vm_id, code, workdir=None): if vm_id in self.vms: self._update_last_used(vm_id) lua_vm_id = self._ensure_lua_vm() if not lua_vm_id: yield "Error: Failed to ensure Lua VM is available\n" return lua_vm = self.vms[lua_vm_id] container_name = lua_vm['container_name'] current_workdir = workdir or '/' for line in run_lua_code_streaming(container_name, code, current_workdir): yield line self._update_last_used(lua_vm_id) def get_vm_status(self, vm_id): if vm_id not in self.vms: return None self._update_last_used(vm_id) vm = self.vms[vm_id] container_name = vm['container_name'] success, stdout, stderr = run_podman_cmd([ 'ps', '--filter', f'name={container_name}', '--format', 'json' ]) vm_copy = vm.copy() if success and stdout: try: import json as json_lib containers = json_lib.loads(stdout) if containers: vm_copy['status'] = 'running' else: vm_copy['status'] = 'stopped' except: vm_copy['status'] = 'unknown' else: vm_copy['status'] = 'error' return vm_copy def get_vm_cwd(self, vm_id): if vm_id not in self.vms: return None self._update_last_used(vm_id) return self.vms[vm_id]['cwd'] def list_vms(self): return list(self.vms.keys()) def delete_vm(self, vm_id): if vm_id not in self.vms: return False container_name = self.vms[vm_id]['container_name'] run_podman_cmd(['stop', container_name]) run_podman_cmd(['rm', container_name]) del self.vms[vm_id] return True manager = VMManager() @app.route('/') def index(): return """ Simple VM API

Simple VM API

Endpoints:

""" @app.route('/vm', methods=['POST']) def create_vm(): data = request.get_json() or {} vcpu = data.get('vcpu', 1) memory = data.get('memory', '512m') image = data.get('image', 'debian:latest') install_python = data.get('install_python', True) vm_id = manager.create_vm(vcpu, memory, image, install_python) return jsonify({'vm_id': vm_id, 'vcpu': vcpu, 'memory': memory, 'image': image}) @app.route('/vm//command', methods=['POST']) def run_command(vm_id): data = request.get_json() or {} command = data.get('command', '') result = manager.run_command(vm_id, command) if result is None: return jsonify({'error': 'VM not found'}), 404 return jsonify({'output': result, 'command': command}) @app.route('/vm//status') def get_status(vm_id): status = manager.get_vm_status(vm_id) if not status: return jsonify({'error': 'VM not found'}), 404 return jsonify(status) @app.route('/vm//cwd') def get_cwd(vm_id): cwd = manager.get_vm_cwd(vm_id) if cwd is None: return jsonify({'error': 'VM not found'}), 404 return jsonify({'cwd': cwd}) @app.route('/vm//python', methods=['POST']) def execute_python(vm_id): if vm_id in manager.vms: manager._update_last_used(vm_id) code = None if 'file' in request.files and request.files['file'].filename: file = request.files['file'] if not file.filename.endswith('.py'): return jsonify({'error': 'Only .py files are allowed'}), 400 file.seek(0, os.SEEK_END) file_size = file.tell() file.seek(0) if file_size > 10 * 1024 * 1024: return jsonify({'error': 'File too large (max 10MB)'}), 400 code = file.read().decode('utf-8') elif request.is_json and request.get_json().get('code'): code = request.get_json()['code'] else: return jsonify({'error': 'No Python code provided. Use "code" field in JSON or upload a .py file'}), 400 if not code or not code.strip(): return jsonify({'error': 'Empty Python code'}), 400 workdir = request.args.get('workdir') or request.get_json().get('workdir') if request.is_json else None def generate(): try: for line in manager.execute_python_streaming(vm_id, code, workdir): yield f"data: {line}\n\n" except Exception as e: yield f"data: Error: {str(e)}\n\n" return app.response_class(generate(), mimetype='text/event-stream') @app.route('/vm//lua', methods=['POST']) def execute_lua(vm_id): if vm_id in manager.vms: manager._update_last_used(vm_id) code = None if 'file' in request.files and request.files['file'].filename: file = request.files['file'] if not file.filename.endswith('.lua'): return jsonify({'error': 'Only .lua files are allowed'}), 400 file.seek(0, os.SEEK_END) file_size = file.tell() file.seek(0) if file_size > 10 * 1024 * 1024: return jsonify({'error': 'File too large (max 10MB)'}), 400 code = file.read().decode('utf-8') elif request.is_json and request.get_json().get('code'): code = request.get_json()['code'] else: return jsonify({'error': 'No Lua code provided. Use "code" field in JSON or upload a .lua file'}), 400 if not code or not code.strip(): return jsonify({'error': 'Empty Lua code'}), 400 workdir = request.args.get('workdir') or request.get_json().get('workdir') if request.is_json else None def generate(): try: for line in manager.execute_lua_streaming(vm_id, code, workdir): yield f"data: {line}\n\n" except Exception as e: yield f"data: Error: {str(e)}\n\n" return app.response_class(generate(), mimetype='text/event-stream') @app.route('/vm/', methods=['DELETE']) def delete_vm(vm_id): if manager.delete_vm(vm_id): return jsonify({'message': 'VM deleted'}) return jsonify({'error': 'VM not found'}), 404 @app.route('/vm//copy-to', methods=['POST']) def copy_to_vm(vm_id): data = request.get_json() or {} local_path = data.get('local_path', '') container_path = data.get('container_path', '') if not local_path or not container_path: return jsonify({'error': 'local_path and container_path required'}), 400 if not os.path.exists(local_path): return jsonify({'error': 'Local file does not exist'}), 400 success = manager.copy_file_to_vm(vm_id, local_path, container_path) if success: return jsonify({'message': 'File copied successfully'}) return jsonify({'error': 'Failed to copy file'}), 500 @app.route('/vm//copy-from', methods=['POST']) def copy_from_vm(vm_id): data = request.get_json() or {} container_path = data.get('container_path', '') local_path = data.get('local_path', '') if not container_path or not local_path: return jsonify({'error': 'container_path and local_path required'}), 400 success = manager.copy_file_from_vm(vm_id, container_path, local_path) if success: return jsonify({'message': 'File copied successfully'}) return jsonify({'error': 'Failed to copy file'}), 500 @app.route('/cleanup') def cleanup(): def cleanup_generator(): import time import json as json_lib if manager._cleanup_running: yield f"data: {json_lib.dumps({'event': 'busy', 'message': 'Cleanup already running, please wait...'})}\n\n" return manager._cleanup_running = True try: current_time = time.time() cleanup_threshold = 600 # 10 minutes for manual cleanup deleted_count = 0 orphaned_containers_deleted = 0 yield f"data: {json_lib.dumps({'event': 'started', 'message': 'Starting manual cleanup process (10+ minute threshold)...'})}\n\n" vms_info = [] vms_to_delete = [] # First, analyze managed VMs yield f"data: {json_lib.dumps({'event': 'analyzing_vms', 'message': 'Analyzing managed VMs...'})}\n\n" for vm_id, vm in manager.vms.items(): last_activity = vm.get('last_used', vm.get('created', 0)) age_seconds = current_time - last_activity age_minutes = age_seconds / 60 vm_info = { 'vm_id': vm_id, 'age_minutes': round(age_minutes, 1), 'is_lua_vm': vm_id == manager.lua_vm_id, 'should_delete': age_seconds > cleanup_threshold and vm_id != manager.lua_vm_id } vms_info.append(vm_info) if vm_id == manager.lua_vm_id: continue if age_seconds > cleanup_threshold: vms_to_delete.append(vm_id) 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" # Clean up managed VMs that are idle if vms_to_delete: yield f"data: {json_lib.dumps({'event': 'deleting_vms', 'message': f'Deleting {len(vms_to_delete)} idle VMs...'})}\n\n" for vm_id in vms_to_delete: age_minutes = round((current_time - manager.vms[vm_id].get('last_used', manager.vms[vm_id]['created'])) / 60, 1) yield f"data: {json_lib.dumps({'event': 'deleting_vm', 'vm_id': vm_id, 'age_minutes': age_minutes})}\n\n" print(f"Manual cleanup: deleting idle VM {vm_id} (unused for {age_minutes} minutes)") manager.delete_vm(vm_id) deleted_count += 1 yield f"data: {json_lib.dumps({'event': 'vm_deleted', 'vm_id': vm_id, 'deleted_count': deleted_count})}\n\n" # Now clean up orphaned podman containers yield f"data: {json_lib.dumps({'event': 'checking_containers', 'message': 'Checking for orphaned podman containers...'})}\n\n" try: # Get all containers with our naming pattern ps_success, ps_stdout, ps_stderr = run_podman_cmd(['ps', '-a', '--filter', 'name=vm-', '--format', 'json']) if ps_success and ps_stdout: try: containers = json_lib.loads(ps_stdout) orphaned_containers = [] if isinstance(containers, list): for container in containers: container_name = container.get('Names', [''])[0] if container.get('Names') else '' if container_name.startswith('vm-'): vm_id = container_name[3:] # Remove 'vm-' prefix # Check if this VM is still managed if vm_id not in manager.vms: orphaned_containers.append(container_name) if orphaned_containers: yield f"data: {json_lib.dumps({'event': 'orphaned_found', 'count': len(orphaned_containers), 'containers': orphaned_containers})}\n\n" for container_name in orphaned_containers: yield f"data: {json_lib.dumps({'event': 'deleting_container', 'container': container_name})}\n\n" print(f"Manual cleanup: deleting orphaned container {container_name}") rm_success, rm_stdout, rm_stderr = run_podman_cmd(['rm', '-f', container_name]) if rm_success: orphaned_containers_deleted += 1 yield f"data: {json_lib.dumps({'event': 'container_deleted', 'container': container_name, 'orphaned_deleted': orphaned_containers_deleted})}\n\n" else: yield f"data: {json_lib.dumps({'event': 'container_delete_failed', 'container': container_name, 'error': rm_stderr})}\n\n" else: yield f"data: {json_lib.dumps({'event': 'no_orphaned', 'message': 'No orphaned containers found'})}\n\n" except Exception as e: yield f"data: {json_lib.dumps({'event': 'error', 'message': f'Error parsing container list: {str(e)}'})}\n\n" else: yield f"data: {json_lib.dumps({'event': 'error', 'message': f'Failed to list containers: {ps_stderr}'})}\n\n" except Exception as e: yield f"data: {json_lib.dumps({'event': 'error', 'message': f'Error checking for orphaned containers: {str(e)}'})}\n\n" # Final summary summary_data = { 'event': 'completed', 'summary': { 'deleted_vms': deleted_count, 'deleted_containers': orphaned_containers_deleted, 'total_cleaned': deleted_count + orphaned_containers_deleted, 'remaining_vms': len(manager.vms) } } yield f"data: {json_lib.dumps(summary_data)}\n\n" finally: manager._cleanup_running = False return app.response_class(cleanup_generator(), mimetype='text/event-stream') @app.route('/poll') def poll(): return jsonify({'vms': manager.list_vms()}) def main(): if len(sys.argv) < 2: command = "server" else: command = sys.argv[1] if command == "server": if not FLASK_AVAILABLE: print("Flask not available") return # Check if podman is available (will auto-install on Linux if needed) check_podman, podman_version, _ = run_podman_cmd(['--version']) if check_podman: print(f"Podman is available - {podman_version.strip()}") else: print("Podman not found initially, but will be auto-installed on first use if running on Linux") print("Starting simple VM API server on http://localhost:7860") print("Podman is available - ready to create containers!") app.run(host='0.0.0.0', port=7860, debug=True) elif command == "list": if not FLASK_AVAILABLE: print("Flask not available") return manager = VMManager() vms = manager.list_vms() print(f"VMs: {vms}") else: print("Usage: python3 lib.py [server|list]") if __name__ == "__main__": main()