156 lines
7.0 KiB
Markdown
156 lines
7.0 KiB
Markdown
# Архитектура
|
|
|
|
## Назначение
|
|
|
|
Jellybit принимает торрент с текстовым контекстом, скачивает его через
|
|
qBittorrent, определяет содержимое (фильм или сериал с сезонами и
|
|
сериями) и раскладывает файлы по конвенциям Jellyfin — хардлинками, не
|
|
трогая исходную раздачу.
|
|
|
|
## Принципы
|
|
|
|
- **Один статический бинарь.** Доставка — копированием на сервер. См.
|
|
[ADR-2026-06-13-go-single-binary](../adr/ADR-2026-06-13-go-single-binary.md).
|
|
- **Источник не трогаем.** В библиотеку кладём хардлинки; qBittorrent
|
|
продолжает раздачу, место на диске не дублируется.
|
|
- **Единое ядро, тонкие транспорты.** Вся логика приёма загрузки — в
|
|
use-case `Ingest`. HTTP API, веб-UI и Telegram — обёртки над ним.
|
|
- **Опциональные внешние зависимости.** Базы метаданных (TMDB/TVDB)
|
|
включаются конфигом; без них сервис работает на одном LLM.
|
|
- **Минимум компонентов.** В духе umbar — без лишних сервисов.
|
|
|
|
## Компоненты
|
|
|
|
| Пакет | Ответственность |
|
|
| ----------- | ----------------------------------------------------- |
|
|
| `ingest` | use-case приёма загрузки, общий для всех транспортов |
|
|
| `qbt` | клиент qBittorrent WebUI API |
|
|
| `worker` | фоновый цикл: машина состояний, поллинг завершения |
|
|
| `recognize` | пред-парс имени + LLM + модель уверенности |
|
|
| `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 по категории.
|
|
- **recognizing** — `recognize` строит план раскладки и оценку
|
|
уверенности (см. [recognition.md](recognition.md)).
|
|
- **review** — план уходит человеку (веб-UI / Telegram), ждём решения.
|
|
- **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, секреты — placeholder'ы; реальный конфиг не коммитим. Пример:
|
|
|
|
```toml
|
|
[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]
|
|
provider = "anthropic"
|
|
model = "claude-sonnet-4-6" # сложные случаи — claude-opus-4-8
|
|
api_key = ""
|
|
|
|
[metadata.tmdb]
|
|
enabled = true
|
|
api_key = ""
|
|
|
|
[metadata.tvdb]
|
|
enabled = false
|
|
api_key = ""
|
|
|
|
[recognition]
|
|
auto_confidence_threshold = 0.85
|
|
|
|
[telegram]
|
|
enabled = false
|
|
token = ""
|
|
allowed_user_ids = []
|
|
|
|
[http]
|
|
listen = ":8080"
|
|
|
|
[log]
|
|
level = "info"
|
|
format = "json"
|
|
```
|
|
|
|
## Логирование
|
|
|
|
Структурированный JSON через `log/slog`. Каждая загрузка проходит со
|
|
сквозным идентификатором; решения распознавания (почему авто/ревью)
|
|
логируются явно.
|
|
|
|
## Раскладка файлов
|
|
|
|
Хардлинки в `paths.movies` / `paths.series` по конвенциям Jellyfin.
|
|
Детали и крайние случаи — в [jellyfin-layout.md](jellyfin-layout.md).
|
|
Требование: `downloads` и `media` на одной ФС (иначе хардлинк
|
|
невозможен). Если jellybit в docker — смонтировать общего родителя,
|
|
чтобы хардлинк работал внутри контейнера.
|
|
|
|
## Предполагаемая структура репозитория
|
|
|
|
```
|
|
cmd/jellybit/ точка входа, сборка зависимостей
|
|
internal/
|
|
ingest/ qbt/ worker/ recognize/ metadata/
|
|
layout/ store/ httpapi/ tgbot/ config/
|
|
migrations/ миграции SQLite
|
|
web/templates/ шаблоны веб-UI
|
|
docs/ specs / adr / drafts
|
|
config.example.toml
|
|
```
|
|
|
|
## Открытые вопросы
|
|
|
|
- Подтвердить, что `/srv/downloads` и `/srv/media` — одна ФС.
|
|
- Способ детекта завершения: поллинг (старт) или webhook qBittorrent
|
|
«run on completion» (позже).
|
|
- Где хранить секреты при деплое (шаблонизация из umbar).
|