build: сборка в docker

- Dockerfile: python 3.12, Google Chrome (patchright), Xvfb, tini как PID 1 против зомби-процессов
- docker-compose: volume для extra/, shm_size 2gb, порт через API_PORT
- requirements: добавлены playwright и camoufox
- docs: Deployment.md, README и доки актуализированы под Chrome

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
protokeyandClaude Opus 5.5 committed 2026-10-11 00:17:28 +04:00
1 parent 4e930caccb
commit 08a6b52b9d
8 files changed
+162 -21

No files matched your search

+10
View File
@@ -0,0 +1,10 @@
.git
.idea
.venv
wheels
extra
docs
tests
**/__pycache__
*.pyc
.env
+38
View File
@@ -0,0 +1,38 @@
FROM python:3.12-slim-bookworm
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1
# Xvfb — браузер запускается в headful-режиме (headless=False), нужен виртуальный дисплей
RUN apt-get update \
&& apt-get install -y --no-install-recommends tini xvfb xauth fonts-liberation fonts-noto-color-emoji ca-certificates \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
# real_chrome=True → channel "chrome": ставим Google Chrome stable вместе с системными зависимостями
RUN patchright install --with-deps chrome \
&& rm -rf /var/lib/apt/lists/*
RUN useradd --create-home --uid 1000 app \
&& mkdir -p /app/extra \
&& chown -R app:app /app
COPY --chown=app:app src/ ./src/
USER app
WORKDIR /app/src
# профили браузера (extra/user_data_dir) и geoip-кэш — в volume
VOLUME ["/app/extra"]
EXPOSE 8000
# tini как PID 1: пробрасывает сигналы и подбирает осиротевшие процессы Chrome/Xvfb (иначе — зомби)
ENTRYPOINT ["/usr/bin/tini", "-g", "--"]
CMD ["xvfb-run", "-a", "--server-args=-screen 0 1920x1080x24", \
"uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
+30 -18
View File
@@ -13,15 +13,15 @@ Early MVP (v0), under active development. Single browser engine, no queue, no bi
Client → Caddy (TLS) → FastAPI → API-key auth → semaphore acquire
│
▼
Camoufox browser context
Chrome (Patchright) context
(run actions, collect result)
│
▼
release semaphore → response
```
- **Engine**: [Camoufox](https://camoufox.com/) (stealth Firefox, driven via Playwright). Chosen for v1 because
Firefox-based fingerprinting is less commoditized against anti-bot vendors than patched Chromium.
- **Engine**: real Google Chrome (`channel="chrome"`) driven via [Patchright](https://github.com/Kaliiiiiiiiii-Vinyzu/patchright-python)
(undetected Playwright fork), headful under Xvfb. Firefox is not supported yet.
- **API**: FastAPI, single versioned endpoint (`POST /v1/solve`).
- **Concurrency**: bounded by an `asyncio.Semaphore` over browser contexts — no external job queue in v1. Simpler to
run, hard ceiling on throughput per instance (see Roadmap for scaling out).
@@ -29,28 +29,30 @@ Client → Caddy (TLS) → FastAPI → API-key auth → semaphore acquire
- **Logging**: every request's outcome (success/failure/duration) is persisted — this becomes the success-rate data used
to evaluate the service and talk to clients about reliability.
- **Proxies**: not managed by the service in v1 — the client supplies their own proxy per request if they need one.
- **Deployment**: single Docker container behind a Caddy reverse proxy.
- **Deployment**: single Docker container behind a Caddy reverse proxy — see [Docker](#docker) and
[docs/Deployment.md](docs/Deployment.md).
- **Canvas fingerprint** по дизайну генерируется каждую сессию новый.
### Planned project layout
### Project layout
```
src/
main.py # FastAPI app + router wiring
config.py # settings (env vars)
main.py # FastAPI app, POST /v1/solve
api/
solve.py # POST /v1/solve
deps.py # API-key auth dependency
engine/
browser.py # Camoufox context pool + semaphore
actions.py # fill / click / wait_for execution
schemas.py # SolveRequest / SolveResponse / Action models
db/
models.py # api_keys, request_logs
session.py
schemas.py # SolveRequest / Action models
geoip.py # proxy → locale / timezone
engine/
stealthy.py # AsyncStealthySession (Patchright + Chrome)
session.py # browser / context launch options
page_pool.py # page pool
antibot/ # anti-bot detection & bypass orchestrator (Cloudflare, reCAPTCHA)
captcha/ # captcha solvers (Turnstile, reCAPTCHA, hCaptcha)
tests/
wheels/ # whl библиотеки для оффлайн установки под Ubuntu Noble
Dockerfile
docker-compose.yml
.dockerignore
requirements.txt
env.example
```
@@ -74,7 +76,7 @@ X-API-Key: <key>
"proxy": "socks5://user:pass@host:port",
"screen": "1280x920",
"user_agent": "Firefox",
"user_agent": "Mozilla/5.0 (...) Chrome/...",
"timeout": 120
}
```
@@ -131,7 +133,7 @@ Deliberately out of scope for v1:
- Job queue (Redis/RabbitMQ) + multiple worker VPS for horizontal scaling
- Real billing/usage plans beyond a flat API-key check
- Broader action vocabulary beyond fill/click/wait_for
- Second engine (Patchright/Chromium) for targets where the Firefox fingerprint doesn't fit
- Second engine (Firefox, e.g. Camoufox) for targets where the Chrome fingerprint doesn't fit
## Local development
@@ -139,5 +141,15 @@ Deliberately out of scope for v1:
python -m venv .venv
source .venv/bin/activate
make sync-offline
uvicorn app.main:app --reload
cd src && uvicorn main:app --reload
```
## Docker
```bash
docker compose up -d --build
```
The API listens on `http://localhost:8000`. Browser profiles (`extra/user_data_dir`) and the GeoIP cache live in the
`extra` named volume, so sessions survive container restarts. Details (Xvfb, tini, `/dev/shm`) —
[docs/Deployment.md](docs/Deployment.md).
+17
View File
@@ -0,0 +1,17 @@
services:
api:
build: .
image: robotex-api
restart: unless-stopped
ports:
- "${API_PORT:-8000}:8000"
env_file:
- path: .env
required: false
volumes:
- extra:/app/extra
# Chrome активно использует /dev/shm, дефолтных 64MB не хватает
shm_size: "2gb"
volumes:
extra:
+2 -2
View File
@@ -57,7 +57,7 @@ src/antibot/
```python
from dataclasses import dataclass
from enum import Enum
from playwright.async_api import Page
from patchright.async_api import Page
class Stage(str, Enum):
INTERSTITIAL = "interstitial"
@@ -188,7 +188,7 @@ async def solve_interactive(page, info, captcha_key: str):
1. **Траектория мыши** — не прыжок в точку, а несколько промежуточных точек по кривой Безье от текущей позиции курсора до цели, с нелинейным (ease-in-out) распределением скорости и небольшим шумом (±1-2px) на каждом шаге. `page.mouse.move(x, y, steps=N)` даёт линейную интерполяцию — этого мало, нужно звать `mouse.move` несколько раз вручную по расчётным точкам.
2. **Ускорение/замедление клика** — задержка `mouse.down()` -> `mouse.up()` рандомная (100-250мс), плюс случайный сдвиг координаты клика внутри bounding box (не в центр, как это делает и сам Scrapling — `randint(26,28)`).
3. **Общая стелс-часть** (не про капчу, но без неё стадия A вообще не пройдёт): подмена `navigator.webdriver`, canvas noise, консистентные timezone/locale/UA — то, что в вашем проекте уже частично закрыто Camoufox-фингерпринтом в [main.py](../src/main.py) (`generate_context_fingerprint`). Это нужно сохранить как есть — не переизобретать, Camoufox с этим справляется хорошо, вопрос отказа от него у Scrapling был про производительность/стабильность, а не про качество стелса.
3. **Общая стелс-часть** (не про капчу, но без неё стадия A вообще не пройдёт): подмена `navigator.webdriver`, canvas noise, консистентные timezone/locale/UA — сейчас закрывается связкой Patchright + настоящий Google Chrome (`real_chrome=True`) в [engine/stealthy.py](../src/engine/stealthy.py), timezone/locale берутся из геолокации прокси ([api/geoip.py](../src/api/geoip.py)). Camoufox (Firefox) пока остаётся в зависимостях, но как движок не используется — проблемы были скорее с генератором отпечатков, чем с самим Camoufox.
## 6. Оркестратор (`orchestrator.py`)
+62
View File
@@ -0,0 +1,62 @@
# Развёртывание в Docker
## Быстрый старт
```bash
docker compose up -d --build
docker compose logs -f api
```
API доступно на `http://localhost:8000` (`POST /v1/solve`).
Без compose:
```bash
docker build -t robotex-api .
docker run -d --name robotex -p 8000:8000 --shm-size=2g -v robotex-extra:/app/extra robotex-api
```
## Что внутри образа
| Компонент | Зачем |
|---|---|
| `python:3.12-slim-bookworm` | базовый образ, Python той же версии, что и в `.venv` |
| `requirements.txt` | ставится из PyPI (папка `wheels/` в образ не попадает — она под Ubuntu Noble и неполная) |
| Google Chrome stable (`patchright install --with-deps chrome`) | сессия запускается с `real_chrome=True` → channel `chrome` |
| Xvfb (`xvfb-run`) | браузер работает в headful-режиме (`headless=False`), нужен виртуальный дисплей 1920x1080x24 |
| `tini` (ENTRYPOINT, PID 1) | пробрасывает сигналы и подбирает осиротевшие процессы Chrome/Xvfb — без него копятся зомби-процессы |
| пользователь `app` (uid 1000) | Chrome не запускается от root без `--no-sandbox` |
Рабочая директория — `/app/src`, uvicorn запускается как `uvicorn main:app` (импорты в проекте вида `from api import ...`).
## Данные и volume
`/app/extra` вынесен в volume `extra`:
- `extra/user_data_dir/<session_id>` — профили браузера (cookies, local storage, fingerprint) по `session_id`;
- `extra/geoip_cache.json` — кэш геолокации прокси.
Профили со временем накапливаются — чистить вручную:
```bash
docker compose exec api sh -c 'rm -rf /app/extra/user_data_dir/*'
```
## Настройки compose
- `shm_size: 2gb` — Chrome активно использует `/dev/shm`, дефолтных 64 МБ не хватает (вкладки падают с «Aw, Snap»).
- `API_PORT` — порт на хосте (по умолчанию 8000): `API_PORT=8001 docker compose up -d`.
- `restart: unless-stopped`.
- `.env` подключается, если существует (шаблон — `env.example`).
## Reverse proxy
В проде контейнер ставится за Caddy (TLS). Пример `Caddyfile`:
```
api.example.com {
reverse_proxy localhost:8000
}
```
Таймауты прокси должны быть не меньше `timeout` запроса (по умолчанию 120 с).
+1 -1
View File
@@ -10,7 +10,7 @@
- proxy - обязательно нужно задать прокси, система не станет палить свою инфраструктуру
- time_zone - временная зона
- locale - языковая настройка
- user_agent - заголовок браузера Firefox, если не передать будет использован свой
- user_agent - User-Agent браузера Chrome, если не передать будет использован свой
- screen - размер окна, по умолчанию: 1280х920
- timeout - общий таймаут выполнения запроса, по умолчанию: 120
- captcha - токен сервиса распознавания, если требуется решить капчу на странице
+2
View File
@@ -10,6 +10,7 @@ apify_fingerprint_datapoints==0.15.0
attrs==26.1.0
blessed==1.49.0
browserforge==1.2.4
camoufox==0.5.6
certifi==2026.7.22
charset-normalizer==3.5.1
click==8.5.0
@@ -47,6 +48,7 @@ numpy==2.5.3
orjson==3.12.0
patchright==1.63.0
platformdirs==4.11.7
playwright==1.62.0
propcache==0.5.2
psutil==7.2.2
pydantic==2.13.5