Проект агента (~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>
69 lines
4.5 KiB
Markdown
69 lines
4.5 KiB
Markdown
# 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`, иначе разойдётся ревизия браузера.
|