feat(docker): add QEMU image support

- Detect QEMU images (cua-linux, cua-windows) and configure accordingly
- Validate golden image storage path for QEMU images
- Add /dev/kvm device support with warning if not provided
- Add NET_ADMIN capability for QEMU networking
- Map correct ports (8006 VNC, 5000 API) for QEMU vs Kasm/XFCE
- Support custom environment variables via run_opts (RAM_SIZE, CPU_CORES, DISK_SIZE)
- Skip Docker memory/CPU limits for QEMU (uses env vars instead)
This commit is contained in:
synacktra.work@gmail.com
2025-12-19 14:23:14 +05:30
parent 7b7c75293f
commit eb04b5b023
@@ -12,6 +12,7 @@ import logging
import re
import subprocess
import time
from pathlib import Path
from typing import Any, Dict, List, Optional
from ..base import BaseVMProvider, VMProviderType
@@ -56,6 +57,8 @@ class DockerProvider(BaseVMProvider):
Supported images:
- "trycua/cua-ubuntu:latest" (Kasm-based)
- "trycua/cua-xfce:latest" (vanilla XFCE)
- "trycua/cua-linux:latest" (QEMU-based, only supports Ubuntu for now)
- "trycua/cua-windows:latest" (QEMU-based, only supports Windows 11 Enterprise for now)
verbose: Enable verbose logging
ephemeral: Use ephemeral (temporary) storage
vnc_port: Port for VNC interface (default: 6901)
@@ -66,12 +69,6 @@ class DockerProvider(BaseVMProvider):
self.vnc_port = vnc_port
self.ephemeral = ephemeral
# Handle ephemeral storage (temporary directory)
if ephemeral:
self.storage = "ephemeral"
else:
self.storage = storage
self.shared_path = shared_path
self.image = image
self.verbose = verbose
@@ -81,13 +78,32 @@ class DockerProvider(BaseVMProvider):
# Detect image type and configure user directory accordingly
self._detect_image_config()
if self._image_type == "qemu":
if not storage or Path(storage).is_dir() is False:
raise ValueError(
"Golden image storage path must be provided for QEMU-based images."
)
self.storage = storage
elif ephemeral:
self.storage = "ephemeral" # Handle ephemeral storage (temporary directory)
else:
self.storage = storage
def _detect_image_config(self):
"""Detect image type and configure paths accordingly."""
# Detect if this is a docker-xfce image or Kasm image
# Detect if this is a XFCE, Kasm or QEMU image
if "docker-xfce" in self.image.lower() or "xfce" in self.image.lower():
self._home_dir = "/home/cua"
self._image_type = "docker-xfce"
logger.info(f"Detected docker-xfce image: using {self._home_dir}")
elif any(
x in self.image.lower()
for x in ("cua-linux", "cua-windows", "qemu-linux", "qemu-windows")
):
# QEMU-based images
self._home_dir = ""
self._image_type = "qemu"
logger.info("Detected QEMU image: using /")
else:
# Default to Kasm configuration
self._home_dir = "/home/kasm-user"
@@ -279,25 +295,49 @@ class DockerProvider(BaseVMProvider):
# Build docker run command
cmd = ["docker", "run", "-d", "--name", name]
# Add memory limit if specified
if "memory" in run_opts:
memory_limit = self._parse_memory(run_opts["memory"])
cmd.extend(["--memory", memory_limit])
if self._image_type != "qemu":
# Add memory limit if specified
if "memory" in run_opts:
memory_limit = self._parse_memory(run_opts["memory"])
cmd.extend(["--memory", memory_limit])
# Add CPU limit if specified
if "cpu" in run_opts:
cpu_count = str(run_opts["cpu"])
cmd.extend(["--cpus", cpu_count])
# Add CPU limit if specified
if "cpu" in run_opts:
cpu_count = str(run_opts["cpu"])
cmd.extend(["--cpus", cpu_count])
# Add devices if specified (e.g., /dev/kvm for QEMU)
warn_no_kvm = False
if "devices" in run_opts:
devices = run_opts["devices"]
warn_no_kvm = self._image_type == "qemu" and "/dev/kvm" not in devices
for device in devices:
cmd.extend(["--device", device])
elif "device" in run_opts:
warn_no_kvm = self._image_type == "qemu" and run_opts["device"] != "/dev/kvm"
cmd.extend(["--device", run_opts["device"]])
if warn_no_kvm:
logger.warning(
"/dev/kvm device is recommended for QEMU images for better performance."
)
# Add NET_ADMIN capability for QEMU images
if self._image_type == "qemu":
cmd.extend(["--cap-add", "NET_ADMIN"])
# Add port mappings
vnc_port = run_opts.get("vnc_port", self.vnc_port)
api_port = run_opts.get("api_port", self.api_port)
# Port mappings differ for QEMU vs Kasm/XFCE images
internal_vnc_port = 8006 if self._image_type == "qemu" else 6901
internal_api_port = 5000 if self._image_type == "qemu" else 8000
if vnc_port:
cmd.extend(["-p", f"{vnc_port}:6901"]) # VNC port
cmd.extend(["-p", f"{vnc_port}:{internal_vnc_port}"]) # VNC port
if api_port:
# Map the API port to container port 8000 (computer-server default)
cmd.extend(["-p", f"{api_port}:8000"]) # computer-server API port
cmd.extend(["-p", f"{api_port}:{internal_api_port}"]) # computer-server API port
# Add volume mounts if storage is specified
storage_path = storage or self.storage
@@ -314,6 +354,11 @@ class DockerProvider(BaseVMProvider):
cmd.extend(["-e", "VNC_PW=password"]) # Set VNC password
cmd.extend(["-e", "VNCOPTIONS=-disableBasicAuth"]) # Disable VNC basic auth
# Add custom environment variables from run_opts
if "env" in run_opts and isinstance(run_opts["env"], dict):
for key, value in run_opts["env"].items():
cmd.extend(["-e", f"{key}={value}"])
# Apply display resolution if provided (e.g., "1024x768")
display_resolution = run_opts.get("display")
if (