Merge pull request #622 from synacktraa/feat/qemu-linux-container

Add QEMU Ubuntu 22.04 template with CUA computer-server support
This commit is contained in:
Francesco Bonacci
2025-12-08 20:13:56 +00:00
committed by GitHub
9 changed files with 452 additions and 0 deletions

View File

@@ -0,0 +1,29 @@
name: Build and Publish CUA Linux Container
on:
push:
branches:
- main
tags:
- "docker-cua-linux-v*.*.*"
paths:
- "libs/qemu-docker/linux/**"
- ".github/workflows/docker-publish-cua-linux.yml"
- ".github/workflows/docker-reusable-publish.yml"
pull_request:
paths:
- "libs/qemu-docker/linux/**"
- ".github/workflows/docker-publish-cua-linux.yml"
- ".github/workflows/docker-reusable-publish.yml"
jobs:
publish:
uses: ./.github/workflows/docker-reusable-publish.yml
with:
image_name: cua-linux
context_dir: libs/qemu-docker/linux
dockerfile_path: Dockerfile
tag_prefix: docker-cua-linux-v
docker_hub_org: trycua
secrets:
DOCKER_HUB_TOKEN: ${{ secrets.DOCKER_HUB_TOKEN }}

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
**/image/setup.iso
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]

View File

@@ -0,0 +1,14 @@
FROM trycua/qemu-local:latest
COPY src/vm/setup/. /oem/
COPY --chmod=755 src/entry.sh /entry.sh
ENV RAM_SIZE="8G"
ENV CPU_CORES="8"
ENV DISK_SIZE="64G"
ENV ARGUMENTS="-qmp tcp:0.0.0.0:7200,server,nowait"
EXPOSE 5000 8006
ENTRYPOINT ["/entry.sh"]

View File

@@ -0,0 +1,146 @@
# CUA Linux Container
Containerized Ubuntu 22.04 LTS virtual desktop for Computer-Using Agents (CUA). Utilizes QEMU/KVM with Ubuntu Desktop and computer-server pre-installed for remote computer control.
## Features
- Ubuntu 22.04 LTS Desktop running in QEMU/KVM
- Automated installation via cloud-init autoinstall
- Pre-installed CUA computer-server for remote computer control
- Support for custom OEM scripts during setup
- noVNC access for visual desktop interaction
## Quick Start
### 1. Download Ubuntu Server ISO
**Download Ubuntu 22.04 LTS Server ISO:**
1. Visit & download the [server ISO](https://releases.ubuntu.com/22.04/ubuntu-22.04.5-live-server-amd64.iso)
2. After downloading, rename the file to `setup.iso`
3. Copy it to the directory `src/vm/image/`
This ISO is used for automated Ubuntu installation with cloud-init on first run.
### 2. Build the Image
```bash
docker build -t cua-linux:dev .
```
### 3. First Run - Create Golden Image
On first run, the container will install Ubuntu from scratch and create a golden image. This takes 15-30 minutes.
```bash
# Create storage directory
mkdir -p ./storage
# Run with ubuntu.iso to create golden image
docker run -it --rm \
--device=/dev/kvm \
--name cua-linux \
--mount type=bind,source=/path/to/ubuntu.iso,target=/custom.iso \
--cap-add NET_ADMIN \
-v $(pwd)/storage:/storage \
-p 8006:8006 \
-p 5000:5000 \
-e RAM_SIZE=8G \
-e CPU_CORES=4 \
-e DISK_SIZE=64G \
cua-linux:dev
```
**What happens during first run:**
1. Ubuntu 22.04 Server installs automatically using cloud-init autoinstall
2. Minimal desktop environment is installed with auto-login enabled
3. OEM setup scripts install Python 3, create venv, and install CUA computer-server
4. systemd service created for CUA server (runs automatically on login)
5. X11 access configured for GUI automation
6. Golden image is saved to `/storage` directory
7. Container exits after setup completes
### 4. Subsequent Runs - Use Golden Image
After the golden image is created, subsequent runs boot much faster (30 sec - 2 min):
```bash
# Run without ubuntu.iso - uses existing golden image
docker run -it --rm \
--device=/dev/kvm \
--name cua-linux \
--cap-add NET_ADMIN \
-v $(pwd)/storage:/storage \
-p 8006:8006 \
-p 5000:5000 \
-e RAM_SIZE=8G \
-e CPU_CORES=4 \
cua-linux:dev
```
**Access points:**
- **Computer Server API**: `http://localhost:5000`
- **noVNC Browser**: `http://localhost:8006`
## Container Configuration
### Ports
- **5000**: CUA computer-server API endpoint
- **8006**: noVNC web interface for visual desktop access
### Environment Variables
- `RAM_SIZE`: RAM allocated to Ubuntu VM (default: "8G", recommended: "8G" for WSL2)
- `CPU_CORES`: CPU cores allocated to VM (default: "8")
- `DISK_SIZE`: VM disk size (default: "64G", minimum: "32G")
### Volumes
- `/storage`: Persistent VM storage (golden image, disk)
- `/custom.iso`: Mount point for ubuntu.iso (only needed for first run)
- `/oem`: Optional mount point for custom OEM scripts (built-in scripts included in image)
## Architecture
```
┌─────────────────────────────────────────────────────────┐
│ Docker Container (Linux host) │
│ │
│ • Port forwarding: localhost:5000 → EMULATOR_IP:5000 │
│ • Exposes: 5000 (API), 8006 (noVNC) │
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ QEMU VM (Ubuntu 22.04) │ │
│ │ │ │
│ │ • CUA computer-server listens on 5000 │ │
│ │ │ │
│ └────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
```
**Communication Flow:**
1. External client → `localhost:5000` (host)
2. Docker port mapping → Container's `localhost:5000`
3. Container detects VM IP and waits for server to be ready
4. CUA computer-server in Ubuntu VM processes request
## Development
### Modifying Setup Scripts
Setup scripts are in `src/vm/setup/`:
- `install.sh`: Entry point called after cloud-init installation (runs OEM setup)
- `setup.sh`: Main setup orchestration (copies scripts to /opt/oem)
- `setup-cua-server.sh`: CUA server installation with isolated venv and systemd service
After modifying, rebuild the image:
```bash
docker build -t cua-linux:dev .
```

View File

@@ -0,0 +1,61 @@
#!/bin/bash
cleanup() {
echo "Received signal, shutting down gracefully..."
if [ -n "$VM_PID" ]; then
kill -TERM "$VM_PID" 2>/dev/null
wait "$VM_PID" 2>/dev/null
fi
exit 0
}
# Install trap for signals
trap cleanup SIGTERM SIGINT SIGHUP SIGQUIT
# Start the VM in the background
echo "Starting Ubuntu VM..."
/usr/bin/tini -s /run/entry.sh &
VM_PID=$!
echo "Live stream accessible at localhost:8006"
echo "Waiting for Ubuntu to boot and CUA computer-server to start..."
VM_IP=""
while true; do
# Wait for VM and get the IP
if [ -z "$VM_IP" ]; then
VM_IP=$(ps aux | grep dnsmasq | grep -oP '(?<=--dhcp-range=)[0-9.]+' | head -1)
if [ -n "$VM_IP" ]; then
echo "Detected VM IP: $VM_IP"
else
echo "Waiting for VM to start..."
sleep 5
continue
fi
fi
# Check if server is ready
response=$(curl --write-out '%{http_code}' --silent --output /dev/null $VM_IP:5000/status)
if [ "${response:-0}" -eq 200 ]; then
break
fi
echo "Waiting for CUA computer-server to be ready. This might take a while..."
sleep 5
done
echo "VM is up and running, and the CUA Computer Server is ready!"
echo "Computer server accessible at localhost:5000"
# Detect initial setup by presence of custom ISO
CUSTOM_ISO=$(find / -maxdepth 1 -type f -iname "*.iso" -print -quit 2>/dev/null || true)
if [ -n "$CUSTOM_ISO" ]; then
echo "Preparation complete. Shutting down gracefully..."
cleanup
fi
# Keep container alive for golden image boots
echo "Container running. Press Ctrl+C to stop."
tail -f /dev/null

View File

@@ -0,0 +1,7 @@
> Add your Ubuntu 22.04 live server setup.iso to this folder
**Download Ubuntu 22.04 LTS Server ISO:**
1. Visit & download the [server ISO](https://releases.ubuntu.com/22.04/ubuntu-22.04.5-live-server-amd64.iso)
2. After downloading, rename the file to `setup.iso`
3. Copy it to the current directory.

View File

@@ -0,0 +1,26 @@
#!/bin/bash
# OEM Installation Entry Point for Linux
# This script is called by the OEM systemd service on first boot
set -e
SCRIPT_DIR="/opt/oem"
LOG_FILE="$SCRIPT_DIR/setup.log"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
log "=== Starting OEM Setup ==="
# Run main setup script
if [ -f "$SCRIPT_DIR/setup.sh" ]; then
log "Running setup.sh..."
bash "$SCRIPT_DIR/setup.sh" 2>&1 | tee -a "$LOG_FILE"
log "setup.sh completed with exit code: $?"
else
log "ERROR: setup.sh not found at $SCRIPT_DIR/setup.sh"
exit 1
fi
log "=== OEM Setup Completed ==="

View File

@@ -0,0 +1,135 @@
#!/bin/bash
# Setup CUA Computer Server on Linux
# Creates a system-level systemd service to run computer server in background
set -e
USER_NAME="docker"
USER_HOME="/home/$USER_NAME"
SCRIPT_DIR="/opt/oem"
CUA_DIR="/opt/cua-server"
VENV_DIR="$CUA_DIR/venv"
SERVICE_NAME="cua-computer-server"
LOG_FILE="$SCRIPT_DIR/setup.log"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
log "=== Installing CUA Computer Server ==="
# Install Python 3 and venv
log "Installing Python 3 and dependencies..."
sudo apt-get install -y python3 python3-venv python3-pip python3-tk python3-dev
# Create CUA directory
log "Creating CUA directory at $CUA_DIR..."
sudo mkdir -p "$CUA_DIR"
sudo chown "$USER_NAME:$USER_NAME" "$CUA_DIR"
# Create virtual environment
if [ -f "$VENV_DIR/bin/python" ]; then
log "Existing venv detected; skipping creation"
else
log "Creating Python virtual environment at $VENV_DIR..."
python3 -m venv "$VENV_DIR"
log "Virtual environment created successfully"
fi
# Activate and install packages
log "Upgrading pip, setuptools, and wheel..."
"$VENV_DIR/bin/pip" install --upgrade pip setuptools wheel
log "Installing cua-computer-server..."
"$VENV_DIR/bin/pip" install --upgrade cua-computer-server
log "cua-computer-server installed successfully"
# Open firewall for port 5000 (if ufw is available)
if command -v ufw &> /dev/null; then
log "Opening firewall for port 5000..."
sudo ufw allow 5000/tcp || true
log "Firewall rule added"
fi
# Create start script with auto-restart
START_SCRIPT="$CUA_DIR/start-server.sh"
log "Creating start script at $START_SCRIPT..."
cat > "$START_SCRIPT" << 'EOF'
#!/bin/bash
# CUA Computer Server Start Script with auto-restart
CUA_DIR="/opt/cua-server"
VENV_DIR="$CUA_DIR/venv"
LOG_FILE="$CUA_DIR/server.log"
start_server() {
echo "$(date '+%Y-%m-%d %H:%M:%S') Updating cua-computer-server..." >> "$LOG_FILE"
"$VENV_DIR/bin/pip" install --upgrade cua-computer-server >> "$LOG_FILE" 2>&1
echo "$(date '+%Y-%m-%d %H:%M:%S') Starting CUA Computer Server on port 5000..." >> "$LOG_FILE"
"$VENV_DIR/bin/python" -m computer_server --port 5000 >> "$LOG_FILE" 2>&1
return $?
}
while true; do
start_server
EXIT_CODE=$?
echo "$(date '+%Y-%m-%d %H:%M:%S') Server exited with code: $EXIT_CODE. Restarting in 5s..." >> "$LOG_FILE"
sleep 5
done
EOF
chmod +x "$START_SCRIPT"
log "Start script created"
# Create xhost script for X11 access
log "Creating xhost script..."
sudo tee /etc/X11/Xsession.d/99xauth > /dev/null << 'EOF'
#!/bin/sh
# Grant local X11 access for CUA Computer Server
export DISPLAY=:0
xhost +local: 2>/dev/null || true
EOF
sudo chmod +x /etc/X11/Xsession.d/99xauth
log "X11 access script created"
# Create system-level systemd service
log "Creating systemd system service..."
sudo tee /etc/systemd/system/$SERVICE_NAME.service > /dev/null << EOF
[Unit]
Description=CUA Computer Server
After=graphical.target
[Service]
Type=simple
ExecStart=$START_SCRIPT
Restart=always
RestartSec=5
Environment=PYTHONUNBUFFERED=1
Environment=DISPLAY=:0
Environment=XAUTHORITY=$USER_HOME/.Xauthority
User=$USER_NAME
WorkingDirectory=$CUA_DIR
[Install]
WantedBy=graphical.target
EOF
log "Systemd service created at /etc/systemd/system/$SERVICE_NAME.service"
# Ensure proper ownership of CUA directory
log "Setting ownership of $CUA_DIR to $USER_NAME..."
sudo chown -R "$USER_NAME:$USER_NAME" "$CUA_DIR"
# Enable and start the service
log "Enabling systemd service..."
sudo systemctl daemon-reload
sudo systemctl enable "$SERVICE_NAME.service"
log "Starting CUA Computer Server service..."
sudo systemctl start "$SERVICE_NAME.service" || true
log "=== CUA Computer Server setup completed ==="
log "Service status: $(sudo systemctl is-active $SERVICE_NAME.service 2>/dev/null || echo 'unknown')"

View File

@@ -0,0 +1,33 @@
#!/bin/bash
# Main Setup Script for Linux
# Installs dependencies and sets up CUA Computer Server
set -e
SCRIPT_DIR="/opt/oem"
LOG_FILE="$SCRIPT_DIR/setup.log"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
log "=== Running Main Setup ==="
# Update package lists
log "Updating package lists..."
sudo apt-get update
# Install Git
log "Installing Git..."
sudo apt-get install -y git
# Setup CUA Computer Server
log "Setting up CUA Computer Server..."
if [ -f "$SCRIPT_DIR/setup-cua-server.sh" ]; then
bash "$SCRIPT_DIR/setup-cua-server.sh" 2>&1 | tee -a "$LOG_FILE"
log "CUA Computer Server setup completed."
else
log "ERROR: setup-cua-server.sh not found at $SCRIPT_DIR/setup-cua-server.sh"
fi
log "=== Main Setup Completed ==="