Files
gilfanovrandClaude Opus 5 a65d6a249e imgbot: стек из двух сервисов вместо процессов на хосте
Проект агента (~tgclaude/imgbot) работал тремя голыми python-процессами,
запущенными руками из SSH-сессии: без юнита, без restart-политики, при
Linger=no. Держались только на KillUserProcesses=no и не пережили бы
перезагрузку, причём молча.

Один образ на базе playwright/python:v1.62.0-noble (версия пакета
playwright обязана совпадать с тегом, иначе разъезжается ревизия
chromium), два сервиса:
  - imgbot-daemon — APScheduler: refresh 30 мин, analyze 5 мин
  - imgbot-http   — объединил прежние mcp-http (8765) и file-server
    (8766): MCP как под-приложение Starlette плюс маршрут /photo/<id>

Неочевидное, из-за чего это ломается молча (в compose есть комментарии):
  - данные монтируются в тот же путь, что на хосте: images.file_path в
    SQLite абсолютный, и его читают обе стороны
  - корней хранения может быть больше одного — проверять все префиксы
    file_path, а не только data_dir
  - HOME обязателен: collector.py вызывает Path.home(), а uid 1001 не
    имеет записи в /etc/passwd образа
  - у MCP своя защита от DNS-rebinding: без IMGBOT_ALLOWED_HOSTS отдаёт
    421 на любой Host кроме localhost

Порт не публикуется на хост, сервис доступен из общей docker-сети с
claude-channels — агент наконец ходит по MCP, а не по SSH.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 15:26:16 +03:00
..

imgbot-daemon

Контейнер для фонового демона проекта imgbot — headless-поиска и скачивания картинок. Проект принадлежит агенту, живёт в /home/tgclaude/imgbot и правится им же; здесь только среда запуска и присмотр.

Зачем

До этого демон работал как голый процесс на хосте: запущен руками из SSH-сессии, PPID=1, без юнита и restart-политики, Linger=no у пользователя. Держался только потому, что в Ubuntu KillUserProcesses=no — после перезагрузки не поднялся бы, и сигнала об этом не было бы. Плюс его 15% ядра не были ничем ограничены и не были видны мониторингу.

Контейнер даёт: автозапуск и restart: unless-stopped, потолки CPU/памяти, видимость в cAdvisor (а значит и в Grafana), и снимает костыль с руками распакованными библиотеками chromium.

Что тут НЕ запускается и почему

Два других процесса проекта — imgbot-mcp-http (порт 8765) и imgbot-file-server (8766) — оставлены вне контейнеров сознательно:

  • оба жёстко забиндены в коде на 172.22.0.1 — адрес шлюза docker-сети openclaw_default. В контейнере такой адрес не поднять, а сама привязка ломается при пересоздании сети;
  • до них всё равно никто не дотягивается: единственный живой потребитель — агент в контейнере claude-channels — находится в другой docker-сети и ходит через SSH, а не по HTTP (проверено: curl из контейнера даёт 000);
  • imgbot-mcp-http вообще без авторизации, в отличие от file-server с его Bearer-токеном.

Их имеет смысл контейнеризовать только вместе с правкой бинда на 0.0.0.0 (две строки: HOST в file_server.py, host= в mcp_server.py) и уже в общей docker-сети с claude-channels — тогда агент получит MCP по токену вместо SSH. Это решение владельца кода, не инфраструктуры.

Запуск

Собрать и поднять (на сервере каталог стека — ~/imgbot-daemon/, чтобы не путался с самим проектом в ~tgclaude/imgbot):

docker compose build
# погасить запущенный руками демон, иначе будет два и двойной analyze:
pkill -u tgclaude -f imgbot-daemon
docker compose up -d
docker logs -f imgbot-daemon

Проверка, что расписание пошло: в ~tgclaude/imgbot-data/daemon.log должны появляться строки _job_analyze ... executed successfully каждые 5 минут и _job_refresh каждые 30.

Откат

docker compose down
# и вернуть как было, из-под tgclaude:
cd ~/imgbot && .venv/bin/imgbot-daemon >> ~/imgbot-data/daemon.log 2>&1 &

Важно для владельца кода

Перезапуск демона теперь docker restart imgbot-daemon, а не pkill плюс ручной старт. Если запустить процесс руками при живом контейнере — получатся два демона, которые будут конкурировать за одни и те же строки SQLite (в проекте это уже случалось с cron без flock и выглядело как зависание).

Правки кода пересборки не требуют: исходники монтируются read-only, достаточно docker restart imgbot-daemon. Пересобирать нужно только при изменении зависимостей в pyproject.toml — и тогда версию playwright в Dockerfile надо держать совпадающей с uv.lock, иначе разойдётся ревизия браузера.