Добавил документацию и описание репозитория
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
# Архитектура
|
||||
|
||||
## Назначение
|
||||
|
||||
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).
|
||||
Reference in New Issue
Block a user