Files
jellybit/docs/specs/architecture.md
T

9.3 KiB
Raw Blame History

Архитектура

Назначение

Jellybit принимает торрент с текстовым контекстом, скачивает его через qBittorrent, определяет содержимое (фильм или сериал с сезонами и сериями) и раскладывает файлы по конвенциям Jellyfin — хардлинками, не трогая исходную раздачу.

Принципы

  • Один статический бинарь. Доставка — копированием на сервер. См. ADR-2026-06-13-go-single-binary.
  • Источник не трогаем. В библиотеку кладём хардлинки; qBittorrent продолжает раздачу, место на диске не дублируется.
  • Единое ядро, тонкие транспорты. Вся логика приёма загрузки — в use-case Ingest. HTTP API, веб-UI и Telegram — обёртки над ним.
  • Опциональные внешние зависимости. Базы метаданных (TMDB/TVDB) включаются конфигом; без них сервис работает на одном LLM.
  • Минимум компонентов. В духе umbar — без лишних сервисов.

Компоненты

Пакет Ответственность
ingest use-case приёма загрузки, общий для всех транспортов
qbt клиент qBittorrent WebUI API
worker фоновый цикл: машина состояний, поллинг завершения
recognize пред-парс имени + вызов LLM + модель уверенности
llm провайдер LLM за интерфейсом (дискриминатор type)
metadata интерфейс баз метаданных + TMDB/TVDB (опц.)
layout конвенции Jellyfin + хардлинкер
store SQLite: загрузки, распознавание, ссылки
httpapi REST + веб-UI (server-rendered, htmx)
tgbot Telegram-адаптер + парсер сообщений торрент-бота
config загрузка TOML-конфига

Поток и машина состояний

ingest → downloading → completed → recognizing ─┬─ уверенно ───────→ linking → done
                                                 └─ сомнительно → review → linking → done
любой шаг при ошибке → failed
  • ingest — приняли источник + контекст, поставили в qBittorrent (категория jellybit), записали в БД.
  • downloading / completedworker поллит qBittorrent по категории (интервал worker.poll_interval, по умолчанию 5 с).
  • recognizingrecognize строит план раскладки и оценку уверенности (см. recognition.md).
  • review — план уходит человеку (веб-UI / Telegram), ждём решения; сценарии — в review-ux.md.
  • linkinglayout создаёт хардлинки в библиотеке.
  • done — опционально дёргаем скан библиотеки Jellyfin.

Состояние персистентно в SQLite — перезапуск сервиса безопасен, worker продолжает с того же места.

Транспорты

Все три ведут в один Ingest(req):

  • HTTP API + веб-UI — форма «добавить», список загрузок, экран подтверждения раскладки (server-rendered + htmx, без JS-сборки).
  • Telegram-бот — переслать magnet или сообщение торрент-бота прямо в jellybit; текст становится контекстом распознавания.
  • CLIjellybit add <magnet> --context "..." для отладки.

Хранилище

SQLite, минимум таблиц:

  • download — источник, контекст, hash торрента, категория, состояние, тайминги.
  • recognition — тип, название, год, сезон, provider-id, оценка уверенности, сырой ответ LLM.
  • file_link — соответствие исходный файл → целевой путь, вид (видео/субтитры), статус.

Конфигурация

TOML. В репозитории — config.example.toml с placeholder'ами; реальный config.toml рендерится при деплое Ansible-шаблоном из переменных umbar (секреты — в vars/secrets.yml под ansible-vault) и не коммитится. Пример:

[qbittorrent]
url      = "http://127.0.0.1:8989"
username = "admin"
password = ""
category = "jellybit"

[paths]
downloads = "/srv/downloads"
movies    = "/srv/media/movies"
series    = "/srv/media/series"

[llm]
# type — дискриминатор реализации; пока поддерживается "openai-compat"
type     = "openai-compat"
base_url = "http://127.0.0.1:1234/v1"
api_key  = ""
model    = "qwen2.5-32b-instruct"

[metadata.tmdb]
enabled = true
api_key = ""

[metadata.tvdb]
enabled = false
api_key = ""

[worker]
poll_interval = "5s"   # как часто опрашивать qBittorrent

[recognition]
auto_confidence_threshold = 0.85

[telegram]
enabled          = false
token            = ""
allowed_user_ids = []

[http]
listen = ":8080"

[log]
level  = "info"
format = "json"

Логирование

Структурированный JSON через log/slog. Каждая загрузка проходит со сквозным идентификатором; решения распознавания (почему авто/ревью) логируются явно.

Деплой

Jellybit работает в docker — в одной среде с qBittorrent и Jellyfin (они тоже в контейнерах на umbar). Единая среда запуска перевешивает простоту нативного systemd.

Сборка — дёшево и сердито: статический бинарь собирается здесь; на сервер во временную build-папку кладутся бинарь + Dockerfile (он просто копирует бинарь в минимальный образ), образ собирается прямо на сервере и запускается. Go-тулчейн на сервере не нужен — только docker.

Разделение ответственности:

  • jellybit (этот репозиторий) — производит статический бинарь и Dockerfile.
  • umbar — оркестрация деплоя: доставка артефактов, docker build и запуск через docker compose (playbook-jellybit.yml).

Раскладка файлов

Хардлинки в paths.movies / paths.series по конвенциям Jellyfin. Детали и крайние случаи — в jellyfin-layout.md. /srv/downloads и /srv/media — одна ФС (подтверждено), поэтому хардлинки применимы. Так как jellybit в docker (см. «Деплой»), контейнеру монтируем общего родителя /srv — чтобы внутри оба каталога остались на одной ФС и хардлинк проходил.

Предполагаемая структура репозитория

cmd/jellybit/         точка входа, сборка зависимостей
internal/
  ingest/  qbt/  worker/  recognize/  llm/  metadata/
  layout/  store/  httpapi/  tgbot/  config/
migrations/           миграции SQLite
web/templates/        шаблоны веб-UI
docs/                 specs / adr / drafts
config.example.toml

Решённые вопросы

  • /srv/downloads и /srv/media — одна ФС (подтверждено); хардлинки применимы.
  • Детект завершения — поллинг qBittorrent раз в несколько секунд (worker.poll_interval). Webhook — возможная оптимизация на будущее (drafts/ideas.md).
  • Секреты — в переменных umbar; config.toml рендерится Ansible-шаблоном при деплое.
  • Форма запуска — docker, образ собирается на сервере из готового бинаря (см. «Деплой»).

Открытые вопросы

Существенных пока нет.