Files
vesti/STATUS.md
T

118 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# VESTI — Статус
Обновлено: 2026-09-13 (publisher снова на Docker; systemd1 ожил)
<!--
История обновлений:
2026-09-13 — publisher: Docker-контейнер (основной способ); user-юнит disabled (костыль убран)
2026-09-12 — publisher → systemd user-юнит (костыль, пока сломан systemd1)
2026-09-11 — веб-интерфейс: 4 openspec change (CDN/htmx, markdown, даты, фильтр без CDN)
-->
## Текущее состояние
- Базовый конвейер (прототип) работает: краулер (8 источников, инкрементальный, метрики), классификатор (Ollama qwen3:8b-nothink + словари), банк статей (bundles + origin:own), веб :8400 (кандидаты/approve/reject/опубликованные/метрики).
- **Бэкфилл @dedinit ЗАВЕРШЁН** (2026-09-10): crawler/backfill_dedinit.py, fetched=1040, max_post_id=1092; в БД 846 постов is_own=1, медиа в media/media/ (исторический баг пути: БД media/<file>, файл media/media/<file>; с 2026-09-12 краулер качает в media/<file>, для старых — fallback в make_card). Скрипт оставлен для повторного дозаполнения.
- **РЕАЛЬНАЯ ПУБЛИКАЦИЯ РАБОТАЕТ**: бот-контроллер @dedinit_controller_bot (id 7765665742) → @dedinit_vesti. Токен в .env. Проверено: тестовые посты (message_id 2–4).
- **publisher-service**: **Docker-контейнер** `vesti-publisher` (docker compose -f services/publisher/docker-compose.yml, 127.0.0.1:8410, restart=unless-stopped, healthy). Compose: env из /.env, TG_PROXY=socks5://host.docker.internal:1080, маунт ../../media/media:/srv/publisher/media:ro. Старый user-юнит `vesti-publisher.service` (~/.config/systemd/user/) — **disabled, не используется** (костыль на время сломанного systemd1, порт 8410 конфликтовал бы).
- **Веб :8400**: systemd-юнит `/etc/systemd/system/vesti-web.service` (**enabled**, автостарт), uvicorn web.app:app на 0.0.0.0:8400, пароль из .env (ADMIN_PASSWORD). Доступен снаружи: https://vesti.nixg.ru.
- **Cron (Hermes, no_agent, тихие)**: vesti-crawler-all-sources (`*/30`), vesti-watchdog (`*/15`, Telegram при проблемах).
- **Бэкап**: `/opt/vesti/backup.sh` (по образцу icq/netbox) → `backups/vesti_<дата>.tar.gz` + Яндекс.Диск `/mnt/yandex-disk/backup/vesti-backups/`. Root cron `45 2 * * *` (лог /var/log/vesti-backup.log). Локально 7 дней, на ЯД 30 дней.
## Архитектура (реальная)
```
vesti-web (:8400) ──HTTP POST──▶ publisher-service (:8410) ──Bot API (SOCKS5 127.0.0.1:1080)──▶ Telegram
approve (человек) VESTI_BOT_TOKEN, VESTI_BOT_CHANNELS @dedinit_vesti (+ другие)
```
- Бот-контроллер @dedinit_controller_bot публикует во все каналы, в которые добавлен администратором.
- Каналы из конфига: VESTI_BOT_CHANNELS (сейчас @dedinit_vesti, потом несколько).
- publisher-service: POST /api/v1/publish (card {text, media?, direction, lang}, channels?), GET /healthz, GET /api/v1/views/{channel}/{mid}.
## Сделано (сессия web-ui 2026-09-11)
- [x] **Внешний доступ через Caddy**: https://vesti.nixg.ru (VPS02, /opt/caddy/Caddyfile, reverse_proxy 10.8.0.2:8400); юнит веба переведён на 0.0.0.0:8400; LE-серт выпущен
- [x] **4 OpenSpec change** (по одному на замечание пользователя), все валидны (`openspec validate`):
- `deexternalize-web-assets` — удалены unpkg (htmx 1.9.12) и jsdelivr (bootstrap 5.3.3); bootstrap локализован в web/static/bootstrap.min.css; approve/reject → обычные POST-формы. 0 внешних доменов в шаблонах.
- `web-render-markdown` — Python-Markdown (>=3.6) + Jinja2-фильтр `markdown` (XSS-safe: escape→nl2br→sane_lists); тексты кандидатов/опубликованных рендерятся
- `web-format-datetime` — фильтр `dt` → `ЧЧ:ММ ДД.ММ.ГГГГ` (candidates/published/metrics), ISO `[:16]` убран
- `web-list-filter-no-js` — фильтрация чистыми GET-формами без JS/CDN (кнопка «Применить»); ожидания unpkg нет
- [x] **Баг бандлов исправлен**: `create_bundle` возвращал абсолютный путь → в published.bundle_path лежал `/opt/vesti/bundles/...` → ссылки `/bundle//opt/...` (двойной слэш). Теперь относительный путь от bundles/ (`linux/2026-09/slug.md`), 4 строки БД обновлены, роут `/bundle/{path}` работает (HTTP 200)
- [x] **AGENT.MD** — правила проекта (все изменения через OpenSpec и т.д.)
## Сделано (сессия publisher-service)
- [x] OpenSpec change `publisher-service` — создан и ВАЛИДЕН (proposal, design, spec tg-publisher-service, tasks); `openspec validate` чисто
- [x] services/publisher/ — FastAPI-микросервис: app/{main,config,telegram,channels}.py, requirements.txt, Dockerfile, docker-compose.yml, .env.example
- [x] httpx[socks] установлен в venv (для SOCKS5-прокси к Bot API)
- [x] Токен записан в .env (VESTI_BOT_TOKEN), чтение починил (load_dotenv override + путь BASE)
- [x] Сервис запущен (venv, 127.0.0.1:8410); healthz: bot=dedinit_controller_bot, proxy=socks5://127.0.0.1:1080, channels=[@dedinit_vesti]
- [x] **Реальная публикация**: POST /api/v1/publish → ok, message_id=2 в @dedinit_vesti (проверено)
- [x] web/app.py: approve → HTTP-клиент web/publisher_client.py (publish + views), импорт publisher.bot убран
- [x] .env.example обновлён (VESTI_BOT_CHANNELS, TG_PROXY, PUBLISHER_URL)
- [x] **Docker publisher**: compose (пути ../../, extra_hosts host.docker.internal, TG_PROXY override, load_dotenv override=False), контейнер Up (healthy), healthz: bot/proxy/channels — ок (2026-09-09)
- [x] **Реальный approve через веб** (сквозной: веб → HTTP → publisher(Docker) → Telegram): посты 136, 137 → @dedinit_vesti, tg_message_id 3 и 4, бандлы и статусы записаны
- [x] **Веб-баги**: 500 на /candidates (raise RedirectResponse → HTTPException 303) и tg_message_id=0 для внешних (ключ результата — канал, не направление) — исправлены
## Сделано (сессия rich-repost-card 2026-09-12)
- [x] **OpenSpec change `rich-repost-card`** — создан и ВАЛИДЕН (`openspec validate` чисто): proposal/design/tasks/specs/tg-publisher
- [x] **Формат публикации — «богатый репост»** (publisher/card.py): карточка = [комментарий модератора] + полный текст поста + ссылка на оригинал. Служебка (📁📰👁) убрана. Текст plain (не HTML) — сырые <,>,& не искажаются
- [x] **Предпросмотр ссылки отключён** (services/publisher/app/telegram.py): sendMessage → link_preview_options={is_disabled:true}; канал больше не выглядит «агрегатором ссылок»
- [x] **Медиа правится**: main.py — если media_path существует: медиа первым (caption = первые 1000 симв., лимит 1024), полный текст отдельным сообщением; без медиа — один текст
- [x] **Вебе добавлено поле «Комментарий»** (web/app.py + candidates.html): при approve комментарий встаёт первым блоком в публикацию
- [x] **Фикс бага медиа-пути краулера** (crawler/telegram_crawler.py + backfill_dedinit.py): файл скачивался в media/media/<file>, а media_path в БД = media/<file> → publisher не находил файл, медиа молча не публиковалось. Теперь скачивание в MEDIA_DIR/<basename> (= media/<file>). Для старых постов — fallback в make_card (пробует media/ и media/media/)
- [x] telegram.py: добавлен delete_message (для отмены/чистки тестовых)
- [x] **Проверено на реальном канале**: publish ушёл в @dedinit_vesti (message_id 6), текст с <b>&amp; доставлен как есть, без превью; тестовые сообщения удалены (delete_message ok). Канал чист
- [x] **publisher временно работает как локальный uvicorn** (127.0.0.1:8410, PID 807769) — Docker недоступен из-за зависшего systemd1 на хосте (внешняя проблема, не vesti)
## Сделано ранее (own-content-hub)
- [x] Миграция БД (migrate_own.py): is_own, is_own_canonical, sources.own, published.distributed_dirs
- [x] sources.yaml: +dedinit (own: true), синк
- [x] Краулер own-семантика; классификатор critical + мультинаправления; карточка с атрибуцией; веб «Свои»/бейдж/чекбоксы; бандлы origin:own
- [x] Тест e2e (dry-run): approve с dirs [linux,ai] → /published с distributed_dirs
## В работе / Следующие шаги
- [x] Бэкфилл своего канала @dedinit (задача 2.3) — **ЗАВЕРШЁН** (2026-09-10): fetched=1040, max_post_id=1092, 846 постов is_own=1 в БД
- [x] publisher: медиа при Docker-запуске — **исправлено**: compose маунтит ../../media/media:/srv/publisher/media:ro
- [x] systemd vesti-web.service + cron per-source + тихий watchdog — **юнит установлен** (подхватится при перезагрузке); **cron**: vesti-crawler-all-sources (`*/30`, тихий) + vesti-watchdog (`*/15`, Telegram при проблемах)
- [x] Классификатор по неклассифицированным — **запущен** (2026-09-10, 870 постов, фон; статус в /tmp/vesti-classify.log)
- [ ] Проверить результат классификации (в БД: direction заполнены) и выборочно качество на своих постах
- [ ] **rich-repost-card в проде**: после оживления systemd1 на хосте пересобрать Docker-контейнер publisher (docker compose -f services/publisher/docker-compose.yml up -d --build), перезапустить веб (подхватит шаблон + comment), проверить approve поста с медиа (send_photo/send_video)
- [ ] **Закрыть OpenSpec change rich-repost-card** (статус APPLIED после подтверждения), архивировать
- [ ] git-репозитории (gitverse истина, gitea зеркало) — /opt/vesti ещё НЕ git-репо
- [ ] openspec archive web-изменений (deexternalize-web-assets, web-render-markdown, web-format-datetime, web-list-filter-no-js) и publisher-service / tg-crawler-publisher-prototype (после подтверждения)
## Как запустить / проверить
```bash
cd /opt/vesti
# publisher-service (ОСНОВНОЙ способ — Docker):
docker compose -f services/publisher/docker-compose.yml up -d --build # контейнер vesti-publisher, :8410
curl -s http://127.0.0.1:8410/healthz # ok, bot=dedinit_controller_bot, proxy=socks5://host.docker.internal:1080, channels
docker logs -f vesti-publisher # логи
curl -s -X POST http://127.0.0.1:8410/api/v1/publish \
-H 'Content-Type: application/json' \
-d '{"card":{"text":"<b>Тест</b>","direction":"linux","lang":"ru"}}'
# (старый user-юнит vesti-publisher.service disabled — не использовать, порт занят)
# веб (пароль из .env: VESTI_WEB_PASSWORD or ADMIN_PASSWORD):
# systemd-юнит: /etc/systemd/system/vesti-web.service (после daemon-reload)
# вручную: set -a; . ./.env; set +a; .venv/bin/uvicorn web.app:app --host 127.0.0.1 --port 8400
# краулер/классификатор:
.venv/bin/python -m crawler.telegram_crawler --all # инкрементально все источники
.venv/bin/python -m crawler.backfill_dedinit # бэкфилл своего канала (разовый)
CLASSIFY_TIMEOUT=20 .venv/bin/python -m classifier.classify --db db/vesti.db --limit 200
```
## Ключевые артефакты
- /opt/vesti/AGENT.MD — правила проекта (обязательно: все изменения через OpenSpec)
- /opt/vesti/openspec/changes/{deexternalize-web-assets, web-render-markdown, web-format-datetime, web-list-filter-no-js}/ — 4 change веб-интерфейса (валидны, задачи [x])
- /opt/vesti/openspec/changes/rich-repost-card/ — change формата публикации (proposal/design/tasks/specs/tg-publisher, валиден)
- /opt/vesti/services/publisher/ — микросервис (app/, Dockerfile, compose, README)
- /opt/vesti/web/publisher_client.py — HTTP-клиент веба к микросервису
- /opt/vesti/openspec/changes/publisher-service/ — OpenSpec change (proposal/design/spec/tasks)
- publisher/bot.py — старое (логика перенесена в services/publisher/app/telegram.py)
- publisher/card.py — карточка (осталась)
- db/vesti.db, sources/sources.yaml, crawler/, classifier/, web/, bundles/
## Открытые вопросы
- ~~Медиа при Docker-запуске~~ — решено: compose маунтит ../../media/media:/srv/publisher/media (веб шлёт media/<file>)
- **publisher в проде = Docker** (2026-09-13): systemd1 (D-Bus org.freedesktop.systemd1) после перезагрузки ожил, контейнер vesti-publisher (compose, restart=unless-stopped) работает healthy. User-юнит vesti-publisher.service выключен (disabled) и не используется. Если снова сломается systemd1 — вернуть юнит: systemctl --user enable --now vesti-publisher.service.
- Fan-out: сейчас все каналы из VESTI_BOT_CHANNELS (один). В будущем — направления→каналы (маппинг).
- ~~Бэкфилл канала (задача 2.3)~~ — ЗАВЕРШЁН (2026-09-10): 1040 постов, 846 в БД is_own=1
- git-репозиторий /opt/vesti — инициализировать?
- systemd daemon-reload на bigbox зависает (внешняя проблема, не vesti) — юнит подхватится при перезагрузке
- Качество классификации своих постов (846, словари vs LLM) — проверить выборочно