9.3 KiB
Архитектура
Назначение
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 / completed —
workerполлит qBittorrent по категории (интервалworker.poll_interval, по умолчанию 5 с). - recognizing —
recognizeстроит план раскладки и оценку уверенности (см. recognition.md). - review — план уходит человеку (веб-UI / Telegram), ждём решения; сценарии — в review-ux.md.
- linking —
layoutсоздаёт хардлинки в библиотеке. - done — опционально дёргаем скан библиотеки Jellyfin.
Состояние персистентно в SQLite — перезапуск сервиса безопасен, worker
продолжает с того же места.
Транспорты
Все три ведут в один Ingest(req):
- HTTP API + веб-UI — форма «добавить», список загрузок, экран подтверждения раскладки (server-rendered + htmx, без JS-сборки).
- Telegram-бот — переслать magnet или сообщение торрент-бота прямо в jellybit; текст становится контекстом распознавания.
- CLI —
jellybit 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, образ собирается на сервере из готового бинаря (см. «Деплой»).
Открытые вопросы
Существенных пока нет.