Первая версия требовала >=30 файлов в fc-list и покраснела на фактических
20 — порог был взят из головы, а не измерен. Значение имеет не число
файлов (оно зависит от того, что притащили соседние пакеты), а то, что
запрос Arial отдаёт метрически совместимый шрифт. Проверяем ровно это,
по всем пяти алиасам.
Замер образа 30.08: 20 файлов, 5 семейств (Liberation Sans/Serif/Mono,
Carlito, Caladea); Arial→Liberation Sans, Times New Roman→Liberation
Serif, Courier New→Liberation Mono, Calibri→Carlito, Cambria→Caladea.
Сайдкар инжектил куки на домен из url карточки — для
ekaterinburg.domclick.ru это `.ekaterinburg.domclick.ru`. Настоящие куки
площадки живут на `.domclick.ru` (видно в записи ручной сессии 29.08:
`.domclick.ru qrator_jsid2`, `.domclick.ru qrator_jsr`).
Куки поддомена родительские не заменяют, а сосуществуют с ними: как
только площадка выдаёт свежий `qrator_jsid2` на `.domclick.ru`, браузер
шлёт в одном запросе ДВЕ куки с этим именем, и первой — более
специфичную, нашу протухшую. QRATOR читает её и держит страницу на
274-байтном загрузчике, рукопожатие не завершается никогда.
Замер 29.08, узел 10, чередование, свежий контекст на пробу:
куки на поддомене — 0 успехов из 3 (все три ровно 274 байта),
те же куки на `.domclick.ru` — 3 из 3.
Заодно шрифты в образе сайдкара: контейнер нёс 8 файлов / 3 семейства
(только DejaVu) при заявленном camoufox `os="windows"`. Добавлены
метрически совместимые Liberation/Carlito/Caladea + `fc-cache` и
build-time проверка. С текущими отказами Домклика это НЕ связано —
правка про правдоподобие отпечатка, не про этот баг.
Тестов пока нет сознательно: правка ждёт подтверждения на сервере на
отдохнувшем пуле.
Ничего не апгрейдит. Убирает три способа собрать образ не тем, что в репозитории.
1. tradein-mvp/backend/uv.lock — удалён (файл был untracked, в .gitignore
с рождения). Мера — uv-workspace, сборка берёт КОРНЕВОЙ tradein-mvp/uv.lock
(`context: ./tradein-mvp`, `COPY pyproject.toml uv.lock ./` + `uv sync
--frozen`). Лок, созданный `uv lock` из backend/, не читает никто, и он тихо
разошёлся с рабочим: на 2026-08-29 в нём pillow 12.2.0, starlette 1.0.1,
python-multipart 0.0.29, pydantic-settings 2.14.1, weasyprint 68.1 — пять
пакетов с открытыми advisory, которых в реальной сборке Меры нет вообще.
Именно он подмешал пять фантомных строк в OSV-скан этой серии.
Строка .gitignore остаётся (второй лок не нужен), но теперь с объяснением
почему — иначе следующий читатель снимет ignore и закоммитит призрак.
2. backend/Dockerfile — `COPY pyproject.toml uv.lock* ./` + `if [ -f uv.lock ];
then uv sync --frozen ...; else uv sync ...; fi` → без глоба и без фолбэка.
Глоб + фолбэк означали: пропал лок — сборка не падает, а молча переключается
на резолв «свежайшее из диапазонов pyproject». Образ собрался бы с версиями,
которых никто не видел ни в одном PR. Теперь пропажа лока роняет COPY.
3. tradein-mvp/browser/Dockerfile — `pip install "camoufox[geoip]" aiohttp`
без единого пина. У сайдкара нет лока вообще, так что любая пересборка (в том
числе на несвязанном коммите) тянула свежайший camoufox, а с ним другой
playwright — под который НЕ написан sed-патч coreBundle.js в том же файле.
Запинено по факту прод-контейнера tradein-browser: camoufox 0.5.5,
playwright 1.60.0, aiohttp 3.14.3. playwright явно, хотя и транзитивный
(camoufox 0.5.5 → playwright<1.61): патч завязан на конкретную сборку драйвера.
Там же переписан комментарий «Апгрейд playwright невозможен — camoufox 0.4.11
pinned»: неверны обе половины. camoufox не был запинен ни на что, а 0.4.11
в проде не стоит с неизвестно каких пор — контейнер сейчас несёт 0.5.5 и
playwright 1.60.0.
Версии сняты с живого прод-контейнера, наличие на PyPI проверено.
The playwright WS-server path (camoufox.server.launch_server) is incompatible
with playwright >=1.45: camoufox 0.4.11's launchServer.js requires
playwright/driver/package/lib/browserServerImpl.js, which no longer exists in
any playwright 1.45-1.60 → tradein-browser crash-looped with MODULE_NOT_FOUND
('Server process terminated unexpectedly'). Verified by running the built image.
Pivot (no playwright protocol between containers):
- browser/server.py: aiohttp service holding a local AsyncCamoufox (headless,
os/locale/geoip/humanize, proxy from SCRAPER_PROXY_URL). POST /fetch {url} →
new_page/goto/content/close → {html}; GET /health. Page-recycle every N +
crash-recovery (relaunch + 1 retry), serialized via asyncio.Lock.
- browser/Dockerfile: +aiohttp (camoufox fetch-as-root/#908 block untouched).
- backend BrowserFetcher: playwright.connect → httpx POST to the browser service
(one retry on transport/HTTP error). All playwright imports dropped.
- config: browser_ws_endpoint → browser_http_endpoint (http://tradein-browser:3000).
Verified by a REAL local image build + run: image builds, container stays Up
(Restarts=0, no crash-loop), GET /health=200, POST /fetch launches camoufox and
drives Firefox (only NS_ERROR_UNKNOWN_HOST = no DNS egress in the local build env,
proving the full HTTP→camoufox→Firefox chain; real fetch validates on prod with
network+proxy). 7 unit tests + ruff clean.
Dormant: scraper_fetch_mode stays curl_cffi. Refs #905, #883, #884
CI build-browser (and the deploy) went red on #907's browser image:
PermissionError: [Errno 13] Permission denied:
'/usr/local/lib/python3.12/site-packages/camoufox/GeoLite2-City.mmdb'
camoufox fetch with geoip=True downloads TWO things: the Firefox build (→
$HOME/.cache, correctly routed to /home/app via ENV HOME, the #899 fix) AND the
GeoLite2 mmdb, which it writes INTO the camoufox package dir in site-packages.
Since the browser image pip-installs camoufox as root, that dir is root-owned, so
running fetch as the app user can't write the mmdb → build fails.
Fix: run fetch as root (writes both the mmdb and — via ENV HOME — Firefox into
/home/app/.cache), then chown the Firefox cache to app. The mmdb stays root-owned
in site-packages but world-readable, which is all the runtime app user needs.
Verified by a real local image build (camoufox fetch now completes, image exports).
The deploy was gated off by the failed build-browser job (not partially applied),
so prod was never broken.
Refs #905, #883, #899