diff --git a/.env b/.env index 995710d..d0b4c97 100644 --- a/.env +++ b/.env @@ -19,3 +19,8 @@ AGENT_MAX_STEPS=20 AGENT_USE_VISION=true SERVER_PORT=8383 SERVER_HOST=0.0.0.0 + +# Remote Debugging +REMOTE_DEBUGGING_ENABLED=true +REMOTE_DEBUGGING_PORT=9222 + diff --git a/Dockerfile b/Dockerfile index 84d7614..b44a09e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,8 @@ FROM mcr.microsoft.com/playwright/python:v1.49.0-noble +# Установка системных зависимостей (socat для проброса портов) +RUN apt-get update && apt-get install -y socat && rm -rf /var/lib/apt/lists/* + # Установка дополнительных Python зависимостей RUN pip install --no-cache-dir --root-user-action=ignore \ fastapi \ diff --git a/app.py b/app.py index 0d2baae..e2cebc4 100644 --- a/app.py +++ b/app.py @@ -21,29 +21,34 @@ DEFAULT_MODEL = os.getenv("LLM_MODEL_NAME", "openai/gpt-4o-mini") from contextlib import asynccontextmanager +import logging + +# Настройка логирования +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') +logger = logging.getLogger(__name__) + @asynccontextmanager async def lifespan(app: FastAPI): # Startup logic import sys from importlib import metadata - print(f"Python version: {sys.version}") + logger.info(f"Python version: {sys.version}") # Автоматический запуск Chrome при старте, если включен Remote Debugging if REMOTE_DEBUGGING_ENABLED: - print("Remote Debugging enabled, starting Chrome on startup...") + logger.info("Remote Debugging enabled, starting Chrome on startup...") # Выведем IP адреса для отладки import socket try: hostname = socket.gethostname() ip_address = socket.gethostbyname(hostname) - print(f"Container IP: {ip_address}") + logger.info(f"Container IP: {ip_address}") except: pass # Запускаем Chrome asyncio.create_task(run_browser_task("startup", "about:blank", 1, None)) yield - # Shutdown logic (if needed) app = FastAPI(title="Browser Use API", version="1.0.0", lifespan=lifespan) @@ -117,15 +122,15 @@ async def run_browser_task(task_id: str, task: str, max_steps: int, model: str): cdp_url = f"http://127.0.0.1:{REMOTE_DEBUGGING_PORT}" # 1. Проверяем, не запущен ли уже Chrome с CDP - print(f"[{task_id}] Checking if Chrome is already running with CDP at {cdp_url}...") + logger.info(f"[{task_id}] Checking if Chrome is already running with CDP at {cdp_url}...") chrome_already_running = False try: response = urllib.request.urlopen(f"{cdp_url}/json/version", timeout=2) if response.status == 200: - print(f"[{task_id}] Chrome is already running, connecting to existing instance.") + logger.info(f"[{task_id}] Chrome is already running, connecting to existing instance.") chrome_already_running = True except Exception: - print(f"[{task_id}] Chrome not detected at {cdp_url}. Starting new instance...") + logger.info(f"[{task_id}] Chrome not detected at {cdp_url}. Starting new instance...") if not chrome_already_running: # Список возможных путей к Chrome @@ -149,24 +154,27 @@ async def run_browser_task(task_id: str, task: str, max_steps: int, model: str): chrome_exe = shutil.which("google-chrome") or shutil.which("chromium") or shutil.which("chromium-browser") if not chrome_exe: - # В образе Playwright путь обычно такой: chrome_exe = "/usr/bin/google-chrome" - print(f"[{task_id}] Using Chrome executable: {chrome_exe}") + logger.info(f"[{task_id}] Using Chrome executable: {chrome_exe}") # Используем постоянную директорию для сохранения сессий (логинов) user_data_dir = "/app/browser-data/main_profile" os.makedirs(user_data_dir, exist_ok=True) - # Запускаем Chrome с remote debugging + # Внутренний порт для Chrome + CHROME_INTERNAL_PORT = REMOTE_DEBUGGING_PORT + 1 # 9223 + + # Запускаем Chrome с remote debugging на внутреннем порту chrome_cmd = [ chrome_exe, - f"--remote-debugging-port={REMOTE_DEBUGGING_PORT}", - "--remote-debugging-address=0.0.0.0", - f"--user-data-dir={user_data_dir}", + f"--remote-debugging-port={CHROME_INTERNAL_PORT}", + "--remote-debugging-address=127.0.0.1", "--remote-allow-origins=*", - "--headless=new", + f"--user-data-dir={user_data_dir}", + "--headless", "--no-sandbox", + "--disable-setuid-sandbox", "--disable-dev-shm-usage", "--disable-gpu", "--no-first-run", @@ -175,46 +183,56 @@ async def run_browser_task(task_id: str, task: str, max_steps: int, model: str): "about:blank" ] - print(f"[{task_id}] Executing command: {' '.join(chrome_cmd)}") + logger.info(f"[{task_id}] Executing Chrome: {' '.join(chrome_cmd)}") - # Запускаем процесс + # Запускаем Chrome log_file = open(f"/tmp/chrome_{task_id}.log", "w") - process = subprocess.Popen( + subprocess.Popen( chrome_cmd, stdout=log_file, stderr=log_file, start_new_session=True ) + # Запускаем socat для проброса порта 9222 -> 9223 + socat_cmd = [ + "socat", + f"TCP-LISTEN:{REMOTE_DEBUGGING_PORT},fork,reuseaddr", + f"TCP:127.0.0.1:{CHROME_INTERNAL_PORT}" + ] + logger.info(f"[{task_id}] Executing socat: {' '.join(socat_cmd)}") + subprocess.Popen( + socat_cmd, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + start_new_session=True + ) + # Ждем пока Chrome запустится и CDP будет готов cdp_ready = False - for i in range(30): # Ждем до 30 секунд + # Для внутреннего использования приложением используем 9223 + internal_cdp_url = f"http://127.0.0.1:{CHROME_INTERNAL_PORT}" + + for i in range(30): await asyncio.sleep(1) - - # Проверка порта - import socket - for host in ['127.0.0.1', '0.0.0.0', ip_address]: - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - if s.connect_ex((host, REMOTE_DEBUGGING_PORT)) == 0: - print(f"[{task_id}] Port {REMOTE_DEBUGGING_PORT} is OPEN on {host}!") - else: - print(f"[{task_id}] Port {REMOTE_DEBUGGING_PORT} is CLOSED on {host}!") - try: - response = urllib.request.urlopen(f"{cdp_url}/json/version", timeout=2) + response = urllib.request.urlopen(f"{internal_cdp_url}/json/version", timeout=2) if response.status == 200: cdp_ready = True - print(f"[{task_id}] CDP is ready at {cdp_url}") + logger.info(f"[{task_id}] CDP is ready at {internal_cdp_url} (proxied to {REMOTE_DEBUGGING_PORT})") break except Exception: if i % 5 == 0: - print(f"[{task_id}] Waiting for CDP at {cdp_url}... (attempt {i})") + logger.info(f"[{task_id}] Waiting for Chrome at {internal_cdp_url}... (attempt {i})") - if not cdp_ready: - with open(f"/tmp/chrome_{task_id}.log", "r") as f: - print(f"[{task_id}] Chrome Logs:\n{f.read()}") - if task_id != "startup": - raise RuntimeError(f"Failed to start Chrome or CDP not ready after 30s at {cdp_url}") + if not cdp_ready: + with open(f"/tmp/chrome_{task_id}.log", "r") as f: + logger.error(f"[{task_id}] Chrome Logs:\n{f.read()}") + if task_id != "startup": + raise RuntimeError(f"Failed to start Chrome at {internal_cdp_url}") + + # Обновляем cdp_url для Browser + cdp_url = internal_cdp_url # Подключаемся через CDP browser = Browser(cdp_url=cdp_url) @@ -230,7 +248,7 @@ async def run_browser_task(task_id: str, task: str, max_steps: int, model: str): # Если это был просто запуск при старте - выходим if task_id == "startup": - print("[startup] Chrome started successfully and ready for connections.") + logger.info("[startup] Chrome started successfully and ready for connections.") return # Создание агента @@ -251,12 +269,11 @@ async def run_browser_task(task_id: str, task: str, max_steps: int, model: str): tasks[task_id]["result"] = result except Exception as e: - print(f"[{task_id}] Error: {e}") + logger.error(f"[{task_id}] Error: {e}") if task_id in tasks: tasks[task_id]["status"] = "failed" tasks[task_id]["error"] = str(e) finally: - # Не закрываем браузер pass @app.get("/api/tasks/{task_id}", response_model=TaskResponse) diff --git a/docker-compose.yml b/docker-compose.yml index cd172f3..0203668 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,8 +13,8 @@ services: environment: - OPENROUTER_API_KEY=${OPENROUTER_API_KEY} - LLM_MODEL_NAME=${LLM_MODEL_NAME:-arcee-ai/trinity-large-preview:free} - - REMOTE_DEBUGGING_ENABLED=${REMOTE_DEBUGGING_ENABLED:-false} - - REMOTE_DEBUGGING_PORT=${REMOTE_DEBUGGING_PORT:-9222} + - REMOTE_DEBUGGING_ENABLED=true + - REMOTE_DEBUGGING_PORT=9222 volumes: - /home/nafer/dokploy_data/browser-use:/app/browser-data shm_size: '2gb'