From 61c49bf1cf93b339bfe5258b6a3d31dbc47def51 Mon Sep 17 00:00:00 2001 From: mac Date: Fri, 7 Mar 2025 16:05:38 +0100 Subject: [PATCH 1/8] chore: build and publish dockerfile docker build Todo: add dockerfile --- .github/workflows/ci.yml | 44 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 15de53b..fa1d268 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,6 +12,8 @@ concurrency: env: PYTHON_VERSION: "3.13" + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} jobs: determine_changes: @@ -69,3 +71,45 @@ jobs: - name: "Validate project metadata" run: uvx --from 'validate-pyproject[all,store]' validate-pyproject pyproject.toml + + build-and-publish: + runs-on: ubuntu-latest + + permissions: + contents: read + packages: write + # attestations: write + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build and push Docker image + id: push + uses: docker/build-push-action@v6 + with: + context: ./ + file: ./Dockerfile + platforms: linux/amd64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max From c6ac128ba0b3cde596cf336dfcaa0235668f83ab Mon Sep 17 00:00:00 2001 From: Ryan MacArthur Date: Fri, 7 Mar 2025 16:33:16 +0100 Subject: [PATCH 2/8] dockerfile and docker build arg for vnc password --- .github/workflows/ci.yml | 2 ++ Dockerfile | 70 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 Dockerfile diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fa1d268..5da8e7e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -111,5 +111,7 @@ jobs: push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} + build-args: | + VNC_PASSWORD=${{ secrets.VNC_PASSWORD }} cache-from: type=gha cache-to: type=gha,mode=max diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..05534b9 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,70 @@ +FROM ghcr.io/astral-sh/uv:bookworm-slim AS builder + +ARG VNC_PASSWORD=browser-use + +ENV UV_COMPILE_BYTECODE=1 \ + UV_LINK_MODE=copy \ + UV_PYTHON_INSTALL_DIR=/python \ + UV_PYTHON_PREFERENCE=only-managed + +# Install build dependencies and clean up in the same layer +RUN apt-get update -y && \ + apt-get install --no-install-recommends -y clang && \ + rm -rf /var/lib/apt/lists/* + +# Install Python before the project for caching +RUN uv python install 3.13 + +WORKDIR /app +RUN --mount=type=cache,target=/root/.cache/uv \ + --mount=type=bind,source=uv.lock,target=uv.lock \ + --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ + uv sync --frozen --no-install-project --no-dev +ADD . /app +RUN --mount=type=cache,target=/root/.cache/uv \ + uv sync --frozen --no-dev + +FROM debian:bookworm-slim AS runtime + +# Install required packages including Chromium and clean up in the same layer +RUN apt-get update && \ + apt-get install --no-install-recommends -y \ + xfce4 \ + dbus-x11 \ + tigervnc-standalone-server \ + tigervnc-tools \ + nodejs \ + npm \ + chromium \ + chromium-driver \ + fonts-freefont-ttf \ + fonts-ipafont-gothic \ + fonts-wqy-zenhei \ + fonts-thai-tlwg \ + fonts-kacst \ + fonts-symbola \ + fonts-noto-color-emoji && \ + npm i -g proxy-login-automator && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* && \ + rm -rf /var/cache/apt/* + +# Copy only necessary files from builder +COPY --from=builder --chown=python:python /python /python +COPY --from=builder --chown=app:app /app /app + +ENV PATH="/app/.venv/bin:$PATH" \ + DISPLAY=:0 \ + CHROME_BIN=/usr/bin/chromium \ + CHROMIUM_FLAGS="--no-sandbox --headless --disable-gpu --disable-software-rasterizer --disable-dev-shm-usage" + +# Combine VNC setup commands to reduce layers +RUN mkdir -p ~/.vnc && \ + echo ${VNC_PASSWORD} | vncpasswd -f > /root/.vnc/passwd && \ + chmod 600 /root/.vnc/passwd && \ + printf '#!/bin/sh\nunset SESSION_MANAGER\nunset DBUS_SESSION_BUS_ADDRESS\nstartxfce4' > /root/.vnc/xstartup && \ + chmod +x /root/.vnc/xstartup && \ + printf '#!/bin/bash\nvncserver -depth 24 -geometry 1920x1080 -localhost no -PasswordFile /root/.vnc/passwd :0\nproxy-login-automator\npython /app/main.py' > /app/boot.sh && \ + chmod +x /app/boot.sh + +ENTRYPOINT ["/bin/bash", "/app/boot.sh"] From e20e130bf786c65ee4cabefb34f4e2c8289e1731 Mon Sep 17 00:00:00 2001 From: Ryan MacArthur Date: Fri, 7 Mar 2025 16:40:29 +0100 Subject: [PATCH 3/8] wip: dockerfile for packaged server --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 05534b9..c2c888c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -64,7 +64,7 @@ RUN mkdir -p ~/.vnc && \ chmod 600 /root/.vnc/passwd && \ printf '#!/bin/sh\nunset SESSION_MANAGER\nunset DBUS_SESSION_BUS_ADDRESS\nstartxfce4' > /root/.vnc/xstartup && \ chmod +x /root/.vnc/xstartup && \ - printf '#!/bin/bash\nvncserver -depth 24 -geometry 1920x1080 -localhost no -PasswordFile /root/.vnc/passwd :0\nproxy-login-automator\npython /app/main.py' > /app/boot.sh && \ + printf '#!/bin/bash\nvncserver -depth 24 -geometry 1920x1080 -localhost no -PasswordFile /root/.vnc/passwd :0\nproxy-login-automator\npython server --transport sse --port 8000' > /app/boot.sh && \ chmod +x /app/boot.sh ENTRYPOINT ["/bin/bash", "/app/boot.sh"] From cc6897ea57afecc31521d8dac56d33f3d3c1d4f1 Mon Sep 17 00:00:00 2001 From: Ryan MacArthur Date: Fri, 7 Mar 2025 16:42:51 +0100 Subject: [PATCH 4/8] chore: lint ci.yml --- .github/workflows/ci.yml | 68 ++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5da8e7e..fa1cb0c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -71,7 +71,7 @@ jobs: - name: "Validate project metadata" run: uvx --from 'validate-pyproject[all,store]' validate-pyproject pyproject.toml - + build-and-publish: runs-on: ubuntu-latest @@ -82,36 +82,36 @@ jobs: id-token: write steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Log in to the Container registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Build and push Docker image - id: push - uses: docker/build-push-action@v6 - with: - context: ./ - file: ./Dockerfile - platforms: linux/amd64 - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - build-args: | - VNC_PASSWORD=${{ secrets.VNC_PASSWORD }} - cache-from: type=gha - cache-to: type=gha,mode=max + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build and push Docker image + id: push + uses: docker/build-push-action@v6 + with: + context: ./ + file: ./Dockerfile + platforms: linux/amd64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: | + VNC_PASSWORD=${{ secrets.VNC_PASSWORD }} + cache-from: type=gha + cache-to: type=gha,mode=max From 180a5d34e851655e9cea28f21792b4eb763f2ba6 Mon Sep 17 00:00:00 2001 From: Ryan MacArthur Date: Fri, 7 Mar 2025 17:51:30 +0100 Subject: [PATCH 5/8] fix: path to python app --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index c2c888c..3b49110 100644 --- a/Dockerfile +++ b/Dockerfile @@ -64,7 +64,7 @@ RUN mkdir -p ~/.vnc && \ chmod 600 /root/.vnc/passwd && \ printf '#!/bin/sh\nunset SESSION_MANAGER\nunset DBUS_SESSION_BUS_ADDRESS\nstartxfce4' > /root/.vnc/xstartup && \ chmod +x /root/.vnc/xstartup && \ - printf '#!/bin/bash\nvncserver -depth 24 -geometry 1920x1080 -localhost no -PasswordFile /root/.vnc/passwd :0\nproxy-login-automator\npython server --transport sse --port 8000' > /app/boot.sh && \ + printf '#!/bin/bash\nvncserver -depth 24 -geometry 1920x1080 -localhost no -PasswordFile /root/.vnc/passwd :0\nproxy-login-automator\npython /app/server --transport sse --port 8000' > /app/boot.sh && \ chmod +x /app/boot.sh ENTRYPOINT ["/bin/bash", "/app/boot.sh"] From fe6310fc11076c94b113b25cef0f4706fed67e04 Mon Sep 17 00:00:00 2001 From: Michel Osswald Date: Sat, 8 Mar 2025 17:12:07 +0100 Subject: [PATCH 6/8] chrome path as env var --- .env.example | 3 ++- server/server.py | 15 ++++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/.env.example b/.env.example index 1646e8f..1b049f1 100644 --- a/.env.example +++ b/.env.example @@ -1 +1,2 @@ -OPENAI_API_KEY=sk \ No newline at end of file +OPENAI_API_KEY=sk +CHROME_PATH=/usr/bin/chromium \ No newline at end of file diff --git a/server/server.py b/server/server.py index 43ca076..b5fb31a 100644 --- a/server/server.py +++ b/server/server.py @@ -1,3 +1,4 @@ +import os import anyio import click import asyncio @@ -5,7 +6,7 @@ from datetime import datetime from langchain_openai import ChatOpenAI from browser_use import Agent -from browser_use.browser.browser import Browser +from browser_use.browser.browser import Browser, BrowserConfig import mcp.types as types from mcp.server.lowlevel import Server from dotenv import load_dotenv @@ -31,7 +32,11 @@ ) # Initialize browser and context -browser = Browser() +browser = Browser( + config=BrowserConfig( + chrome_instance_path=os.environ.get("CHROME_PATH"), + ) +) context = BrowserContext(browser=browser, config=config) # Initialize LLM @@ -113,9 +118,9 @@ async def run_browser_task_async(task_id, url, action): if not await check_browser_health(): task_store[task_id]["status"] = "failed" task_store[task_id]["end_time"] = datetime.now().isoformat() - task_store[task_id]["error"] = ( - "Browser context is unhealthy and could not be reset" - ) + task_store[task_id][ + "error" + ] = "Browser context is unhealthy and could not be reset" return # Define step callback function with the correct signature From 9dca64304541d9a6792045ea070cecfa562c2772 Mon Sep 17 00:00:00 2001 From: Michel Osswald Date: Sat, 8 Mar 2025 17:44:08 +0100 Subject: [PATCH 7/8] updated chromium flaggs --- server/server.py | 1 + 1 file changed, 1 insertion(+) diff --git a/server/server.py b/server/server.py index b5fb31a..f190c65 100644 --- a/server/server.py +++ b/server/server.py @@ -35,6 +35,7 @@ browser = Browser( config=BrowserConfig( chrome_instance_path=os.environ.get("CHROME_PATH"), + extra_chromium_args=["--no-sandbox", "--disable-gpu", "--disable-software-rasterizer", "--disable-dev-shm-usage", "--remote-debugging-port=9222"], ) ) context = BrowserContext(browser=browser, config=config) From 433df9a887423bc87040facbd672da2fbba192c3 Mon Sep 17 00:00:00 2001 From: Michel Osswald Date: Sat, 8 Mar 2025 17:47:54 +0100 Subject: [PATCH 8/8] updated linting to ruff --- server/server.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/server/server.py b/server/server.py index f190c65..e411bdd 100644 --- a/server/server.py +++ b/server/server.py @@ -35,7 +35,13 @@ browser = Browser( config=BrowserConfig( chrome_instance_path=os.environ.get("CHROME_PATH"), - extra_chromium_args=["--no-sandbox", "--disable-gpu", "--disable-software-rasterizer", "--disable-dev-shm-usage", "--remote-debugging-port=9222"], + extra_chromium_args=[ + "--no-sandbox", + "--disable-gpu", + "--disable-software-rasterizer", + "--disable-dev-shm-usage", + "--remote-debugging-port=9222", + ], ) ) context = BrowserContext(browser=browser, config=config) @@ -119,9 +125,9 @@ async def run_browser_task_async(task_id, url, action): if not await check_browser_health(): task_store[task_id]["status"] = "failed" task_store[task_id]["end_time"] = datetime.now().isoformat() - task_store[task_id][ - "error" - ] = "Browser context is unhealthy and could not be reset" + task_store[task_id]["error"] = ( + "Browser context is unhealthy and could not be reset" + ) return # Define step callback function with the correct signature