mirror of
https://github.com/trycua/computer.git
synced 2026-05-07 23:51:55 -05:00
added sandbox provider
This commit is contained in:
@@ -109,7 +109,7 @@ class Computer:
|
|||||||
|
|
||||||
# Windows Sandbox always uses ephemeral storage
|
# Windows Sandbox always uses ephemeral storage
|
||||||
if self.provider_type == VMProviderType.WINSANDBOX:
|
if self.provider_type == VMProviderType.WINSANDBOX:
|
||||||
if not ephemeral:
|
if not ephemeral and storage != None and storage != "ephemeral":
|
||||||
self.logger.warning("Windows Sandbox storage is always ephemeral. Setting ephemeral=True.")
|
self.logger.warning("Windows Sandbox storage is always ephemeral. Setting ephemeral=True.")
|
||||||
self.ephemeral = True
|
self.ephemeral = True
|
||||||
self.storage = "ephemeral"
|
self.storage = "ephemeral"
|
||||||
@@ -400,7 +400,6 @@ class Computer:
|
|||||||
# Wait for VM to be ready with a valid IP address
|
# Wait for VM to be ready with a valid IP address
|
||||||
self.logger.info("Waiting for VM to be ready with a valid IP address...")
|
self.logger.info("Waiting for VM to be ready with a valid IP address...")
|
||||||
try:
|
try:
|
||||||
# Increased values for Lumier provider which needs more time for initial setup
|
|
||||||
if self.provider_type == VMProviderType.LUMIER:
|
if self.provider_type == VMProviderType.LUMIER:
|
||||||
max_retries = 60 # Increased for Lumier VM startup which takes longer
|
max_retries = 60 # Increased for Lumier VM startup which takes longer
|
||||||
retry_delay = 3 # 3 seconds between retries for Lumier
|
retry_delay = 3 # 3 seconds between retries for Lumier
|
||||||
@@ -530,7 +529,7 @@ class Computer:
|
|||||||
return
|
return
|
||||||
|
|
||||||
# @property
|
# @property
|
||||||
async def get_ip(self, max_retries: int = 15, retry_delay: int = 2) -> str:
|
async def get_ip(self, max_retries: int = 15, retry_delay: int = 3) -> str:
|
||||||
"""Get the IP address of the VM or localhost if using host computer server.
|
"""Get the IP address of the VM or localhost if using host computer server.
|
||||||
|
|
||||||
This method delegates to the provider's get_ip method, which waits indefinitely
|
This method delegates to the provider's get_ip method, which waits indefinitely
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
@echo off
|
|
||||||
REM Logon script for Windows Sandbox CUA Computer provider
|
|
||||||
REM This script runs when the sandbox starts
|
|
||||||
|
|
||||||
REM Open explorer to show the desktop
|
|
||||||
explorer .
|
|
||||||
|
|
||||||
REM TODO: Install CUA computer server
|
|
||||||
REM pip install cua-computer-server
|
|
||||||
REM python -m computer_server.main --ws
|
|
||||||
@@ -121,18 +121,73 @@ class WinSandboxProvider(BaseVMProvider):
|
|||||||
|
|
||||||
# Check if sandbox is still running
|
# Check if sandbox is still running
|
||||||
try:
|
try:
|
||||||
# For Windows Sandbox, we assume it's running if it's in our active list
|
|
||||||
# and hasn't been terminated
|
|
||||||
# Try to ping the sandbox to see if it's responsive
|
# Try to ping the sandbox to see if it's responsive
|
||||||
try:
|
try:
|
||||||
# Simple test to see if RPyC connection is alive
|
|
||||||
sandbox.rpyc.modules.os.getcwd()
|
sandbox.rpyc.modules.os.getcwd()
|
||||||
status = "running"
|
sandbox_responsive = True
|
||||||
# Windows Sandbox typically uses localhost for RPyC connections
|
|
||||||
ip_address = "127.0.0.1"
|
|
||||||
except Exception:
|
except Exception:
|
||||||
|
sandbox_responsive = False
|
||||||
|
|
||||||
|
if not sandbox_responsive:
|
||||||
|
return {
|
||||||
|
"name": name,
|
||||||
|
"status": "starting",
|
||||||
|
"ip_address": None,
|
||||||
|
"storage": "ephemeral",
|
||||||
|
"memory_mb": self.memory_mb,
|
||||||
|
"networking": self.networking
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check for computer server address file
|
||||||
|
server_address_file = r"C:\Users\WDAGUtilityAccount\Desktop\shared_windows_sandbox_dir\server_address"
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Check if the server address file exists
|
||||||
|
file_exists = sandbox.rpyc.modules.os.path.exists(server_address_file)
|
||||||
|
|
||||||
|
if file_exists:
|
||||||
|
# Read the server address file
|
||||||
|
with sandbox.rpyc.builtin.open(server_address_file, 'r') as f:
|
||||||
|
server_address = f.read().strip()
|
||||||
|
|
||||||
|
if server_address and ':' in server_address:
|
||||||
|
# Parse IP:port from the file
|
||||||
|
ip_address, port = server_address.split(':', 1)
|
||||||
|
|
||||||
|
# Verify the server is actually responding
|
||||||
|
try:
|
||||||
|
import socket
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
sock.settimeout(3)
|
||||||
|
result = sock.connect_ex((ip_address, int(port)))
|
||||||
|
sock.close()
|
||||||
|
|
||||||
|
if result == 0:
|
||||||
|
# Server is responding
|
||||||
|
status = "running"
|
||||||
|
self.logger.debug(f"Computer server found at {ip_address}:{port}")
|
||||||
|
else:
|
||||||
|
# Server file exists but not responding
|
||||||
|
status = "starting"
|
||||||
|
ip_address = None
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.debug(f"Error checking server connectivity: {e}")
|
||||||
|
status = "starting"
|
||||||
|
ip_address = None
|
||||||
|
else:
|
||||||
|
# File exists but doesn't contain valid address
|
||||||
|
status = "starting"
|
||||||
|
ip_address = None
|
||||||
|
else:
|
||||||
|
# Server address file doesn't exist yet
|
||||||
|
status = "starting"
|
||||||
|
ip_address = None
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.debug(f"Error checking server address file: {e}")
|
||||||
status = "starting"
|
status = "starting"
|
||||||
ip_address = None
|
ip_address = None
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Error checking sandbox status: {e}")
|
self.logger.error(f"Error checking sandbox status: {e}")
|
||||||
status = "error"
|
status = "error"
|
||||||
@@ -187,9 +242,6 @@ class WinSandboxProvider(BaseVMProvider):
|
|||||||
|
|
||||||
networking = run_opts.get("networking", self.networking)
|
networking = run_opts.get("networking", self.networking)
|
||||||
|
|
||||||
# Get the logon script path
|
|
||||||
script_path = os.path.join(os.path.dirname(__file__), "logon_script.bat")
|
|
||||||
|
|
||||||
# Create folder mappers if shared directories are specified
|
# Create folder mappers if shared directories are specified
|
||||||
folder_mappers = []
|
folder_mappers = []
|
||||||
shared_directories = run_opts.get("shared_directories", [])
|
shared_directories = run_opts.get("shared_directories", [])
|
||||||
@@ -209,11 +261,10 @@ class WinSandboxProvider(BaseVMProvider):
|
|||||||
if folder_mappers:
|
if folder_mappers:
|
||||||
self.logger.info(f"Shared directories: {len(folder_mappers)}")
|
self.logger.info(f"Shared directories: {len(folder_mappers)}")
|
||||||
|
|
||||||
# Create the sandbox
|
# Create the sandbox without logon script
|
||||||
sandbox = winsandbox.new_sandbox(
|
sandbox = winsandbox.new_sandbox(
|
||||||
memory_mb=str(memory_mb),
|
memory_mb=str(memory_mb),
|
||||||
networking=networking,
|
networking=networking,
|
||||||
logon_script=f'cmd /c "{script_path}"',
|
|
||||||
folder_mappers=folder_mappers
|
folder_mappers=folder_mappers
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -222,6 +273,9 @@ class WinSandboxProvider(BaseVMProvider):
|
|||||||
|
|
||||||
self.logger.info(f"Windows Sandbox {name} created successfully")
|
self.logger.info(f"Windows Sandbox {name} created successfully")
|
||||||
|
|
||||||
|
# Setup the computer server in the sandbox
|
||||||
|
await self._setup_computer_server(sandbox, name)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"success": True,
|
"success": True,
|
||||||
"name": name,
|
"name": name,
|
||||||
@@ -233,6 +287,9 @@ class WinSandboxProvider(BaseVMProvider):
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Failed to create Windows Sandbox {name}: {e}")
|
self.logger.error(f"Failed to create Windows Sandbox {name}: {e}")
|
||||||
|
# stack trace
|
||||||
|
import traceback
|
||||||
|
self.logger.error(f"Stack trace: {traceback.format_exc()}")
|
||||||
return {
|
return {
|
||||||
"success": False,
|
"success": False,
|
||||||
"error": f"Failed to create sandbox: {str(e)}"
|
"error": f"Failed to create sandbox: {str(e)}"
|
||||||
@@ -348,3 +405,66 @@ class WinSandboxProvider(BaseVMProvider):
|
|||||||
# Add progress log every 10 attempts
|
# Add progress log every 10 attempts
|
||||||
if total_attempts % 10 == 0:
|
if total_attempts % 10 == 0:
|
||||||
self.logger.info(f"Still waiting for Windows Sandbox {name} IP after {total_attempts} attempts...")
|
self.logger.info(f"Still waiting for Windows Sandbox {name} IP after {total_attempts} attempts...")
|
||||||
|
|
||||||
|
async def _setup_computer_server(self, sandbox, name: str, visible: bool = False):
|
||||||
|
"""Setup the computer server in the Windows Sandbox using RPyC.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sandbox: The Windows Sandbox instance
|
||||||
|
name: Name of the sandbox
|
||||||
|
visible: Whether the opened process should be visible (default: False)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self.logger.info(f"Setting up computer server in sandbox {name}...")
|
||||||
|
print(f"Setting up computer server in sandbox {name}...")
|
||||||
|
|
||||||
|
# Read the PowerShell setup script
|
||||||
|
script_path = os.path.join(os.path.dirname(__file__), "setup_script.ps1")
|
||||||
|
with open(script_path, 'r', encoding='utf-8') as f:
|
||||||
|
setup_script_content = f.read()
|
||||||
|
|
||||||
|
# Write the setup script to the sandbox using RPyC
|
||||||
|
script_dest_path = r"C:\Users\WDAGUtilityAccount\setup_cua.ps1"
|
||||||
|
|
||||||
|
print(f"Writing setup script to {script_dest_path}")
|
||||||
|
with sandbox.rpyc.builtin.open(script_dest_path, 'w') as f:
|
||||||
|
f.write(setup_script_content)
|
||||||
|
|
||||||
|
# Execute the PowerShell script in the background
|
||||||
|
print("Executing setup script in sandbox...")
|
||||||
|
|
||||||
|
# Use subprocess to run PowerShell script
|
||||||
|
import subprocess
|
||||||
|
powershell_cmd = [
|
||||||
|
"powershell.exe",
|
||||||
|
"-ExecutionPolicy", "Bypass",
|
||||||
|
"-NoExit", # Keep window open after script completes
|
||||||
|
"-File", script_dest_path
|
||||||
|
]
|
||||||
|
|
||||||
|
# Set creation flags based on visibility preference
|
||||||
|
if visible:
|
||||||
|
# CREATE_NEW_CONSOLE - creates a new console window (visible)
|
||||||
|
creation_flags = 0x00000010
|
||||||
|
else:
|
||||||
|
# DETACHED_PROCESS - runs in background (not visible)
|
||||||
|
creation_flags = 0x00000008
|
||||||
|
|
||||||
|
# Start the process using RPyC
|
||||||
|
process = sandbox.rpyc.modules.subprocess.Popen(
|
||||||
|
powershell_cmd,
|
||||||
|
creationflags=creation_flags,
|
||||||
|
shell=False
|
||||||
|
)
|
||||||
|
|
||||||
|
# Sleep for 30 seconds
|
||||||
|
await asyncio.sleep(30)
|
||||||
|
|
||||||
|
ip = await self.get_ip(name)
|
||||||
|
print(f"Sandbox IP: {ip}")
|
||||||
|
print(f"Setup script started in background in sandbox {name} with PID: {process.pid}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Failed to setup computer server in sandbox {name}: {e}")
|
||||||
|
import traceback
|
||||||
|
self.logger.error(f"Stack trace: {traceback.format_exc()}")
|
||||||
|
|||||||
@@ -0,0 +1,124 @@
|
|||||||
|
# Setup script for Windows Sandbox CUA Computer provider
|
||||||
|
# This script runs when the sandbox starts
|
||||||
|
|
||||||
|
Write-Host "Starting CUA Computer setup in Windows Sandbox..."
|
||||||
|
|
||||||
|
# Function to find the mapped Python installation from pywinsandbox
|
||||||
|
function Find-MappedPython {
|
||||||
|
Write-Host "Looking for mapped Python installation from pywinsandbox..."
|
||||||
|
|
||||||
|
# pywinsandbox maps the host Python installation to the sandbox
|
||||||
|
# Look for mapped shared folders on the desktop (common pywinsandbox pattern)
|
||||||
|
$desktopPath = "C:\Users\WDAGUtilityAccount\Desktop"
|
||||||
|
$sharedFolders = Get-ChildItem -Path $desktopPath -Directory -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
foreach ($folder in $sharedFolders) {
|
||||||
|
# Look for Python executables in shared folders
|
||||||
|
$pythonPaths = @(
|
||||||
|
"$($folder.FullName)\python.exe",
|
||||||
|
"$($folder.FullName)\Scripts\python.exe",
|
||||||
|
"$($folder.FullName)\bin\python.exe"
|
||||||
|
)
|
||||||
|
|
||||||
|
foreach ($pythonPath in $pythonPaths) {
|
||||||
|
if (Test-Path $pythonPath) {
|
||||||
|
try {
|
||||||
|
$version = & $pythonPath --version 2>&1
|
||||||
|
if ($version -match "Python") {
|
||||||
|
Write-Host "Found mapped Python: $pythonPath - $version"
|
||||||
|
return $pythonPath
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Also check subdirectories that might contain Python
|
||||||
|
$subDirs = Get-ChildItem -Path $folder.FullName -Directory -ErrorAction SilentlyContinue
|
||||||
|
foreach ($subDir in $subDirs) {
|
||||||
|
$pythonPath = "$($subDir.FullName)\python.exe"
|
||||||
|
if (Test-Path $pythonPath) {
|
||||||
|
try {
|
||||||
|
$version = & $pythonPath --version 2>&1
|
||||||
|
if ($version -match "Python") {
|
||||||
|
Write-Host "Found mapped Python in subdirectory: $pythonPath - $version"
|
||||||
|
return $pythonPath
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Fallback: try common Python commands that might be available
|
||||||
|
$pythonCommands = @("python", "py", "python3")
|
||||||
|
foreach ($cmd in $pythonCommands) {
|
||||||
|
try {
|
||||||
|
$version = & $cmd --version 2>&1
|
||||||
|
if ($version -match "Python") {
|
||||||
|
Write-Host "Found Python via command '$cmd': $version"
|
||||||
|
return $cmd
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw "Could not find any Python installation (mapped or otherwise)"
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
# Step 1: Find the mapped Python installation
|
||||||
|
Write-Host "Step 1: Finding mapped Python installation..."
|
||||||
|
$pythonExe = Find-MappedPython
|
||||||
|
Write-Host "Using Python: $pythonExe"
|
||||||
|
|
||||||
|
# Verify Python works and show version
|
||||||
|
$pythonVersion = & $pythonExe --version 2>&1
|
||||||
|
Write-Host "Python version: $pythonVersion"
|
||||||
|
|
||||||
|
# Step 2: Install cua-computer-server directly
|
||||||
|
Write-Host "Step 2: Installing cua-computer-server..."
|
||||||
|
|
||||||
|
Write-Host "Upgrading pip..."
|
||||||
|
& $pythonExe -m pip install --upgrade pip --quiet
|
||||||
|
|
||||||
|
Write-Host "Installing cua-computer-server..."
|
||||||
|
& $pythonExe -m pip install cua-computer-server --quiet
|
||||||
|
|
||||||
|
Write-Host "cua-computer-server installation completed."
|
||||||
|
|
||||||
|
# Step 3: Start computer server in background
|
||||||
|
Write-Host "Step 3: Starting computer server in background..."
|
||||||
|
Write-Host "Starting computer server with: $pythonExe"
|
||||||
|
|
||||||
|
# Start the computer server in the background
|
||||||
|
$serverProcess = Start-Process -FilePath $pythonExe -ArgumentList "-m", "computer_server.main" -WindowStyle Hidden -PassThru
|
||||||
|
Write-Host "Computer server started in background with PID: $($serverProcess.Id)"
|
||||||
|
|
||||||
|
# Give it a moment to start
|
||||||
|
Start-Sleep -Seconds 3
|
||||||
|
|
||||||
|
# Check if the process is still running
|
||||||
|
if (Get-Process -Id $serverProcess.Id -ErrorAction SilentlyContinue) {
|
||||||
|
Write-Host "Computer server is running successfully in background"
|
||||||
|
} else {
|
||||||
|
throw "Computer server failed to start or exited immediately"
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
Write-Error "Setup failed: $_"
|
||||||
|
Write-Host "Error details: $($_.Exception.Message)"
|
||||||
|
Write-Host "Stack trace: $($_.ScriptStackTrace)"
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Press any key to close this window..."
|
||||||
|
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Setup completed successfully!"
|
||||||
|
Write-Host "Press any key to close this window..."
|
||||||
|
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||||
Reference in New Issue
Block a user