Пять независимых bugfix'ов из ревью приёма (docs/backlog/review-f7-f10-ingest-ui-fixes.md): - F7: oversized .torrent через веб отдавал 500. Введён sentinel ingest.ErrTorrentTooLarge, classifyErr транслирует его в 400. - F8: гонка fast-path attach с cancel. Пред-рид FindReingestBlockingByInfohash больше не короткозамыкает активную запись — авторитетное дедуп-решение принимает CreateDownloadIfNoActive под BEGIN IMMEDIATE; короткозамыкание оставлено только для терминальных desync-записей (target_missing/orphaned). F6-апгрейд сохранён. - F9: magnet — регистронезависимый URN-префикс xt (RFC 2141); tgbot.ParseMessage срезает хвостовую пунктуацию, приклеенную жадным matchем. - F10: cap контекста до 16 KiB в ingest.Ingest (единственное место слияния — покрывает все транспорты), рунобезопасная обрезка + маркер. - N2: httpapi.shorten режет по рунам, не байтам — кириллица не рвётся в U+FFFD. Добавлены юнит-тесты на каждое исправленное поведение. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Jellybit
Jellybit — связующий сервис между qBittorrent и Jellyfin. Принимает magnet-ссылку вместе с текстовым контекстом, ставит загрузку в qBittorrent, дожидается её завершения, распознаёт содержимое (фильм или сериал, сезоны и серии) и раскладывает готовые файлы по конвенциям библиотеки Jellyfin.
Полный замысел и причины — в BRIEF.md.
Зачем
Arr-стек (prowlarr/radarr/sonarr) плохо ложится на русские трекеры, аниме и ручные раздачи. Jellybit намеренно сокращает путь: одна точка входа → готовая раскладка для Jellyfin, без каталога индексаторов и сложных правил качества. Распознавание делает LLM, которому помогает переданный человеком контекст и (опционально) внешние базы метаданных.
Как работает
- Точка входа принимает magnet + контекст (HTTP API, веб-UI, Telegram-бот или CLI).
- Загрузка ставится в qBittorrent в выделенную категорию.
- Сервис отслеживает завершение загрузки.
- По именам файлов, контексту и (опц.) базам метаданных определяется фильм/сериал и нужная раскладка.
- Файлы хардлинкаются в библиотеку Jellyfin — источник остаётся в раздаче, место на диске не дублируется.
- После раскладки сервис (опц.) просит Jellyfin пересканировать медиатеку, чтобы новые файлы быстрее появились в проигрывателе.
При высокой уверенности раскладка выполняется автоматически, иначе — уходит на подтверждение человеку.
Статус
Рабочий прототип с полным сквозным путём: приём magnet → загрузка в
qBittorrent → распознавание (LLM + опционально базы метаданных
TMDB/TVDB/TVMaze) → раскладка в библиотеку хардлинками, автоматически при
уверенном результате либо через подтверждение человеком. Транспорты приёма:
REST API, веб-UI, Telegram-бот и CLI (jellybit add).
Из источников пока поддержан magnet; .torrent и обычные ссылки — в планах.
См. дорожную карту.
Документация
Разработка идёт по Spec-Driven Development через OpenSpec: изменение сначала описывается спекой, потом реализуется.
- openspec/ — OpenSpec: актуальные capability-спеки в
openspec/specs/, изменения (proposal → design → tasks → archive) вopenspec/changes/. Capabilities постепенно переносятся сюда изdocs/specs/. - docs/conventions/ — конвенции кода (как пишем): логирование (logging.md), конфигурация (config.md), ошибки (errors.md).
- docs/specs/ — спецификации устройства системы (архитектурный обзор + ещё не перенесённые в OpenSpec темы). Начать с architecture.md.
- docs/adr/ — журнал архитектурных решений (почему так).
- docs/drafts/ — черновики: планы, идеи, нерешённое.
Стек
Go (один статический бинарь), SQLite (modernc.org/sqlite + sqlx,
миграции goose), HTTP — chi + html/template + htmx, конфигурация —
TOML, логи — структурированный JSON (slog). Подробнее — в
architecture.md.
Конфигурация
Конфигурация — один файл TOML. По умолчанию ищется config.toml в рабочей
директории; путь переопределяется опцией --config=path. Образец со всеми
секциями и описанием каждого поля (назначение, диапазон значений, единицы
измерения) — config.example.toml; скопируй его в
config.toml и заполни под себя. Конфиг валидируется на старте: при
ошибке сервис не стартует.
Секреты (пароль qBittorrent, ключи LLM/метабаз, токен Telegram) в репозиторий
не коммитятся — их подставляет деплой прямо в файл. Доступ к внешним сервисам
(LLM, базы метаданных, Telegram) при необходимости идёт через HTTP-прокси —
поле proxy в соответствующих секциях. Правила — в
docs/conventions/config.md.
Разработка
Нужны Go 1.26 и Task. Полный список задач —
task --list.
cp config.example.toml config.toml # локально: db_path -> ./jellybit.db
task setup # golangci-lint + git-хуки lefthook
task tidy # go mod tidy
task run # go run ./cmd/jellybit --config ./config.toml
task test lint # тесты и golangci-lint
task build # статический бинарь (linux/amd64) для сервера
task image # docker-образ из готового бинаря
Отладка распознавания на реальной раздаче (только чтение, без раскладки):
jellybit recognize <infohash> --dry-run [--context "..."] --config ./config.toml
Берёт торрент из qBittorrent по infohash, прогоняет распознавание (LLM + метабазы) и печатает план: тип/название/год, матч в базе, решение авто/review и превью целевых путей — то, что создалось бы при Apply.
Доставка
Рассчитан на домашний медиа-сервер. Артефакты репозитория — статический
бинарь (task build) и Dockerfile (упаковка в distroless/static). Образ
собирается на сервере из доставленного бинаря, поэтому Go-тулчейн на
сервере не нужен. В distroless нет shell/curl, поэтому HEALTHCHECK зовёт сам
бинарь: jellybit healthcheck (GET /healthz по порту из конфига, exit 0/1).
Контейнер: user 1000:1000, порт 8080 на хост, mount /srv/media (единая
песочница для хардлинков) + том /config (ro, config.toml, восстановим при
деплое) + data-том /data (SQLite, бекапить); к qBittorrent — по сети Docker.
Конкретная деплой-обвязка (плейбук, секреты) держится в отдельном приватном
репозитории и в комплект не входит.