| """Launch VisualWebArena / WebArena Docker containers for PoisonClaw. |
| |
| Usage: |
| # Start VisualWebArena on port 9999 |
| python scripts/launch_env.py --env visualwebarena --port 9999 |
| |
| # Start WebArena on port 8080 |
| python scripts/launch_env.py --env webarena --port 8080 |
| |
| # Stop all running PoisonClaw containers |
| python scripts/launch_env.py --stop_all |
| """ |
|
|
| import argparse |
| import logging |
| import subprocess |
| import sys |
|
|
| logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s") |
| logger = logging.getLogger("poisonclaw.launch_env") |
|
|
| |
| _DOCKER_IMAGES = { |
| "visualwebarena": { |
| "image": "ghcr.io/web-arena-x/visualwebarena:latest", |
| "services": { |
| "reddit": 9999, |
| "shopping": 7770, |
| "classifieds": 9980, |
| }, |
| "docs": "https://github.com/web-arena-x/visualwebarena", |
| }, |
| "webarena": { |
| "image": "ghcr.io/web-arena-x/webarena:latest", |
| "services": { |
| "gitlab": 8023, |
| "reddit": 9999, |
| "shopping": 7770, |
| "map": 3000, |
| }, |
| "docs": "https://github.com/web-arena-x/webarena", |
| }, |
| } |
|
|
|
|
| def parse_args() -> argparse.Namespace: |
| parser = argparse.ArgumentParser(description="Launch web environment Docker containers") |
| parser.add_argument( |
| "--env", |
| choices=list(_DOCKER_IMAGES.keys()), |
| help="Environment to launch", |
| ) |
| parser.add_argument( |
| "--port", |
| type=int, |
| default=None, |
| help="Host port for the main service", |
| ) |
| parser.add_argument( |
| "--stop_all", |
| action="store_true", |
| help="Stop all running PoisonClaw environment containers", |
| ) |
| parser.add_argument( |
| "--status", |
| action="store_true", |
| help="Show status of running containers", |
| ) |
| return parser.parse_args() |
|
|
|
|
| def check_docker() -> bool: |
| """Check if Docker is available. |
| |
| Returns: |
| True if Docker is installed and running. |
| """ |
| try: |
| result = subprocess.run( |
| ["docker", "info"], |
| capture_output=True, |
| check=True, |
| timeout=10, |
| ) |
| return True |
| except (subprocess.CalledProcessError, FileNotFoundError, subprocess.TimeoutExpired): |
| logger.error( |
| "Docker is not available. Please install Docker to use web environments.\n" |
| "For local development without Docker, use stub environments by not setting " |
| "env.vwa_task_file in your config." |
| ) |
| return False |
|
|
|
|
| def launch_env(env_name: str, port: int) -> None: |
| """Launch the specified environment Docker container. |
| |
| Args: |
| env_name: Environment name (``"visualwebarena"`` or ``"webarena"``). |
| port: Host port for the main service. |
| """ |
| if not check_docker(): |
| sys.exit(1) |
|
|
| env_info = _DOCKER_IMAGES[env_name] |
| image = env_info["image"] |
| main_port = port or list(env_info["services"].values())[0] |
|
|
| logger.info("Launching %s (image: %s) on port %d...", env_name, image, main_port) |
| logger.info("Docs: %s", env_info["docs"]) |
|
|
| |
| port_args = [] |
| services = env_info["services"] |
| base_service_port = list(services.values())[0] |
| for service, service_port in services.items(): |
| host_port = main_port + (service_port - base_service_port) |
| port_args += ["-p", f"{host_port}:{service_port}"] |
|
|
| cmd = [ |
| "docker", "run", "-d", |
| "--name", f"poisonclaw-{env_name}", |
| *port_args, |
| image, |
| ] |
| logger.info("Running: %s", " ".join(cmd)) |
|
|
| try: |
| result = subprocess.run(cmd, capture_output=True, text=True, check=True) |
| container_id = result.stdout.strip()[:12] |
| logger.info( |
| "Container started: %s\n" |
| "Access at: http://localhost:%d", |
| container_id, |
| main_port, |
| ) |
| except subprocess.CalledProcessError as exc: |
| logger.error("Failed to start container: %s", exc.stderr) |
| sys.exit(1) |
|
|
|
|
| def stop_all() -> None: |
| """Stop all running PoisonClaw environment containers.""" |
| if not check_docker(): |
| return |
|
|
| result = subprocess.run( |
| ["docker", "ps", "--filter", "name=poisonclaw-", "--format", "{{.Names}}"], |
| capture_output=True, |
| text=True, |
| ) |
| containers = [c.strip() for c in result.stdout.splitlines() if c.strip()] |
|
|
| if not containers: |
| logger.info("No PoisonClaw containers running.") |
| return |
|
|
| for name in containers: |
| subprocess.run(["docker", "stop", name], check=False) |
| subprocess.run(["docker", "rm", name], check=False) |
| logger.info("Stopped and removed: %s", name) |
|
|
|
|
| def show_status() -> None: |
| """Show status of running PoisonClaw containers.""" |
| if not check_docker(): |
| return |
|
|
| result = subprocess.run( |
| [ |
| "docker", "ps", |
| "--filter", "name=poisonclaw-", |
| "--format", "table {{.Names}}\t{{.Status}}\t{{.Ports}}", |
| ], |
| capture_output=True, |
| text=True, |
| ) |
| print(result.stdout or "No PoisonClaw containers running.") |
|
|
|
|
| def main() -> None: |
| args = parse_args() |
|
|
| if args.stop_all: |
| stop_all() |
| elif args.status: |
| show_status() |
| elif args.env: |
| launch_env(args.env, args.port or 0) |
| else: |
| logger.error("Specify --env, --stop_all, or --status.") |
| sys.exit(1) |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|