mirror of
https://github.com/trycua/lume.git
synced 2026-01-06 12:29:56 -06:00
Merge pull request #675 from trycua/feat/android-docker-support-&-docs
- Support for Android Docker image (`trycua/cua-qemu-android`) to the Python Computer SDK. - Docker publish workflow for publishing to registry - Adds "Android on Docker" in quickstart docs
This commit is contained in:
29
.github/workflows/docker-publish-cua-qemu-android.yml
vendored
Normal file
29
.github/workflows/docker-publish-cua-qemu-android.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
name: Build and Publish CUA Android Container
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- "docker-cua-qemu-android-v*.*.*"
|
||||
paths:
|
||||
- "libs/qemu-docker/android/**"
|
||||
- ".github/workflows/docker-publish-cua-qemu-android.yml"
|
||||
- ".github/workflows/docker-reusable-publish.yml"
|
||||
pull_request:
|
||||
paths:
|
||||
- "libs/qemu-docker/android/**"
|
||||
- ".github/workflows/docker-publish-cua-qemu-android.yml"
|
||||
- ".github/workflows/docker-reusable-publish.yml"
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
uses: ./.github/workflows/docker-reusable-publish.yml
|
||||
with:
|
||||
image_name: cua-qemu-android
|
||||
context_dir: libs/qemu-docker/android
|
||||
dockerfile_path: Dockerfile
|
||||
tag_prefix: docker-cua-qemu-android-v
|
||||
docker_hub_org: trycua
|
||||
secrets:
|
||||
DOCKER_HUB_TOKEN: ${{ secrets.DOCKER_HUB_TOKEN }}
|
||||
34
.github/workflows/docker-reusable-publish.yml
vendored
34
.github/workflows/docker-reusable-publish.yml
vendored
@@ -69,6 +69,7 @@ jobs:
|
||||
- name: Build & push digest (PR)
|
||||
if: github.event_name == 'pull_request'
|
||||
id: build-pr
|
||||
continue-on-error: true
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: ./${{ inputs.context_dir }}
|
||||
@@ -93,6 +94,7 @@ jobs:
|
||||
- name: Build & push digest (main)
|
||||
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
|
||||
id: build-main
|
||||
continue-on-error: true
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: ./${{ inputs.context_dir }}
|
||||
@@ -120,6 +122,7 @@ jobs:
|
||||
- name: Build & push digest (semver)
|
||||
if: startsWith(github.ref, format('refs/tags/{0}', inputs.tag_prefix))
|
||||
id: build-semver
|
||||
continue-on-error: true
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: ./${{ inputs.context_dir }}
|
||||
@@ -134,12 +137,23 @@ jobs:
|
||||
|
||||
- name: Export digest
|
||||
id: export-digest
|
||||
if: |
|
||||
(steps.build-pr.outcome == 'success') ||
|
||||
(steps.build-main.outcome == 'success') ||
|
||||
(steps.build-semver.outcome == 'success')
|
||||
run: |
|
||||
mkdir -p /tmp/digests
|
||||
digest="${{ steps.build-pr.outputs.digest || steps.build-main.outputs.digest || steps.build-semver.outputs.digest }}"
|
||||
echo "$digest" > "/tmp/digests/${{ steps.platform.outputs.tag }}.txt"
|
||||
if [ -n "$digest" ]; then
|
||||
echo "$digest" > "/tmp/digests/${{ steps.platform.outputs.tag }}.txt"
|
||||
echo "Digest exported for platform ${{ matrix.platform }}: $digest"
|
||||
else
|
||||
echo "No digest to export for platform ${{ matrix.platform }}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Upload digest artifact (unique per platform)
|
||||
if: steps.export-digest.outcome == 'success'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: digests-${{ steps.platform.outputs.tag }}
|
||||
@@ -150,6 +164,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build-and-push
|
||||
if: success() || failure() # Run even if some platform builds failed
|
||||
|
||||
steps:
|
||||
- name: Set up Docker Buildx
|
||||
@@ -196,6 +211,23 @@ jobs:
|
||||
path: /tmp/digests
|
||||
merge-multiple: true
|
||||
|
||||
- name: Check available digests
|
||||
id: check-digests
|
||||
run: |
|
||||
DIGEST_COUNT=$(find /tmp/digests -type f -name "*.txt" | wc -l)
|
||||
echo "digest_count=$DIGEST_COUNT" >> $GITHUB_OUTPUT
|
||||
|
||||
if [ "$DIGEST_COUNT" -eq 0 ]; then
|
||||
echo "Error: No platform builds succeeded!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Found $DIGEST_COUNT platform digest(s)"
|
||||
for f in $(find /tmp/digests -type f -name "*.txt"); do
|
||||
platform=$(basename "$f" .txt)
|
||||
echo " - $platform"
|
||||
done
|
||||
|
||||
- name: Create & push multi-arch manifest
|
||||
run: |
|
||||
IMAGE="${{ inputs.docker_hub_org }}/${{ inputs.image_name }}"
|
||||
|
||||
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@@ -23,8 +23,6 @@ jobs:
|
||||
|
||||
- name: Set up pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
|
||||
@@ -7,6 +7,7 @@ This guide covers setting up and developing the CUA (Computer Use Agent) monorep
|
||||
The project is organized as a monorepo with these main packages:
|
||||
|
||||
**Python Packages** (located in `libs/python/`):
|
||||
|
||||
- `core/` - Base package with telemetry support
|
||||
- `computer/` - Computer-use interface (CUI) library
|
||||
- `agent/` - AI agent library with multi-provider support
|
||||
@@ -16,6 +17,7 @@ The project is organized as a monorepo with these main packages:
|
||||
- `bench-ui/` - Benchmark UI utilities
|
||||
|
||||
**Other Packages**:
|
||||
|
||||
- `libs/lume/` - Lume CLI (Swift)
|
||||
- `libs/typescript/` - TypeScript packages including `cua-cli`
|
||||
|
||||
@@ -24,39 +26,45 @@ All Python packages are part of a [uv workspace](https://docs.astral.sh/uv/works
|
||||
## Quick Start
|
||||
|
||||
1. **Install Lume CLI** (required for local VM management):
|
||||
|
||||
```bash
|
||||
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/trycua/cua/main/libs/lume/scripts/install.sh)"
|
||||
```
|
||||
|
||||
2. **Clone the repository**:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/trycua/cua.git
|
||||
cd cua
|
||||
```
|
||||
|
||||
3. **Create `.env.local`** in the root directory with your API keys:
|
||||
|
||||
```bash
|
||||
ANTHROPIC_API_KEY=your_anthropic_key_here
|
||||
OPENAI_API_KEY=your_openai_key_here
|
||||
```
|
||||
|
||||
4. **Install Node.js dependencies**:
|
||||
|
||||
```bash
|
||||
npm install -g pnpm # if not already installed
|
||||
pnpm install
|
||||
```
|
||||
|
||||
5. **Install Python dependencies**:
|
||||
|
||||
```bash
|
||||
pip install uv # if not already installed
|
||||
uv sync
|
||||
```
|
||||
|
||||
6. **Open workspace in VS Code/Cursor**:
|
||||
|
||||
```bash
|
||||
# For Python development
|
||||
code .vscode/py.code-workspace
|
||||
|
||||
|
||||
# For Lume (Swift) development
|
||||
code .vscode/lume.code-workspace
|
||||
```
|
||||
@@ -159,6 +167,7 @@ Formatting configuration is defined in [`pyproject.toml`](./pyproject.toml). See
|
||||
##### Python-specific settings
|
||||
|
||||
Python-specific IDE settings are configured in [`.vscode/settings.json`](.vscode/settings.json), including:
|
||||
|
||||
- Python interpreter path
|
||||
- Format on save
|
||||
- Code actions on save
|
||||
@@ -250,11 +259,13 @@ pnpm install
|
||||
#### Usage
|
||||
|
||||
- **Check formatting** (without making changes):
|
||||
|
||||
```bash
|
||||
pnpm prettier:check
|
||||
```
|
||||
|
||||
- **Automatically format files**:
|
||||
|
||||
```bash
|
||||
pnpm prettier:format
|
||||
```
|
||||
@@ -342,6 +353,7 @@ To release a new version of the CUA CLI (`@trycua/cli`):
|
||||
5. Click "Run workflow"
|
||||
|
||||
The workflow will:
|
||||
|
||||
- Build single-file executables for all supported platforms
|
||||
- Publish the package to npm
|
||||
- Create a GitHub release with the version tag (format: `cua-vX.Y.Z`)
|
||||
@@ -367,6 +379,7 @@ The workflow will:
|
||||
### 4. Update Documentation
|
||||
|
||||
Update any relevant documentation with the new version number, including:
|
||||
|
||||
- Example code in documentation
|
||||
- Any version-specific instructions
|
||||
- Compatibility matrices
|
||||
|
||||
@@ -4,7 +4,11 @@ description: Supported computer-using agent loops and models
|
||||
---
|
||||
|
||||
<Callout>
|
||||
A corresponding <a href="https://github.com/trycua/cua/blob/main/notebooks/agent_nb.ipynb" target="_blank">Jupyter Notebook</a> is available for this documentation.
|
||||
A corresponding{' '}
|
||||
<a href="https://github.com/trycua/cua/blob/main/notebooks/agent_nb.ipynb" target="_blank">
|
||||
Jupyter Notebook
|
||||
</a>{' '}
|
||||
is available for this documentation.
|
||||
</Callout>
|
||||
|
||||
An agent can be thought of as a loop - it generates actions, executes them, and repeats until done:
|
||||
|
||||
@@ -3,7 +3,14 @@ title: Customize ComputerAgent
|
||||
---
|
||||
|
||||
<Callout>
|
||||
A corresponding <a href="https://github.com/trycua/cua/blob/main/notebooks/customizing_computeragent.ipynb" target="_blank">Jupyter Notebook</a> is available for this documentation.
|
||||
A corresponding{' '}
|
||||
<a
|
||||
href="https://github.com/trycua/cua/blob/main/notebooks/customizing_computeragent.ipynb"
|
||||
target="_blank"
|
||||
>
|
||||
Jupyter Notebook
|
||||
</a>{' '}
|
||||
is available for this documentation.
|
||||
</Callout>
|
||||
|
||||
The `ComputerAgent` interface provides an easy proxy to any computer-using model configuration, and it is a powerful framework for extending and building your own agentic systems.
|
||||
|
||||
@@ -4,7 +4,11 @@ description: Use ComputerAgent with HUD for benchmarking and evaluation
|
||||
---
|
||||
|
||||
<Callout>
|
||||
A corresponding <a href="https://github.com/trycua/cua/blob/main/notebooks/eval_osworld.ipynb" target="_blank">Jupyter Notebook</a> is available for this documentation.
|
||||
A corresponding{' '}
|
||||
<a href="https://github.com/trycua/cua/blob/main/notebooks/eval_osworld.ipynb" target="_blank">
|
||||
Jupyter Notebook
|
||||
</a>{' '}
|
||||
is available for this documentation.
|
||||
</Callout>
|
||||
|
||||
The HUD integration allows an agent to be benchmarked using the [HUD framework](https://www.hud.so/). Through the HUD integration, the agent controls a computer inside HUD, where tests are run to evaluate the success of each task.
|
||||
|
||||
@@ -11,14 +11,9 @@ The Cua CLI is a command-line tool for managing your Cua cloud sandboxes. Create
|
||||
## Installation
|
||||
|
||||
<Tabs items={['macOS / Linux', 'Windows']}>
|
||||
<Tab value="macOS / Linux">
|
||||
```bash
|
||||
curl -LsSf https://cua.ai/cli/install.sh | sh
|
||||
```
|
||||
</Tab>
|
||||
<Tab value="macOS / Linux">```bash curl -LsSf https://cua.ai/cli/install.sh | sh ```</Tab>
|
||||
<Tab value="Windows">
|
||||
```powershell
|
||||
powershell -ExecutionPolicy ByPass -c "irm https://cua.ai/cli/install.ps1 | iex"
|
||||
```powershell powershell -ExecutionPolicy ByPass -c "irm https://cua.ai/cli/install.ps1 | iex"
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
@@ -4,7 +4,14 @@ slug: sandboxed-python
|
||||
---
|
||||
|
||||
<Callout>
|
||||
A corresponding <a href="https://github.com/trycua/cua/blob/main/examples/sandboxed_functions_examples.py" target="_blank">Python example</a> is available for this documentation.
|
||||
A corresponding{' '}
|
||||
<a
|
||||
href="https://github.com/trycua/cua/blob/main/examples/sandboxed_functions_examples.py"
|
||||
target="_blank"
|
||||
>
|
||||
Python example
|
||||
</a>{' '}
|
||||
is available for this documentation.
|
||||
</Callout>
|
||||
|
||||
You can run Python functions securely inside a sandboxed virtual environment on a remote Cua Computer Framework. This is useful for executing untrusted user code, isolating dependencies, or providing a safe environment for automation tasks.
|
||||
|
||||
@@ -86,7 +86,8 @@ CUA_API_KEY=sk_cua-api01...
|
||||
CUA_CONTAINER_NAME=m-linux-...
|
||||
```
|
||||
|
||||
Finally, setup your sandbox. Refer to the [quickstart guide](https://cua.ai/docs/get-started/quickstart) on how to setup the computer environment.
|
||||
Finally, setup your sandbox. Refer to the [quickstart guide](https://cua.ai/docs/get-started/quickstart) on how to setup the computer environment.
|
||||
|
||||
</Step>
|
||||
|
||||
<Step>
|
||||
|
||||
@@ -88,13 +88,13 @@ You can run your Cua sandbox in the cloud (recommended for easiest setup), local
|
||||
</Tab>
|
||||
<Tab value="QEMU Docker">
|
||||
|
||||
Run full Linux (Ubuntu Desktop) or Windows 11 VMs inside Docker containers using QEMU virtualization.
|
||||
Run full Linux (Ubuntu Desktop), Windows 11, or Android 11 VMs inside Docker containers using QEMU virtualization.
|
||||
|
||||
<Callout type="warn">
|
||||
QEMU images require a **golden image preparation step** on first use. This involves downloading an ISO and running a one-time installation.
|
||||
Linux and Windows images require a **golden image preparation step** on first use. Android images start directly without preparation.
|
||||
</Callout>
|
||||
|
||||
<Tabs items={['Linux', 'Windows']}>
|
||||
<Tabs items={['Linux', 'Windows', 'Android']}>
|
||||
<Tab value="Linux">
|
||||
|
||||
**1. Install Docker Desktop or Docker Engine**
|
||||
@@ -163,6 +163,19 @@ You can run your Cua sandbox in the cloud (recommended for easiest setup), local
|
||||
The container will install Windows 11 from the ISO and shut down when complete. Monitor progress at [http://localhost:8006](http://localhost:8006).
|
||||
|
||||
</Tab>
|
||||
<Tab value="Android">
|
||||
|
||||
**1. Install Docker Desktop or Docker Engine**
|
||||
|
||||
**2. Pull the QEMU Android image:**
|
||||
|
||||
```bash
|
||||
docker pull trycua/cua-qemu-android:latest
|
||||
```
|
||||
|
||||
No golden image preparation needed - the Android emulator starts directly when you run it!
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
</Tab>
|
||||
@@ -306,7 +319,7 @@ Install Cua Computer Framework and verify your sandbox is working by performing
|
||||
```
|
||||
</Tab>
|
||||
<Tab value="QEMU Docker">
|
||||
<Tabs items={['Linux', 'Windows']}>
|
||||
<Tabs items={['Linux', 'Windows', 'Android']}>
|
||||
<Tab value="Linux">
|
||||
```python
|
||||
from computer import Computer
|
||||
@@ -377,6 +390,40 @@ Install Cua Computer Framework and verify your sandbox is working by performing
|
||||
asyncio.run(main())
|
||||
```
|
||||
</Tab>
|
||||
<Tab value="Android">
|
||||
```python
|
||||
from computer import Computer
|
||||
import asyncio
|
||||
|
||||
computer = Computer(
|
||||
os_type="android",
|
||||
provider_type="docker",
|
||||
image="trycua/cua-qemu-android:latest",
|
||||
timeout=150, # Emulator needs more time to boot
|
||||
run_opts={
|
||||
"devices": ["/dev/kvm"], # Required for Android emulator
|
||||
"env": {
|
||||
"EMULATOR_DEVICE": "Samsung Galaxy S10",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
async def main():
|
||||
await computer.run() # Launch & connect to Android emulator
|
||||
|
||||
try:
|
||||
# Take a screenshot of the Android screen
|
||||
screenshot = await computer.interface.screenshot()
|
||||
# Simulate a left-click at coordinates (100, 100)
|
||||
await computer.interface.left_click(100, 100)
|
||||
# Type "Hello!" into the active application
|
||||
await computer.interface.type_text("Hello!")
|
||||
finally:
|
||||
await computer.disconnect()
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</Tab>
|
||||
<Tab value="macOS Sandbox">
|
||||
|
||||
@@ -26,10 +26,12 @@ Computer-Use Sandboxes are isolated, controlled environments where AI agents can
|
||||
## Key Features
|
||||
|
||||
With **Cua Computer Framework**, you can:
|
||||
|
||||
- Automate **Windows, Linux, and macOS** sandboxes with a consistent, pyautogui-like API
|
||||
- Create & manage sandboxes locally or using **Cua Cloud**
|
||||
|
||||
With **Cua Agent Framework**, you can:
|
||||
|
||||
- Run computer-use models with a consistent schema
|
||||
- Benchmark on **OSWorld-Verified**, **SheetBench-V2**, and **ScreenSpot**
|
||||
- Combine UI grounding models with any LLM using **composed agents**
|
||||
@@ -44,6 +46,14 @@ Check out our [tutorials](https://cua.ai/blog), [examples](https://github.com/tr
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-2 mt-4 text-sm">
|
||||
<Card icon={<Rocket className="w-4 h-4" />} href="/get-started/quickstart" title="Quickstart" />
|
||||
<Card icon={<Zap className="w-4 h-4" />} href="/agent-sdk/agent-loops" title="Agent Loops" />
|
||||
<Card icon={<BookOpen className="w-4 h-4" />} href="/computer-sdk/computers" title="Cua Computer" />
|
||||
<Card icon={<Monitor className="w-4 h-4" />} href="/example-usecases/form-filling" title="Examples" />
|
||||
<Card
|
||||
icon={<BookOpen className="w-4 h-4" />}
|
||||
href="/computer-sdk/computers"
|
||||
title="Cua Computer"
|
||||
/>
|
||||
<Card
|
||||
icon={<Monitor className="w-4 h-4" />}
|
||||
href="/example-usecases/form-filling"
|
||||
title="Examples"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -34,7 +34,9 @@ export const baseOptions: BaseLayoutProps = {
|
||||
className="hidden dark:block"
|
||||
alt="Logo"
|
||||
/>
|
||||
<span className="text-lg font-bold" style={{ fontFamily: 'var(--font-urbanist)' }}>Cua</span>
|
||||
<span className="text-lg font-bold" style={{ fontFamily: 'var(--font-urbanist)' }}>
|
||||
Cua
|
||||
</span>
|
||||
</div>
|
||||
),
|
||||
url: 'https://cua.ai',
|
||||
|
||||
@@ -157,4 +157,4 @@ export function Footer() {
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
from __future__ import annotations
|
||||
import time
|
||||
from bench_ui import launch_window, get_element_rect, execute_javascript
|
||||
from pathlib import Path
|
||||
|
||||
import os
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
from bench_ui import execute_javascript, get_element_rect, launch_window
|
||||
|
||||
|
||||
def main():
|
||||
os.environ["CUA_BENCH_UI_DEBUG"] = "1"
|
||||
|
||||
# Get the path to the gui folder
|
||||
gui_folder = Path(__file__).parent / "gui"
|
||||
|
||||
|
||||
# Launch a window serving the static folder
|
||||
pid = launch_window(
|
||||
folder=str(gui_folder),
|
||||
@@ -30,7 +33,7 @@ def main():
|
||||
# Check if button has been clicked
|
||||
clicked = execute_javascript(pid, "document.getElementById('testButton').disabled")
|
||||
print("Button clicked:", clicked)
|
||||
|
||||
|
||||
# Get the page title
|
||||
title = execute_javascript(pid, "document.title")
|
||||
print("Page title:", title)
|
||||
|
||||
@@ -51,7 +51,7 @@ SYSTEM_INFO = {
|
||||
from .providers.base import VMProviderType
|
||||
from .providers.factory import VMProviderFactory
|
||||
|
||||
OSType = Literal["macos", "linux", "windows"]
|
||||
OSType = Literal["macos", "linux", "windows", "android"]
|
||||
|
||||
|
||||
class Computer:
|
||||
|
||||
@@ -59,6 +59,7 @@ class DockerProvider(BaseVMProvider):
|
||||
- "trycua/cua-xfce:latest" (vanilla XFCE)
|
||||
- "trycua/cua-qemu-linux:latest" (QEMU-based, only supports Ubuntu for now)
|
||||
- "trycua/cua-qemu-windows:latest" (QEMU-based, only supports Windows 11 Enterprise for now)
|
||||
- "trycua/cua-qemu-android:latest" (QEMU-based Android 11 emulator)
|
||||
verbose: Enable verbose logging
|
||||
ephemeral: Use ephemeral (temporary) storage
|
||||
vnc_port: Port for VNC interface (default: 6901)
|
||||
@@ -78,10 +79,11 @@ class DockerProvider(BaseVMProvider):
|
||||
# Detect image type and configure user directory accordingly
|
||||
self._detect_image_config()
|
||||
|
||||
if self._image_type == "qemu":
|
||||
# QEMU Linux/Windows images require golden image storage
|
||||
if self._image_type in ("qemu-linux", "qemu-windows"):
|
||||
if not storage or Path(storage).is_dir() is False:
|
||||
raise ValueError(
|
||||
"Golden image storage path must be provided for QEMU-based images."
|
||||
"Golden image storage path must be provided for QEMU-based Linux/Windows images."
|
||||
)
|
||||
self.storage = storage
|
||||
elif ephemeral:
|
||||
@@ -91,16 +93,26 @@ class DockerProvider(BaseVMProvider):
|
||||
|
||||
def _detect_image_config(self):
|
||||
"""Detect image type and configure paths accordingly."""
|
||||
# Detect if this is a XFCE, Kasm or QEMU image
|
||||
# Detect if this is a XFCE, Kasm, or QEMU-based 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 ("qemu-linux", "qemu-windows")):
|
||||
# QEMU-based images
|
||||
elif "qemu-linux" in self.image.lower():
|
||||
# QEMU-based Linux images
|
||||
self._home_dir = ""
|
||||
self._image_type = "qemu"
|
||||
logger.info("Detected QEMU image: using /")
|
||||
self._image_type = "qemu-linux"
|
||||
logger.info("Detected QEMU Linux image: using /")
|
||||
elif "qemu-windows" in self.image.lower():
|
||||
# QEMU-based Windows images
|
||||
self._home_dir = ""
|
||||
self._image_type = "qemu-windows"
|
||||
logger.info("Detected QEMU Windows image: using /")
|
||||
elif "qemu-android" in self.image.lower():
|
||||
# QEMU-based Android images
|
||||
self._home_dir = "/home/androidusr"
|
||||
self._image_type = "qemu-android"
|
||||
logger.info("Detected QEMU Android image: using /home/androidusr")
|
||||
else:
|
||||
# Default to Kasm configuration
|
||||
self._home_dir = "/home/kasm-user"
|
||||
@@ -292,44 +304,58 @@ class DockerProvider(BaseVMProvider):
|
||||
# Build docker run command
|
||||
cmd = ["docker", "run", "-d", "--name", name]
|
||||
|
||||
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 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
|
||||
# Add devices if specified (e.g., /dev/kvm for QEMU and Android)
|
||||
has_kvm = False
|
||||
if "devices" in run_opts:
|
||||
devices = run_opts["devices"]
|
||||
warn_no_kvm = self._image_type == "qemu" and "/dev/kvm" not in devices
|
||||
has_kvm = "/dev/kvm" 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"
|
||||
has_kvm = run_opts["device"] == "/dev/kvm"
|
||||
cmd.extend(["--device", run_opts["device"]])
|
||||
|
||||
if warn_no_kvm:
|
||||
# QEMU Android requires /dev/kvm
|
||||
if self._image_type == "qemu-android" and not has_kvm:
|
||||
raise ValueError(
|
||||
"Android images requires KVM. "
|
||||
'Add run_opts={"devices": ["/dev/kvm"]} to your Computer configuration.'
|
||||
)
|
||||
|
||||
if self._image_type in ("qemu-linux", "qemu-windows") and not has_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":
|
||||
# Add NET_ADMIN capability for QEMU Linux/Windows images
|
||||
if self._image_type in ("qemu-linux", "qemu-windows"):
|
||||
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
|
||||
# Port mappings differ by image type
|
||||
if self._image_type in ("qemu-linux", "qemu-windows"):
|
||||
internal_vnc_port = 8006
|
||||
internal_api_port = 5000
|
||||
elif self._image_type == "qemu-android":
|
||||
internal_vnc_port = 6080
|
||||
internal_api_port = 8000
|
||||
else:
|
||||
# Kasm/XFCE images
|
||||
internal_vnc_port = 6901
|
||||
internal_api_port = 8000
|
||||
|
||||
if vnc_port:
|
||||
cmd.extend(["-p", f"{vnc_port}:{internal_vnc_port}"]) # VNC port
|
||||
@@ -351,6 +377,13 @@ class DockerProvider(BaseVMProvider):
|
||||
cmd.extend(["-e", "VNC_PW=password"]) # Set VNC password
|
||||
cmd.extend(["-e", "VNCOPTIONS=-disableBasicAuth"]) # Disable VNC basic auth
|
||||
|
||||
# Add Android-specific default environment variables
|
||||
if self._image_type == "qemu-android":
|
||||
if "env" not in run_opts:
|
||||
run_opts["env"] = {}
|
||||
run_opts["env"].setdefault("EMULATOR_DEVICE", "Samsung Galaxy S10")
|
||||
run_opts["env"]["WEB_VNC"] = "true"
|
||||
|
||||
# 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():
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# ============================================================================
|
||||
FROM eclipse-temurin:17-jdk AS builder
|
||||
|
||||
RUN apt-get update && apt-get install -y wget unzip && \
|
||||
RUN apt-get update && apt-get install -y wget unzip dos2unix && \
|
||||
mkdir -p /opt/android-sdk/cmdline-tools && \
|
||||
cd /opt/android-sdk/cmdline-tools && \
|
||||
wget -q https://dl.google.com/android/repository/commandlinetools-linux-9477386_latest.zip && \
|
||||
@@ -17,11 +17,12 @@ ENV PATH="${ANDROID_HOME}/cmdline-tools/latest/bin:${ANDROID_HOME}/platform-tool
|
||||
RUN yes | sdkmanager --licenses && \
|
||||
sdkmanager "platforms;android-30" "build-tools;30.0.3"
|
||||
|
||||
COPY wallpaper-manager /build/wallpaper-manager
|
||||
COPY src/wallpaper-manager /build/wallpaper-manager
|
||||
WORKDIR /build/wallpaper-manager
|
||||
|
||||
RUN curl -fsSL -o gradle/wrapper/gradle-wrapper.jar \
|
||||
https://raw.githubusercontent.com/gradle/gradle/v7.6.0/gradle/wrapper/gradle-wrapper.jar && \
|
||||
dos2unix gradlew && \
|
||||
chmod +x gradlew
|
||||
|
||||
RUN ./gradlew assembleDebug --no-daemon
|
||||
@@ -56,7 +57,7 @@ ENV PATH="/opt/venv/bin:$PATH"
|
||||
RUN /opt/venv/bin/pip install --no-cache-dir --upgrade pip && \
|
||||
/opt/venv/bin/pip install --no-cache-dir cua-computer-server
|
||||
|
||||
COPY entry.sh /usr/local/bin/entry.sh
|
||||
COPY src/entry.sh /usr/local/bin/entry.sh
|
||||
RUN chmod +x /usr/local/bin/entry.sh
|
||||
|
||||
# Make venv accessible to androidusr
|
||||
@@ -1,6 +1,6 @@
|
||||
# Android Docker
|
||||
# QEMU Android
|
||||
|
||||
Docker image that runs an Android emulator with CUA Computer Server integration, enabling programmatic control of Android devices via HTTP API.
|
||||
Docker image that runs an Android emulator using QEMU/KVM with CUA Computer Server integration, enabling programmatic control of Android devices via HTTP API.
|
||||
|
||||
## Features
|
||||
|
||||
@@ -21,27 +21,26 @@ Docker image that runs an Android emulator with CUA Computer Server integration,
|
||||
### Production Build
|
||||
|
||||
```bash
|
||||
cd android-docker
|
||||
docker build -t trycua/cua-droid .
|
||||
docker build -t trycua/cua-qemu-android .
|
||||
docker run -d -p 6080:6080 -p 8000:8000 \
|
||||
-e EMULATOR_DEVICE="Samsung Galaxy S10" \
|
||||
-e WEB_VNC=true \
|
||||
--device /dev/kvm \
|
||||
--name android-container \
|
||||
trycua/cua-droid
|
||||
trycua/cua-qemu-android
|
||||
```
|
||||
|
||||
### Development Build
|
||||
|
||||
```bash
|
||||
cd .. # Go to libs/ directory
|
||||
docker build -f android-docker/dev.Dockerfile -t trycua/cua-droid:dev .
|
||||
cd ../.. # Go to libs/ directory
|
||||
docker build -f qemu-docker/android/dev.Dockerfile -t trycua/cua-qemu-android:dev .
|
||||
docker run -d -p 6080:6080 -p 8000:8000 \
|
||||
-e EMULATOR_DEVICE="Samsung Galaxy S10" \
|
||||
-e WEB_VNC=true \
|
||||
--device /dev/kvm \
|
||||
--name android-container \
|
||||
trycua/cua-droid:dev
|
||||
trycua/cua-qemu-android:dev
|
||||
```
|
||||
|
||||
## Access Points
|
||||
@@ -1,6 +1,6 @@
|
||||
# ============================================================================
|
||||
# Development Dockerfile - builds from libs/ directory with local sources
|
||||
# Build command: docker build -f android-docker/dev.Dockerfile -t android-cua:dev .
|
||||
# Build command: docker build -f qemu-docker/android/dev.Dockerfile -t cua-qemu-android:dev .
|
||||
# ============================================================================
|
||||
|
||||
# ============================================================================
|
||||
@@ -8,7 +8,7 @@
|
||||
# ============================================================================
|
||||
FROM eclipse-temurin:17-jdk AS builder
|
||||
|
||||
RUN apt-get update && apt-get install -y wget unzip && \
|
||||
RUN apt-get update && apt-get install -y wget unzip dos2unix && \
|
||||
mkdir -p /opt/android-sdk/cmdline-tools && \
|
||||
cd /opt/android-sdk/cmdline-tools && \
|
||||
wget -q https://dl.google.com/android/repository/commandlinetools-linux-9477386_latest.zip && \
|
||||
@@ -22,11 +22,12 @@ ENV PATH="${ANDROID_HOME}/cmdline-tools/latest/bin:${ANDROID_HOME}/platform-tool
|
||||
RUN yes | sdkmanager --licenses && \
|
||||
sdkmanager "platforms;android-30" "build-tools;30.0.3"
|
||||
|
||||
COPY android-docker/wallpaper-manager /build/wallpaper-manager
|
||||
COPY qemu-docker/android/src/wallpaper-manager /build/wallpaper-manager
|
||||
WORKDIR /build/wallpaper-manager
|
||||
|
||||
RUN curl -fsSL -o gradle/wrapper/gradle-wrapper.jar \
|
||||
https://raw.githubusercontent.com/gradle/gradle/v7.6.0/gradle/wrapper/gradle-wrapper.jar && \
|
||||
dos2unix gradlew && \
|
||||
chmod +x gradlew
|
||||
|
||||
RUN ./gradlew assembleDebug --no-daemon
|
||||
@@ -64,7 +65,7 @@ WORKDIR /tmp/computer-server
|
||||
RUN /opt/venv/bin/pip install --no-cache-dir --upgrade pip && \
|
||||
/opt/venv/bin/pip install --no-cache-dir -e .
|
||||
|
||||
COPY android-docker/entry.sh /usr/local/bin/entry.sh
|
||||
COPY qemu-docker/android/src/entry.sh /usr/local/bin/entry.sh
|
||||
RUN chmod +x /usr/local/bin/entry.sh
|
||||
|
||||
# Make venv accessible to androidusr
|
||||
@@ -52,4 +52,4 @@
|
||||
"typescript": "^5.8.3",
|
||||
"vitest": "^4.0.14"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,5 +5,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "^3.6.2"
|
||||
}
|
||||
},
|
||||
"packageManager": "pnpm@9.0.4+sha1.b198ac6d38244fd829253720f9daafd6a606834d"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user