# syntax=docker/dockerfile:1 # Standalone Hybrid Turnstile Solver (Go + Rust + C++ + Python) # # HF Space 拉取方式:构建时 git clone 完整仓库(含 vendor/chromium 离线 deb)。 # Chromium:优先安装仓库内置 Gitee chromium-browser.deb,不依赖构建时访问 Gitee。 # # REPO_URL 默认: https://github.com/xiaocongyu66/turnstile-solver.git # REPO_REF 默认: main # CHROMIUM_TAG 默认: 22.04_amd64 | 22.04_arm64 # # Hardware: ≥ 2 vCPU / 16 GB RAM recommended ARG PYTHON_VERSION=3.12-bookworm ARG REPO_URL=https://github.com/xiaocongyu66/turnstile-solver.git ARG REPO_REF=main ARG CHROMIUM_TAG= # ========== Stage 0: ALWAYS clone full GitHub source (includes vendor/chromium) ========== FROM debian:bookworm-slim AS source ARG REPO_URL ARG REPO_REF ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update && apt-get install -y --no-install-recommends \ git ca-certificates \ && rm -rf /var/lib/apt/lists/* WORKDIR /src # Need full history depth only for files; large vendor/chromium is in the tree RUN set -eu; \ echo "Cloning ${REPO_URL} @ ${REPO_REF} (full tree with vendor/chromium)"; \ git clone --depth 1 --branch "${REPO_REF}" "${REPO_URL}" /src/app \ || git clone --depth 1 "${REPO_URL}" /src/app; \ cd /src/app && git checkout "${REPO_REF}" 2>/dev/null || true; \ test -f /src/app/watchdog/src/main.rs; \ test -f /src/app/gateway/main.go; \ test -f /src/app/worker/browser_worker.py; \ test -f /src/app/worker/proxy_pool.py; \ test -f /src/app/worker/proxy_relay.py; \ test -f /src/app/worker/cf_ares_helper.py; \ test -d /src/app/vendor/CF-Ares/cf_ares; \ test -f /src/app/util/solver_util.cpp; \ test -f /src/app/docker/entrypoint.sh; \ test -d /src/app/vendor/chromium; \ ls -la /src/app/vendor/chromium; \ echo "Source OK: $(cd /src/app && git rev-parse --short HEAD 2>/dev/null || echo unknown)" # ---------- Go gateway ---------- FROM golang:1.22-bookworm AS gobuild WORKDIR /src COPY --from=source /src/app/gateway/go.mod /src/app/gateway/main.go ./ RUN mkdir -p /out && go build -trimpath -ldflags='-s -w' -o /out/solver-gateway . # ---------- Rust watchdog ---------- FROM rust:1-bookworm AS rustbuild WORKDIR /src RUN mkdir -p /out COPY --from=source /src/app/watchdog/Cargo.toml ./ COPY --from=source /src/app/watchdog/Cargo.lock* ./ COPY --from=source /src/app/watchdog/src ./src RUN cargo build --release && cp target/release/solver-watchdog /out/solver-watchdog # ---------- C++ util ---------- FROM debian:bookworm-slim AS cppbuild RUN apt-get update && apt-get install -y --no-install-recommends g++ \ && rm -rf /var/lib/apt/lists/* && mkdir -p /out COPY --from=source /src/app/util/solver_util.cpp /src/app/util/solver_util.hpp /src/ RUN g++ -O2 -std=c++17 -o /out/solver-util /src/solver_util.cpp # ---------- Runtime ---------- FROM python:${PYTHON_VERSION} ARG CHROMIUM_TAG ENV DEBIAN_FRONTEND=noninteractive \ PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 \ PORT=7860 \ HOST=0.0.0.0 \ PROJECT_ROOT=/app \ SOLVER_PYTHON=python \ SOLVER_GATEWAY_WORKERS=auto \ SOLVER_GATEWAY_WORKERS_MAX=auto \ TURNSTILE_SOLVER_HEADLESS=1 \ PLAYWRIGHT_BROWSERS_PATH=/ms-playwright RUN apt-get update && apt-get install -y --no-install-recommends \ tini ca-certificates curl \ libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 \ libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 \ libgbm1 libasound2 libpango-1.0-0 libcairo2 libatspi2.0-0 \ libx11-6 libx11-xcb1 libxcb1 libxext6 libxshmfence1 fonts-liberation \ unzip \ && rm -rf /var/lib/apt/lists/* \ && pip install --no-cache-dir \ 'playwright>=1.55' \ 'curl_cffi>=0.5.7' \ 'requests>=2.28.0' \ 'seleniumbase>=4.0.0' \ 'undetected-chromedriver>=3.5.0' \ 'selenium>=4.15.0' \ 'cf-ares>=0.1.0' || pip install --no-cache-dir \ 'playwright>=1.55' 'curl_cffi>=0.5.7' 'requests>=2.28.0' \ 'seleniumbase>=4.0.0' 'undetected-chromedriver>=3.5.0' 'selenium>=4.15.0' # Offline install: bundled debs from vendor/chromium (Gitee jizijhj/chromium_1) COPY --from=source /src/app/vendor/chromium /opt/vendor/chromium RUN set -eux; \ arch="$(dpkg --print-architecture)"; \ tag="${CHROMIUM_TAG}"; \ if [ -z "$tag" ]; then \ case "$arch" in \ amd64) tag="22.04_amd64" ;; \ arm64) tag="22.04_arm64" ;; \ *) tag="22.04_amd64" ;; \ esac; \ fi; \ dir="/opt/vendor/chromium/${tag}"; \ echo "Installing offline chromium from ${dir}"; \ if [ ! -d "$dir" ]; then \ echo "WARN: missing ${dir}, trying any matching *_${arch}"; \ dir="$(ls -d /opt/vendor/chromium/*_${arch} 2>/dev/null | head -1 || true)"; \ fi; \ if [ -n "$dir" ] && [ -f "${dir}/chromium-browser.deb" ]; then \ cd "$dir"; \ dpkg -i chromium-codecs-ffmpeg-extra.deb 2>/dev/null || true; \ dpkg -i chromium-browser.deb || true; \ dpkg -i chromium-browser-l10n.deb 2>/dev/null || true; \ apt-get update && apt-get install -y -f --no-install-recommends || true; \ rm -rf /var/lib/apt/lists/*; \ else \ echo "WARN: no bundled chromium deb for arch=${arch}"; \ fi; \ CHROME=""; \ for c in \ /usr/bin/chromium-browser \ /usr/bin/chromium \ /usr/lib/chromium-browser/chromium-browser \ /usr/lib/chromium/chromium \ /usr/local/bin/chromium-browser; do \ if [ -x "$c" ]; then CHROME="$c"; break; fi; \ done; \ if [ -n "$CHROME" ]; then \ echo "Installed system chromium (fallback): $CHROME"; \ "$CHROME" --version || true; \ ln -sf "$CHROME" /usr/local/bin/chromium-browser; \ echo "$CHROME" > /etc/solver-chrome-path; \ else \ echo "WARN: system chromium missing"; \ fi; \ # Always install modern Playwright Chromium — Turnstile rejects ancient 108-era # chromium-browser debs. Worker prefers /ms-playwright over system chrome. echo "Installing Playwright bundled chromium → /ms-playwright"; \ mkdir -p /ms-playwright; \ PLAYWRIGHT_BROWSERS_PATH=/ms-playwright python -m playwright install --with-deps chromium || \ PLAYWRIGHT_BROWSERS_PATH=/ms-playwright python -m playwright install chromium; \ find /ms-playwright -type f \( -name chrome -o -name chromium -o -name headless_shell \) 2>/dev/null | head -20 WORKDIR /app COPY --from=source /src/app/worker /app/worker COPY --from=source /src/app/vendor/CF-Ares /app/vendor/CF-Ares # also ship under worker/ so helper finds it even if CF_ARES_PATH is wrong COPY --from=source /src/app/vendor/CF-Ares /app/worker/vendor/CF-Ares COPY --from=source /src/app/docker/entrypoint.sh /entrypoint.sh COPY --from=gobuild /out/solver-gateway /app/gateway/solver-gateway COPY --from=rustbuild /out/solver-watchdog /app/watchdog/solver-watchdog COPY --from=cppbuild /out/solver-util /app/util/solver-util ENV PYTHONPATH=/app/worker:/app/vendor/CF-Ares \ CF_ARES_PATH=/app/vendor/CF-Ares \ PROXY_RELAY_WORK_DIR=/tmp/solver-proxy-relay \ PROXY_RELAY_ENABLED=1 \ PROXY_RELAY_AUTO_INSTALL=1 \ CF_ARES=1 \ CF_ARES_BROWSER_ENGINE=undetected \ CF_ARES_HEADLESS=1 \ CF_ARES_TIMEOUT=60 \ CF_ARES_SESSION_DIR=/tmp/solver-cf-ares-sessions RUN chmod +x /entrypoint.sh /app/gateway/solver-gateway \ /app/watchdog/solver-watchdog /app/util/solver-util \ /app/worker/browser_worker.py \ && mkdir -p /app/logs /data/logs /tmp/solver-proxy-relay /tmp/solver-cf-ares-sessions \ && (test -d /app/vendor/CF-Ares/cf_ares && echo "vendor CF-Ares OK" || echo "WARN: vendor CF-Ares missing — rely on pip cf-ares") \ && (test -d /app/worker/vendor/CF-Ares/cf_ares && echo "worker vendor OK" || true) \ && ls -la /app/vendor 2>/dev/null || true \ && python -c "import sys; sys.path[:0]=['/app/vendor/CF-Ares','/app/worker']; import cf_ares; print('cf_ares', getattr(cf_ares,'__version__','?')); import cf_ares_helper as h; d=h.diagnose(); print('diagnose', d); assert h.available() or d.get('error'); print('helper available', h.available())" EXPOSE 7860 ENTRYPOINT ["/usr/bin/tini", "--"] CMD ["/entrypoint.sh"]