Files
obsidian-vault/family/how-to/arr-stack-kraken.md
T

61 KiB
Executable File
Raw Blame History

title, created, updated, type, namespace, tags, related
title created updated type namespace tags related
Arr Stack — Kraken 2026-05-23 2026-09-22 tech family
arr
radarr
sonarr
prowlarr
transmission
jellyfin
infra
kraken
tech/jellyfin-config
tech/arr-stack-taiga
tech/prowlarr-flaresolverr-cloudflare
concepts/kraken-media-stack
personal/tech/lampac-deployment
family/how-to/lampac-torrents
family/projects/watchlist-automation

Arr Stack — Kraken

Pipeline

Prowlarr → Radarr + Sonarr → Transmission → Jellyfin
                                   ↓
                            router.py (cron 10min)
                                   ↓
                      symlinks → /media/{movies,cartoons,series,documentaries,...}/

Два параллельных пути поступления контента:

  1. Radarr/Sonarr → качают в /media/movies-radarr/ и /media/series-sonarr/
  2. Transmission (ручные торренты) → on-download-complete.sh → sync.py → /media/{movies,cartoons,...}/

router.py — жанровая маршрутизация

router.py читает NFO-файлы (которые Radarr/Sonarr создают при загрузке), определяет жанр и возрастной рейтинг, и создаёт symlink в нужную Jellyfin-библиотеку:

Источник Условие Цель
movies-radarr/ Documentary /media/documentaries/
movies-radarr/ Animation + !R/18+ /media/cartoons/
movies-radarr/ всё остальное /media/movies/
series-sonarr/ Documentary /media/documentaries-series/
series-sonarr/ Animation + !TV-MA /media/cartoons-series/
series-sonarr/ всё остальное /media/series/

Расположение: /srv/dev-disk-by-uuid-49e8f586-3839-4c5d-a1e1-58bfc3579ade/docker/media-pipeline/router.py Запуск: python3 router.py --dry-run (проверка) / python3 router.py --apply (исполнение)

media-router.sh — cron-скрипт для Telegram-уведомлений

Обёртка над router.py, которая:

  • запускает router.py --apply
  • если Nothing to do — SILENT, ничего не шлёт
  • если созданы symlink'и или ошибка — отправляет отчёт в Telegram Kraken

Расположение: рядом с router.py — media-router.sh Cron (Kraken crontab): каждые 10 минут Telegram: kraken_htpc_bot → чат @mallex87

sync.py — обработка ручных торрентов (Transmission)

sync.py работает внутри Transmission-контейнера. При завершении загрузки:

  1. Определяет название через TMDB API
  2. Определяет жанр
  3. Перемещает файл в /media/{movies,cartoons,series,documentaries}/
  4. Обновляет downloadDir в Transmission через RPC

Расположение: /srv/.../docker/media-pipeline/sync.py Триггер: on-download-complete.sh (смонтирован в Transmission как /config/on-download-complete.sh:ro)

Transmission

  • download-dir: /downloads (без complete)
  • incomplete-dir: /downloads/incomplete
  • Язык: отключён auth для локальных
  • RPC: localhost:9091

При перемещении файлов для уже завершённых торрентов:

  1. Переименовать файл/папку под кейс торрента (Linux ext4 case-sensitive)
  2. Вызвать torrent-set-location с move=false
  3. Запустить торрент на сидирование

Cron'ы на Kraken (crontab)

✅ Watchlist-крон ВОССТАНОВЛЕН 2026-09-20 21:21 (был мёртв с 2026-06-20). Бэкап ~/crontab-backup-20260920-212155.txt. Детали и разбор — в family/projects/watchlist-automation.

Актуальное crontab -l юзера kraken (проверено 2026-09-22):

0  *  * * *  bash ~/scripts/sync-vault.sh                     # Obsidian sync
0  *  * * *  pgs-to-srt.sh /media/downloads/complete           # PGS→SRT новые
0  3  * * *  pgs-to-srt.sh /media                              # PGS→SRT вся библиотека
0  2  * * *  bash ~/scripts/watchlist-cleanup.sh --apply      # cleanup просмотренного
0  1  * * *  bash ~/scripts/watchlist-nightly.sh              # watchlist nightly (БЕЗ sync-down)
0  9  * * 0  bash ~/scripts/watchlist-discover.sh             # watchlist discover
30 2  * * *  bash ~/scripts/watchlist-catalog.sh              # JSON-каталог для Lampa ← НОВЫЙ 2026-09-22

🔴 *watchlist-catalog.sh (30 2 * * ) — генерация watchlist.json для модуля Catalog в Lampa + маркера ❓. Порядок намеренный: nightly 01:00 → cleanup 02:00 → catalog 02:30. Детали: personal/tech/lampac-deployment, разбор генератора — personal/plans/watchlist-tmdb-missing-marker.

⚠️ media-router.sh (роутер symlink'ов, каждые 10 мин) в crontab ОТСУТСТВУЕТ. В таблице «Два параллельных пути» выше он описан как работающий по cron 10min — фактически его там нет. Проверить, нужен ли: без него symlink'и в /media/{movies,cartoons,...}/ не создаются автоматически.

Точная проверка наличия любого крон-джоба (не верить доке, смотреть систему):

crontab -l                                    # юзер kraken
sudo crontab -l                               # root
sudo ls -la /var/spool/cron/crontabs/         # все юзерские
sudo grep -rl watchlist /var/spool/cron/ /etc/cron.d/ /etc/crontab
systemctl --user list-timers --all ; systemctl list-timers --all | grep -i watchlist
systemctl is-active cron                      # демон жив?

Установка крон-джоба безопасно (с бэкапом):

crontab -l > ~/crontab-backup-$(date +%Y%m%d-%H%M%S).txt
cp ~/crontab-backup-*.txt /tmp/crontab-new.txt
# дописать строки в /tmp/crontab-new.txt
crontab /tmp/crontab-new.txt
crontab -l                     # проверка
grep -c watchlist /var/spool/cron/crontabs/kraken   # демон подхватил?

API Keys

Service API Key
Radarr cbcb8ec3...
Sonarr 15fbec32...
Prowlarr 134ac38a...

(Full keys truncated — retrieve from each service's settings page.)

Качество загрузки для НОВЫХ фильмов/сериалов (2026-09-04)

watchlist-sync (автодобавление из watchlist) использует профиль WEB-4K (no remux) (Radarr id 7, Sonarr id 7) вместо бывшего Any/id=1.

Что разрешает профиль (в порядке предпочтения, кодированные, БЕЗ remux/BR-DISK):

  • 2160p: HDTV / WEBDL / WEBRip / BluRay-2160p (кодир.)
  • fallback 1080p: HDTV / WEBDL / WEBRip / BluRay-1080p (кодир.)
  • fallback 720p: HDTV / WEBDL / WEBRip / BluRay-720p (кодир.)
  • Запрещены: Remux-1080p, Remux-2160p, BR-DISK, Raw-HD, SDTV/DVD/480/576.
  • cutoff = Bluray-2160p (кодир.); upgrade выключен после скачивания.

Где менять: watchlist-sync/watchlist_sync/arr.py строки qualityProfileId: 7 (radarr_add ~L77, sonarr_add ~L112). НЕ редактировать профили вручную иначе watchlist-sync надо обновить на новый id. Создать/править профиль «WEB-4K (no remux)» — Radarr/Sonarr → Settings → Quality Profiles.

✅ Профиль подтверждён в бою (2026-09-20): первый же фильм, добавленный через обновлённый arr.py (Gladiator 2000), взял релиз UHD BDRip [H.265/2160p] [4K] — то есть 4K, но кодированный BDRip, БЕЗ Remux. Профиль фильтрует корректно, цель (4K без 60GB-ремьюксов) достигнута.

Подтверждение через API (рабочие вызовы):

# список профилей Sonarr (проверить что id=7 существует)
curl -s "http://localhost:8989/sonarr/api/v3/qualityprofile" -H "X-Api-Key: $SONARR_KEY"
# корневые папки (проверить /media/series-sonarr и freeSpace)
curl -s "http://localhost:8989/sonarr/api/v3/rootfolder" -H "X-Api-Key: $SONARR_KEY"

📁 Актуальные пути контейнеров (проверено 2026-09-20)

Пути на дисках легко перепутать — два HDD с разными UUID. Точные значения через docker inspect:

Сервис Хост-путь конфига
Radarr /srv/dev-disk-by-uuid-6194539b-.../docker/radarr/config/ (внутри config.xml с ApiKey)
Transmission /srv/dev-disk-by-uuid-6194539b-.../docker/transmission/ → /config (внутри settings.json)
Медиа (общая) /srv/dev-disk-by-uuid-49e8f586-.../media/ → /media и /downloads

⚠️ НЕ /srv/...49e8f586.../docker/transmission/config/settings.json — такого файла нет. Transmission на 6194539b, Radarr-конфиг тоже на 6194539b (в подпапке config/, не в корне radarr). Проверять через:

docker inspect radarr --format '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{println}}{{end}}'
docker inspect transmission --format '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{println}}{{end}}'

Чтение ApiKey Radarr: grep -oP '(?<=<ApiKey>)[^<]+' /srv/...6194539b.../docker/radarr/config/config.xml Transmission RPC: docker exec transmission transmission-remote --list (без -n работает, rpc-username пустой)

Ports (Docker network media_net)

All services in the shared external network media_net — доступны друг другу по имени контейнера (radarr:7878, prowlarr:9696, transmission:9091, flaresolverr:8191) и с хоста по kraken:<port>:

Service Port
Radarr 7878
Sonarr 8989
Prowlarr 9696
Transmission 9091
Jellyfin 8096
FlareSolverr 8191

Сеть (проверено 2026-09-20): radarr, sonarr, prowlarr, transmission, flaresolverr — все в media_net. Пересечение сетей (radarr и transmission дополнительно ещё в docker_default) — нормально, роутинг по media_net. Порядок починки, если контейнер выпал из сети: sudo docker network connect media_net <container> (обратимо, без пересоздания) либо docker compose up -d из каталога сервиса.

Jellyfin библиотеки

Библиотека Тип Путь
Movies movies /media/movies
Series tvshows /media/series
Cartoons movies /media/cartoons
Cartoon Series tvshows /media/cartoons-series
Documentaries movies /media/documentaries
Documentary Series tvshows /media/documentaries-series

movies-radarr и series-sonarr НЕ добавлены в Jellyfin — router.py создаёт symlink'и из них в перечисленные пути.

Индексера Prowlarr — диагностика и починка (2026-09-20)

✅ Задача закрыта в тот же день. Ниже — история диагностики (полезна как метод), а актуальное решение с командами — в секции «🔴 Индексера Prowlarr — починка Cloudflare (2026-09-20)». Итог: было 3 «красных» из 6, стало 6/6 рабочих. Radarr 4/4, Sonarr 5/5 зелёные.

Запрос Alex: «что с torrent трекерами в arr, красные». Диагноз снят по логам Prowlarr, не по теории.

Что показывал UI на момент диагностики

id Индексер privacy Было Стало
1 RuTracker semiPrivate ❌ красный (Cloudflare) ✅ 48 результатов на «matrix»
5 Kinozal semiPrivate ❌ красный (Cloudflare) ✅ 50
6 Kinozal (M) semiPrivate ❌ красный (Cloudflare) ✅ 50
2 RuTor public ✅ 100 ✅ 100
4 NoNaMe Club public ✅ 50 ✅ 50
3 Byrutor public ✅ 6 ✅ 6

Root cause #1 — Cloudflare (RuTracker + Kinozal + Kinozal (M))

Из /srv/.../docker/prowlarr/config/logs/prowlarr.debug.txt:

Warn|IndexerHttpClient|HTTP Error - Res: HTTP/2.0 [POST] https://rutracker.org/forum/login.php: 403.Forbidden (5620 bytes)
<title>Just a moment...</title>  ... challenges.cloudflare.com ... cType: 'managed'
Warn|RuTracker|RuTracker HTTP request failed: [403:Forbidden] [POST] at [https://rutracker.org/forum/login.php]
   at NzbDrone.Core.Indexers.Definitions.RuTracker.DoLogin() in ./NzbDrone.Core/Indexers/Definitions/RuTracker.cs:line 103
Error|Cardigann|Cloudflare protection detected for [Kinozal], Flaresolverr may be required.
Error|Cardigann|Cloudflare protection detected for [Kinozal (M)], Flaresolverr may be required.
Debug|IndexerFactory|Temporarily ignoring indexer RuTracker till 09/21/2026 17:30:42 due to recent failures.
  • RuTracker: CF managed Turnstile на /forum/login.php → Prowlarr не может залогиниться (нужен JS-челлендж). Аккаунт при этом может быть жив — падает именно логин.
  • Kinozal / Kinozal (M): kinozal.me тоже за Cloudflare, Prowlarr сам пишет «Flaresolverr may be required».
  • Лечится FlareSolverr-прокси (см. решение). На момент диагностики версия была v2.5.2.5491.

Root cause #2 — Docker network split (Radarr полностью без индексеров)

radarr:       docker_default  172.22.0.3     ← НЕ видел prowlarr
sonarr:       media_net       172.23.0.2
prowlarr:     media_net       172.23.0.3
transmission: docker_default  172.22.0.2     ← НЕ был виден сонаром

Radarr POST /api/v3/indexer/testall → все 4 индексера false: Unable to connect to indexer... Name does not resolve (prowlarr:9696)

Вывод: Radarr был красный не из-за трекеров, а из-за сети. У Sonarr красные = реальные трекеры (CF). Ключевой урок: compose-файлы уже содержали media_net, но контейнеры остались в старой сети — проверять фактическое docker inspect, а не compose-файл.

⚠️ Питфолл: POST /api/v3/indexer/test по одному id НЕ работает

{"indexerId": N} на Radarr/Sonarr/Prowlarr возвращает HTTP 400 со списком validationFailures: 'Name' must not be empty, 'Implementation' must not be empty, 'ConfigContract' must not be empty, 'App Profile Id' must be greater than '0', 'Priority' must be between 1 and 50. То есть эндпоинт валидирует тело как новый индексер, а не берёт существующий по id.

Рабочий способ тестирования — testall:

# Radarr
curl -s -X POST -H "X-Api-Key: $RKEY" \
  "http://localhost:7878/radarr/api/v3/indexer/testall" \
  | jq -r '.[] | "\(.id)\t\(.isValid)\t\(.validationFailures[0].errorMessage // "")"'

# Sonarr (UrlBase /sonarr)
curl -s -X POST -H "X-Api-Key: $SKEY" \
  "http://localhost:8989/sonarr/api/v3/indexer/testall" \
  | jq -r '.[] | "\(.id)\t\(.isValid)\t\(.validationFailures[0].errorMessage // "")"'

# Prowlarr: тест одного индексера требует полного объекта из GET, не {indexerId}
# Быстрая проверка «кто живой» — сразу поиском:
curl -s -H "X-Api-Key: $PKEY" "http://localhost:9696/prowlarr/api/v1/search?query=matrix&type=search" \
  | jq -r 'group_by(.indexer)[] | "\(.[0].indexer): \(length)"'

Поиск через Prowlarr полезнее теста индексеров: ?query=X&type=search по умолчанию возвращает до 100 результатов на индексер (был соблазн прочитать «156 всего» как «по 26 на каждый» — неверно; смотреть group_by(.indexer)).

Итог и уроки

Все три причины были независимы и требовали независимых фиксов:

  1. Сеть — docker network connect media_net radarr|transmission (обратимо, без пересоздания контейнеров).
  2. FlareSolverr не применялся — нужен одинаковый tag на прокси И на индексере (главная засада: при tags=[] молча не работает).
  3. Блокировки в IndexerStatus — рестарт контейнера их НЕ сбрасывает, чистить через sqlite3 между stop/start.

Плюс побочно: RuTor не фильтрует по категориям → ему нужна категория 8000(Other), не 5000.

⚠️ Безопасность: FlareSolverr на LOG_LEVEL=info пишет логин и пароль RuTracker в plaintext в свои логи. Обязательно warning.

Известные проблемы

🔴 2026-09-22: авто-закачка УБРАНА ПОЛНОСТЬЮ

Требование Alex: закачек быть не должно. Сделано:

  1. sync-down вырезан из крона. watchlist-nightly.sh стр. 32-36 — вызов закомментирован, вместо него log "sync-down: ПРОПУЩЕН (автозакачка отключена 2026-09-22)". Бэкап watchlist-nightly.sh.bak-20260922-182655. Код sync_down.py НЕ удалён — можно запустить вручную.
  2. 16 авто-закачек удалены из Radarr (все с added >= 2026-09-20) через API с deleteFiles=true — без этого параметра файл остаётся на диске сиротой. 50 → 34 фильма, 12 с файлами, ошибок 0. Дамп для отката: /tmp/radarr-sonarr-dump-20260922-184351/radarr-movies.json.
  3. Sonarr не тронут — 13 сериалов, авто-закачек там не было.
  4. Все 34 фильма monitored=false — Radarr не будет ничего докачивать.

⚠️ Порядок при удалении закачки критичен: сначала PUT monitored=false, потом DELETE с deleteFiles=true. Иначе Radarr скачает заново.

Ранее: инцидент переполнения диска (2026-09-21, устранён)

Три страховки sync-down (введены 2026-09-21, коммиты 2642882, a131208)

Читать перед любой правкой sync_down.py / watchlist-nightly.sh:

Страховка Где Значение Поведение
Проверка места sync_down.py MIN_FREE_GB = 100.0 free < 100 GiB → [STOP], ничего не очередь
Фильтр ужасов sync_down.py:95 candidate.is_horror() ужас без ⭐ → [🧟] skipped
Приоритет sync_down.py ⭐ (U+2B50) обходит и рейтинг, и ужасы
Рейтинг sync_down.py MIN_RATING = 6.0 ниже → [−] skipped
Батч watchlist-nightly.sh --batch 2 было 5

⚠️ Проверка места — через shutil.disk_usage, НЕ df (df учитывает root-резерв ext4 и завышает заполнение на ~4%). Путь из env WATCHLIST_MEDIA_ROOT, иначе — родитель watchlist-файла.

⚠️ ⭐ (U+2B50) ≠ ✭ (U+272D). ✭ — декоративная звёздочка в конце названия, срезается _clean() и приоритетом НЕ является. Не путать при чтении списка.

Фильтр ужасов работает по ЖАНРУ (TMDB id 27), не по эмодзи. В dry-run отсекается 33 записи, хотя эмодзи 🧟♀️ стоит на 41 строке — жанр надёжнее ручной метки. Решение Alex: ожидаемое поведение; если фильм нужен — ставить ⭐.

mark_queued / unmark_queued — надёжность (те же коммиты)

parser.py: mark_queued возвращает bool, поглощает эмодзи-префикс (🧟♀️, 🗓️, ⭐, ✭, ⬇️…) и ставит метку перед ним: - [ ] ⬇️ 🧟♀️ midsommar 2019. Добавлена unmark_queued (её не было). sync_down._mark() логирует [!] could not write ⬇️ marker при промахе — раньше функция молча ничего не делала, и фильмы попадали в Radarr без метки.

⚠️ Правило для будущих правок: любая функция, мутирующая файл, должна возвращать результат, а вызывающий — его проверять. Молчаливый провал regex = тихая потеря данных.

_clean — эмодзи и год (коммит a131208)

  • _EMOJI_PREFIX: ^(?:[chars]+\s*)+ — повторяющиеся группы. Старая форма ^[chars]+\s* брала только первый эмодзи, и 🧟♀️ оставался внутри title (уходил в TMDB и Radarr).

  • Год: \(\d{4}(?:/\d{4})*\) — *, не +. С + одиночный (2022) не срезался. Пара (1995/2003) от mark_year_ambiguous обрабатывается так же.

  • sync_up._mark_watched: учитывает эмодзи-префикс по обе стороны ⬇️, сохраняет его.

  • ⚠️ 2026-09-20: compose-файл может ВРАТЬ про сеть. Ранее в compose Radarr был указан jellyfin_default, а реально контейнер жил в docker_default; при починке выяснилось, что compose уже писал media_net, но контейнеры не были пересозданы. Всегда проверять фактическое docker inspect -f '{{range $k,$v := .NetworkSettings.Networks}}{{$k}} {{end}}' <container>, а не docker-compose.yml.

  • 17 фильмов в Radarr hasFile=False — ждут подходящего релиза в Prowlarr. Scream (1996) в процессе загрузки.

  • Prowlarr — если долго не находит релизы, проверить статус индексеров (/api/v1/health). ⚠️ health врёт, реальную причину блокировки смотреть в config/logs/prowlarr.debug.txt — см. секцию «🔴 Индексера Prowlarr — починка Cloudflare (2026-09-20)» ниже.

  • ✅ 2026-09-20: все индексера Prowlarr починены (6/6), Radarr 4/4 и Sonarr 5/5 зелёные. Причины были: сеть (docker_default вместо media_net), непривязанный tag у FlareSolverr-прокси, и блокировки в IndexerStatus. Детали и команды — в секции ниже.

  • 🔴 2026-09-24: РЕЦИДИВ — «6/6» больше не действует. RuTracker (1), Kinozal (5), Kinozal (M) (6) снова заблокированы, EscalationLevel=9, disabledTill=2026-09-25T10:17Z — то есть сутки. Первопричина не Cloudflare и не теги (они на месте), а FlareSolverr в crash-loop: Chrome не поднимается под chromedriver (session not created: cannot connect to chrome at 127.0.0.1:PORT), HTTP :8191 не отвечает. Симптом в UI: error IndexerProxyStatusCheck: All indexer proxies are unavailable due to failures. Диагноз и незавершённый фикс (shm_size: 1gb + снятие IndexerStatus): personal/tech/prowlarr-flaresolverr-cloudflare §«2026-09-24». ⚠️ Ни GET /api/v1/health, ни /api/v1/indexerstatus пустотой не отделались — на этот раз они показали блокировки; но авторитет всё равно за prowlarr.debug.txt и таблицей IndexerStatus.

  • 🔴 2026-09-24: swap на Кра́кене может быть МЁРТВ. /proc/swaps пуст при живом /var/swap (512 MB), dphys-swapfile не поднимает его автоматически, строки в /etc/fstab нет. Лечение: sudo swapon /var/swap (⚠️ swapoff -a && swapon -a даёт SwapFree: 0 kB). Без swap Chrome-контейнеры падают при пике старта. Проверка: cat /proc/swaps.

  • 🔴 2026-09-04: 4K UHD Remux при Direct Play подтормаживает на ТВ (webOS). Симптом у Неистовый (Unhinged 2020) — 60.9GB ...Remux.2160p.mkv avg ~99 Мбит/с. После фикса eth0 Kraken → 1G (был 100M, см. kraken-network) торможение сохранилось. Подтверждено по логу Jellyfin log_20260904.log: сессия 10:18→10:28 «Jellyfin for WebOS 1.2.2», Stop at 400916ms (~6:40), ни одной строки transcode → это Direct Play, не CPU-рендер субтитров.

  • 🔴🔴 2026-09-04 (вживую): тормозит Direct Play даже лёгкого 4K → пока шло по Wi-Fi, причина не сервер/диск. В живом воспроизведении по Wi-Fi Project Hail Mary (2026) (...2160p.iT.WEB-DL...mkv, HEVC Main10 4K HDR10+, avg ~24.5-25.7 Мбит/с, 30.2GB, Direct Play) подёргивание было, при этом: транскода нет (0 ffmpeg, CPU Kraken load 0.1), сервер отдаёт всего ~21 Мбит/с, Wi-Fi ТВ «чист» по MAC-счётчикам (0 CRC/0 retries, -53 dBm), линки с запасом ×10+.

  • ✅ 2026-09-04 РАЗРЕШЕНИЕ (проверено пользователем вживую): после двух правок — (1) eth0 Kraken возвращён на 1G (замена патч-корда) и (2) ТВ переведён на проводной Ethernet (100M downshift-линк) — и Unhinged (remux, ~89 Мбит/с), и Project Hail Mary (лёгкий 4K) идут без тормозов. Вывод: причиной были НЕ пропускная способность и НЕ декодер-клиента, а нестабильный Wi-Fi-линк последней мили ТВ (даже при чистых MAC-счётчиках — джиттер/переупорядочивание в зашумлённом эфире). Провод (даже 100M) стабильно отдаёт. Профиль WEB-4K (no remux) остаётся разумной страховкой, но не был обязательной причиной тормозов.

Radarr Quality Profiles на Kraken (проверено 2026-09-04)

Via http://kraken:7878/radarr/api/v3/qualityprofile (X-Api-Key из config.xml; UrlBase=/radarr). Linuxserver image, сеть media_net (с 2026-09-20; ранее ошибочно жил в docker_default). Версия — см. таблицу актуальных версий ниже.

id Профиль Разрешённые качества Cutoff
1 Any весь диапазон CAM..BR-DISK, вкл. все 2160p Remux-1080p
2 SD до Bluray-576p —
3 HD-720p до Bluray-720p —
4 HD-1080p HDTV/Bluray/Remux-1080p Bluray-1080p
5 Ultra-HD HDTV/Bluray/Remux-2160p (BR-DISK не allowed) Remux-2160p
6 HD-720p/1080p 1080p и ниже —
7 WEB-4K (no remux) ✅ создан кодир. HDTV/WEB/BluRay 2160p+1080p+720p Bluray-2160p (кодир.)
8 (если появится из id авто-инкремента — перепроверить) — —

Профиль id=7 разрешает код HDTV/WEBDL/WEBRip/BluRay в 2160/1080/720; запрещает Remux-1080p, Remux-2160p, BR-DISK, Raw-HD, SDTV/DVD/480/576. upgrade выключен. Sonarr: аналогичный профиль тоже id=7.

  • Корень «почему новые качаются в любой жир»: watchlist-sync (который добавляет фильмы/сериалы из watchlist в Radarr/Sonarr) жёстко прописывает "qualityProfileId": 1 (= профиль Any) в arr.py:
    • Реальный путь репо: /srv/dev-disk-by-uuid-6194539b-03e0-4f33-986b-4da09834e82f/watchlist-sync (НЕ ~/Developer/watchlist-sync, хотя симлинк ~/Developer/watchlist-sync → .../49e8.../watchlist-sync есть; 49e8 — симлинк на 6194).
    • watchlist_sync/arr.py → radarr_add() (~стр 77) и sonarr_add() (~стр 112), обе "qualityProfileId": 1. Radarr POST /api/v3/movie c addOptions.searchForMovie:true; Sonarr POST /api/v3/series.
  • ✅ Решение принято и применено (см. выше): профиль WEB-4K (no remux) (Radarr/Sonarr id 7), fallback 1080→720, remux запрещён. Применено 2026-09-04.
  • ✅ 2026-09-04: стратегия качества РЕШЕНА И ПРИМЕНЕНА. Новые фильмы/сериалы (через watchlist-sync) добавляются с профилем WEB-4K (no remux) (Radarr id 7, Sonarr id 7) — см. секцию «Качество загрузки для НОВЫХ». Fallback: если нет 2160p-кодированного — 1080p → 720p; remux не берётся никогда. Было принято так (fallback=1080, если нет — 720). Существующие файлы НЕ трогаются.

Реальный состав жирного remux (пример Unhinged, ffprobe 2026-09-04)

Важно для понимания «что даёт remux» перед выбором профиля. Файл Неистовый.2020.UHD.Blu-Ray.Remux.2160p.mkv = 60.9GB, 1:31:05, контейнер 89.17 Мбит/с средний.

FFprobe потоки:

idx тип кодек каналы язык / роль
0 video HEVC Main10, 3840×2160, 24fps — HDR10 (BT.2020, PQ smpte2084)
1 audio AC3 5.1(side) 384k rus «Dub, iTunes»
2 audio DTS-HD MA 7.1 rus «Д. Есарев»
3 audio TrueHD 7.1 eng
4 audio AC3 5.1 640k eng
5 audio DTS stereo 320k eng Commentary (реж. Деррик Борте)
6-8 sub SRT/SPU — rus / eng full / eng SDH

Вывод: размер 60GB раздувает НЕ видео (HDR10 HEVC-часть типичная), а множество звуковых дорожек (lossless DTS-HD MA + TrueHD) + аудиокомментарий. «Что даёт remux» на бытовом ТВ: видео-выигрыш против аккуратного 4K-рипа почти незаметен с дивана; бонус реализуется только при lossless-прокачке звука (AVR/eARC). Если ТВ играет через собственные динамики/стерео или делает audio-transcode — плюс remux теряется, а вес/сеть/лаг остаются. Это аргумент за профиль «2160p без Remux» для бытового просмотра на webOS-TB.

Бенчмарк WEB-4K (замерено по библиотеке Kraken, 2026-09-04)

«Поддерживаемое качество» для сравнения с жирным remux — это кодированные 2160p. Замер файлов с WEB+2160 в имени в /media:

  • Полнометражки (2ч): Project Hail Mary 28.1GB, The Drama 19.2GB, The Crow 17.4GB, They Will Kill You 17.2GB, Кот в сапогах 19.9GB → range 17–28GB, медиана ~19GB.
  • 45-50 мин эпизоды док-сериалов (prehistoric.planet s01): ~7.2–7.7GB каждый.
  • Статсводка по всем 10 файлам: min 7.2 / median 12.5 / mean 13.9 / max 28.1 GB. Средний битрейт ~20–40 Мбит/с.
  • Практичный floor benchmark: «нормальная» копия 4K для этого ТВ ≈ 17–28GB/фильм (в 2.5–3× меньше remux). ⚠️ ⚠️ Важно: подёргивание 4K Direct Play (и лёгкого, и remux) было вызвано Wi-Fi-линком последней мили ТВ — по проводу оба идут (см. «РАЗРЕШЕНИЕ» в «Известные проблемы»). Напротив клиента webOS не виноват.

🔴 Индексера Prowlarr — починка Cloudflare (2026-09-20)

Итог: было 3 «красных» из 6, стало 6/6 рабочих. Radarr 4/4, Sonarr 5/5 зелёные.

Симптом

Radarr: error IndexerLongTermStatusCheck — «All indexers are unavailable due to failures for more than 6 hours». Sonarr: error DownloadClientCheck — «Unable to communicate with Transmission. Name does not resolve (transmission:9091)». В UI Prowlarr индексера RuTracker / Kinozal / Kinozal (M) показывались красными.

Три независимые причины (все три надо было починить)

(1) Сеть — Radarr и Transmission в неправильной сети. Compose-файлы УЖЕ были прописаны на media_net, но живые контейнеры остались в docker_default (compose обновили, контейнеры не пересоздали). Итог: Radarr не резолвит prowlarr:9696 → все индексеры false; Sonarr не резолвит transmission:9091.

sudo docker network connect media_net radarr
sudo docker network connect media_net transmission
# проверка: docker exec radarr getent hosts prowlarr
#          docker exec sonarr curl -sw "%{http_code}" http://transmission:9091/transmission/rpc  → 409 = связь есть (штатный CSRF-ответ)

(2) FlareSolverr-прокси в Prowlarr не применялся (главный баг). Контейнер flaresolverr был жив, запись прокси создана (id=1), но в логе Prowlarr — прямой 403 login.php, а FlareSolverr вообще не вызывался. Причина: в Prowlarr v2.x прокси применяется к индексерам ТОЛЬКО если у прокси И у индексера стоит одинаковый tag. При tags=[] с обеих сторон прокси молча НЕ применяется, никакой ошибки в UI нет.

KEY=$(grep -oP "(?<=<ApiKey>)[^<]+" .../docker/prowlarr/config/config.xml)
# 1. создать тег
curl -X POST -H "X-Api-Key: $KEY" -H "Content-Type: application/json" \
  -d '{"label":"flaresolverr"}' http://localhost:9696/prowlarr/api/v1/tag
# 2. повесить tag на ПРОКСИ
curl -s -H "X-Api-Key: $KEY" http://localhost:9696/prowlarr/api/v1/indexerproxy/1 | jq ".tags=[1]" > /tmp/p.json
curl -X PUT -H "X-Api-Key: $KEY" -H "Content-Type: application/json" -d @/tmp/p.json \
  http://localhost:9696/prowlarr/api/v1/indexerproxy/1
# 3. повесить ТОТ ЖЕ tag на индексера (RuTracker=1, Kinozal=5, Kinozal M=6)
curl -s -H "X-Api-Key: $KEY" http://localhost:9696/prowlarr/api/v1/indexer/1 | jq ".tags=[1]" > /tmp/i.json
curl -X PUT -H "X-Api-Key: $KEY" -H "Content-Type: application/json" -d @/tmp/i.json \
  http://localhost:9696/prowlarr/api/v1/indexer/1

Пруф работы — только лог FlareSolverr, не UI:

sudo docker logs --tail 30 flaresolverr
# → Challenge detected. Title found: Just a moment...
# → Challenge solved!

Проверка поиска: curl "http://localhost:9696/prowlarr/1/api?t=search&q=matrix&apikey=$KEY" → RuTracker 48 items, Kinozal 50, Kinozal M 50, RuTor 100, NoNaMe 50.

(3) Блокировки индексера в БД — рестарт их НЕ сбрасывает. Prowlarr сам выключает упавший индексер на 24ч: Temporarily ignoring indexer X till +24h. В health API и /api/v1/indexerstatus это НЕ видно (оба возвращают пусто, врут) — только в debug-логе. Состояние лежит в таблице IndexerStatus в prowlarr.db.

sudo docker stop prowlarr
sqlite3 /srv/.../docker/prowlarr/config/prowlarr.db "DELETE FROM IndexerStatus WHERE ProviderId IN (1,5,6);"
sudo docker start prowlarr

⚠️ В контейнерах sqlite3 НЕТ; на хосте Kraken /usr/bin/sqlite3 ЕСТЬ.

Побочно: RuTor не фильтрует по категориям. У него в caps заявлены 2000(Movies)/5000(TV)/8000(Other), но при передаче cat=5000 он возвращает 0 результатов, а без cat — 100. Prowlarr сам подсказывает в полях индексера: «RuTor does not return categories in its search results. To sync to your apps, include 8000(Other)». Поэтому в Sonarr (indexer id=8) категория RuTor переставлена 5000 → 8000.

Версии и сервис

  • Prowlarr обновлён 2.5.2.5491 → 2.6.5.5623 (в 2.5.2 прокси-модель ещё только вводилась).
  • FlareSolverr ghcr.io/flaresolverr/flaresolverr:latest v3.5.2, порт 8191, сеть media_net, env PROWLARR__FLARESOLVERR_URL=http://flaresolverr:8191.
  • ⚠️ FlareSolverr LOG_LEVEL понижен info → warning: на level info FlareSolverr пишет postData запросов в лог, т.е. логин и пароль RuTracker в plaintext. После понижения — лог пуст, пароль не пишется.

Бэкапы (перед изменениями)

/home/kraken/arr-fix-backup-20260920/ — networks.txt, 4× docker inspect, 3 compose-файла, prowlarr.db.bak, prowlarr-config-preupgrade.tar.gz.

Диагностические грабли

  • POST /api/v3/indexer/test с телом {"indexerId":N} → HTTP 400 (валидирует как НОВЫЙ индексер, требует Name/Implementation). Для теста всех сразу: POST /api/v3/indexer/testall.
  • GET /api/v1/health в Prowlarr и /api/v1/indexerstatus НЕ показывают причины блокировки. Смотреть .../docker/prowlarr/config/logs/prowlarr.debug.txt (grep по имени индексера и Cloudflare/403).

Sonarr sonarr_add() не проверяет дубли → 400 SeriesExistsValidator (2026-09-20)

Симптом: при sync-down для уже существующей серии в логе появляется шум:

[!]  'Волшебный участок' — error: Client error '400 Bad Request' for url
     'http://localhost:8989/sonarr/api/v3/series?apikey=...'

Причина (безобидная): серия уже есть в Sonarr. Тело ответа:

[{"propertyName":"TvdbId","errorMessage":"This series has already been added",
  "attemptedValue":442038,"errorCode":"SeriesExistsValidator"}]

«Волшебный участок» → TVDB Magical Police Department (tvdbId=442038).

Корень: watchlist_sync/arr.py::sonarr_add() шлёт POST /api/v3/series без pre-check, в отличие от radarr_add(), где есть radarr_exists(). Sonarr отвечает ошибкой валидатора, sync_down ловит её как исключение и печатает [!], не помечая позицию ⬇️.

Следствие: позиция остаётся [ ] и будет заново пытаться добавиться на каждом прогоне, каждый раз занимая слот батча и не помечаясь.

Починка (не сделана): добавить проверку по tvdbId перед POST — как в Radarr. Реэкспорт series/lookup в Sonarr дубли не отсекает, проверять надо по библиотеке:

GET /api/v3/series  → сверить tvdbId

Обновление Radarr / Sonarr (2026-09-20)

Watchtower их не обновлял (см. family/how-to/kraken-watchtower), поэтому обновлено вручную: Radarr 6.1.1.10360 → 6.4.4.10685, Sonarr 4.0.17.2952 → 4.0.20.3014.

# Sonarr — метаданные проекта правильные, обычный up:
cd /srv/.../docker/sonarr && sudo docker compose pull sonarr && sudo docker compose up -d sonarr

⚠️ Ловушка: Radarr принадлежал ЧУЖОМУ compose-проекту

docker compose up -d radarr из docker/radarr/ падал: Conflict. The container name "/radarr" is already in use.

Причина: контейнеры radarr и transmission были созданы 17.05.2026 из общего проекта docker (файл /srv/.../docker/docker-compose.yml, сейчас его нет — удалён). Метаданные: com.docker.compose.project=docker. При этом docker/radarr/docker-compose.yml существовал и не использовался.

Проверка, к какому проекту принадлежит контейнер:

sudo docker inspect radarr -f '{{index .Config.Labels "com.docker.compose.project"}} {{index .Config.Labels "com.docker.compose.project.config_files"}}'

Фикс (данные в config/ не трогаются, только контейнер):

sudo docker stop radarr && sudo docker rm radarr
cd /srv/.../docker/radarr && sudo docker compose up -d radarr
# стало: project=radarr file=.../docker/radarr/docker-compose.yml, сеть media_net

transmission остался в проекте docker (его compose-файл в docker/transmission/), но подключён к media_net — работает.

Диагностические грабли (Radarr/Sonarr API)

  • POST /api/v3/indexer/test с телом {"indexerId":N} → HTTP 400 (валидирует как НОВЫЙ индексер, требует Name/Implementation/AppProfileId). Использовать POST /api/v3/indexer/testall.
  • То же для клиентов: POST /api/v3/downloadclient/testall.
  • Триггер UrlBase: Radarr /radarr/api/v3/..., Sonarr /sonarr/api/v3/....

Итоговое состояние compose-проектов и сетей (2026-09-20)

После всех правок важно, потому что docker compose up -d надо запускать из своего каталога, иначе будет конфликт имени контейнера.

Контейнер Compose-проект Файл Сеть
radarr radarr ✅ docker/radarr/docker-compose.yml media_net
sonarr sonarr ✅ docker/sonarr/docker-compose.yml media_net
prowlarr prowlarr ✅ docker/prowlarr/docker-compose.yml media_net
flaresolverr (в том же файле prowlarr) docker/prowlarr/docker-compose.yml media_net
transmission transmission ✅ docker/transmission/docker-compose.yml media_net
watchtower watchtower docker/watchtower/docker-compose.yml —

✅ transmission тоже исправлен (2026-09-20, позже в той же сессии). Был в проекте docker, пересоздан так же: stop → rm → up -d из docker/transmission/. Данные не пострадали: 244 торрента в ./torrents/ + 244 resume-файла на месте, RPC вернул torrents: 244. Проверка перед пересозданием обязательна — считать торренты ДО и ПОСЛЕ:

sudo ls $D/transmission/torrents/ | wc -l   # до и после должно совпасть

Общее правило: если docker compose up -d даёт Conflict. The container name "/X" is already in use — контейнер принадлежит чужому (возможно, удалённому) compose-проекту. Смотреть com.docker.compose.project и project.config_files, затем stop → rm → up -d из правильного каталога. Данные в bind-mount (config/, torrents/) при этом не трогаются.

Проверка принадлежности любого контейнера:

sudo docker inspect <name> -f '{{index .Config.Labels "com.docker.compose.project"}} {{index .Config.Labels "com.docker.compose.project.config_files"}}'

Версии после обновления (2026-09-20)

Сервис Версия
Radarr 6.4.4.10685
Sonarr 4.0.20.3014
Prowlarr 2.6.5.5623
FlareSolverr 3.5.2
Watchtower 1.22.2 (nickfedor/watchtower, см. family/how-to/kraken-watchtower)

Автообновлением занимается Watchtower — проверить его конфиг перед тем, как ждать обновлений: family/how-to/kraken-watchtower, теория — personal/tech/watchtower-docker-updates.

Автообновление (Watchtower) — 2026-09-20

Все сервисы стека (radarr, sonarr, prowlarr, flaresolverr, transmission) плюс jellyfin и portainer размечены label com.centurylinklabs.watchtower.enable=true → обновляются автоматически (7 контейнеров, проверено scanned=7). Полный разбор и список того, что НЕ обновляется: family/how-to/kraken-watchtower. Теория: personal/tech/watchtower-docker-updates.

Watchlist-sync: ручной запуск и баг дублирования (2026-09-20)

Детальный разбор бага и полная механика — в family/projects/watchlist-automation. Здесь — операционная выжимка.

Ручной запуск (пока крон не восстановлен)

ssh kraken
bash ~/scripts/watchlist-nightly.sh     # pull → process-thumbs → resolve → sync-down → sync-up → push

Отдельные шаги:

cd /srv/dev-disk-by-uuid-49e8f586-3839-4c5d-a1e1-58bfc3579ade/watchlist-sync
uv run --project . watchlist-sync <resolve|sync-down|sync-up|process-thumbs|discover> [--apply] [--recheck] [--batch N]

uv = ~/.local/bin/uv. Без --apply — dry-run.

🔴 Баг mark_ambiguous() — исправлен

watchlist_sync/parser.py, функция mark_ambiguous(): паттерн снимал только саму ❓-строку, а замена вставляла новые кандидаты перед старыми → под-буллеты дублировались на каждом прогоне resolve (+3 строки / +100 байт за раз). ❓-строки при этом НЕ дублировались — только под-буллеты под ними.

# ФИКС: захват всего блока — маркер + под-буллеты
_SUB_BULLETS = r'(?:\n[ \t]{4,}-[ \t]*(?!\[[ xX]\])[^\n]*)'
pattern = re.compile(
    r'(^[ \t]*-[ \t]*\[ \][ \t]*)[❓]?[ \t]*(' + escaped + r')[ \t]*'
    r'(?P<subs>' + _SUB_BULLETS + r'*)',
    re.MULTILINE
)

Правило для парсеров этого файла: под-буллет кандидата = отступ ≥4 пробела + буллет + НЕ чекбокс (- [ ]/- [x]). Строки фильмов имеют отступ 2 пробела — их надо исключать явно. Внутри *-группы перенос строки обязателен ((?: + \n + ...) + *): если сделать \n? опциональным, regex перешагивает на следующую строку и проглатывает соседние фильмы (проверено — так потеряли ❓ The kill room, The burial, Past lives).

Верификация: прогнать mark_ambiguous() 3× на свежей копии → размер обязан быть идентичным. Плюс проверка, что 0 не-кандидатных строк потеряно.

Скрипт дедупа кандидатов

Одноразовая чистка накопленных дублей: под каждой ❓/🗓️-строкой оставить уникальные под-буллеты (ключ title|year|rating), порядок не менять, строки фильмов не трогать.

python3 /tmp/wl_dedupe.py            # dry-run
python3 /tmp/wl_dedupe.py --apply    # запись + авто-бэкап .dedupe-backup-<ts>

Прогон 2026-09-20: 168 дублей в 15 блоках, 26430 → 21032 байт (838 → 670 строк).

⚠️ Скрипты этой сессии — в /tmp на Кракене (wl_dedupe.py, wl_fix_parser*.py, wl_verify*.py); /tmp чистится при перезагрузке. Для постоянной копии — перенести в ~/scripts/.

Sonarr 400 «This series has already been added» — не сбой

При sync-down уже существующей серии Sonarr отвечает 400 SeriesExistsValidator («Волшебный участок» = «Magical Police Department», tvdbId=442038). arr.py::sonarr_add() не проверяет дубли перед POST — в отличие от radarr_add(). Шумит в логе, данные не портит. Диагноз смотреть в теле ответа: errorCode: SeriesExistsValidator.

Zulip-пинг не настроен

_zulip_ping() (cli.py) требует ZULIP_URL / ZULIP_API_KEY / ZULIP_BOT_EMAIL, которых нет в .env репо → [warn] not set — skipping ping. Уведомления о нерезолвленных items до Alex не доходят (59 штук ушли молча).

Подготовка к авто-удалению просмотренного (2026-09-20)

Требование Alex: удалять [x] при заполнении диска >90%, через API Radarr. План и открытые вопросы — family/plans/watchlist-cleanup. Здесь — только инфраструктурные факты, добытые при разведке.

⚠️ df ВРЁТ про заполнение — считать через shutil.disk_usage

df -h      → 88%   (учитывает root-резерв ext4)
shutil.disk_usage → 83.7%  (реальные 1647 / 1968 GB, free 220.8 GB)

Разница ~4%. Порог «>90%» проверять только через shutil.disk_usage, иначе сработает на 86%.

Jellyfin: заголовок авторизации и отсутствие DatePlayed

# ПРАВИЛЬНО (как в watchlist_sync/jellyfin.py:_auth)
{"Authorization": 'MediaBrowser Token="<token>"'}
# НЕПРАВИЛЬНО
{"X-Emby-Token": token}      # → 401

Рабочие вызовы:

GET /Users                                        → 200; юзеры alex, lisa
GET /Users/{uid}/Items?IsPlayed=true&IncludeItemTypes=Movie&Fields=DatePlayed,Path

🔴 DatePlayed возвращается None для всех просмотренных. Логику, зависящую от даты просмотра, строить нельзя. IsPlayed=true работает, UserData.Played работает — дата нет.

Jellyfin видит просмотренными всего 6 фильмов (Король Лев, Меч короля, Эль рояль, Остров сокровищ, Схватка с дьяволом, Труп невесты) — все из /media/movies (ручные торренты). Ни одного из /media/movies-radarr (на 2026-09-21 — 28 фильмов). То есть пересечение [x] ∩ Radarr-файлы пусто.

⚠️ Это не косметика, а причина, по которой авто-очистка диска не работает. sync-up переносит watched-историю Jellyfin в [x], а cleanup удаляет только то, что помечено [x]. Пока Jellyfin не видит Radarr-библиотеку, диск (94.7%) будет заполняться, а cleanup будет ежедневно писать «nothing to delete». Разбираться с этим, а не с cleanup. Детали — family/plans/watchlist-cleanup.

Radarr: состояние библиотеки (замер 2026-09-22)

  • 34 фильма в базе, 12 с файлами (до удаления 16 авто-закачек было 50/28).
  • 🔴 monitored=0 — ни один фильм не отслеживается. Radarr ничего не докачает.
  • Объём библиотеки на диске: 251.9 GB.
  • Топ по размеру: Chaos Walking 63 GB, Oldboy 31, Project Hail Mary 30, Whiplash 25, The Drama 21.
  • Диск: 73%, свободно 477 GB (/dev/sda1, 1.8T).
  • ✅ Диск был освобождён 2026-09-21: 100% → 77%. История: sync-down --batch 5 качал 5 фильмов ежедневно без проверки места → диск в 0 байт, Radarr отдавал disk I/O error (SQLite не пишет при нуле места). Удалены 5 remux = 403.4 ГБ (Babylon 92.0, Schindler's List 89.4, Midsommar 85.6, Triangle of Sadness 79.7, Unhinged 56.7). Бэкапы: ~/backup-movies-watchlist-20260921-150759.md, ~/backup-radarr-unmonitor-20260921-151117.json. Порядок при удалении: RescanMovie → PUT monitored=false → DELETE. Без снятия monitored Radarr перекачал бы все 403 ГБ.
  • ✅ Стоп-кран MIN_FREE_GB = 100.0 в sync_down.py (коммит 2642882): проверка через shutil.disk_usage, НЕ df (df врёт на ~4%, учитывает root-резерв ext4). При нехватке — [STOP] free space ... < 100 GiB и выход без добавления. Там же фильтр ужасов (is_horror(), ⭐ обходит). Разбор — family/plans/watchlist-disk-overflow-fix.
  • ⚠️ С 2026-09-22 sync-down в кроне не запускается вообще — стоп-кран и профиль качества остаются как страховка для ручного запуска.
  • recycleBin: '' — корзины нет, и настраивать её решено НЕ будет (2026-09-21). PUT config/mediamanagement с путём хоста даёт 400 FolderWritableValidator: not writable by user 'abc': внутри контейнера Radarr медиа замаплено как /media, хостового пути /srv/...6194539b.../media/.recyclebin там не существует. Обходить маппингом ради корзины признано нецелесообразным — удаление идёт через Radarr API, а не rm. recycleBinCleanupDays: 7, deleteEmptyFolders: False.
  • 🔴 Все monitored=false (2026-09-22). Если удалять файл — PUT monitored=false до DELETE (порядок критичен, иначе скачает заново).

Проверка одним вызовом:

KEY=$(grep -oP '(?<=<ApiKey>)[^<]+' /srv/...6194539b.../docker/radarr/config/config.xml)
# только фильмы с файлами
curl -s "http://localhost:7878/radarr/api/v3/movie?apikey=$KEY" \
  | jq -r '.[] | select(.hasFile) | "\(.id)\t\(.sizeOnDisk/1e9|floor)GB\t\(.monitored)\t\(.title)"'
# сводка одной строкой
curl -s "http://localhost:7878/radarr/api/v3/movie?apikey=$KEY" | jq -r '
  "всего=\(length) сФайлом=\([.[]|select(.hasFile)]|length) monitored=\([.[]|select(.monitored)]|length)"'

Watchlist → Lampa: JSON-каталог (2026-09-22)

Второй потребитель watchlist, помимо Radarr/Sonarr: модуль Catalog в Lampa. Пайплайн:

movies-watchlist.md (боевой .md)
        ↓ gen_watchlist.py   ← крон 30 2 * * * (watchlist-catalog.sh)
watchlist.json + missing.json
        ↓
Lampa (Catalog): карточки с постерами + маркер ❓ для нерезолвленных

Пути

Что Путь
Генератор $H/lampac/scripts/gen_watchlist.py
Маркер $H/lampac/scripts/mark_missing.py
Крон-скрипт ~/scripts/watchlist-catalog.sh
Боевой .md $H/obsidian/family/documents/movies-watchlist.md
Вывод $H/lampac/wwwroot/watchlist.json (376 позиций)
Нерезолвленные $H/lampac/wwwroot/missing.json
Кэш $H/lampac/cache/.lampac_tmdb_{cache,details}.json (скрытые файлы)

где $H = /srv/dev-disk-by-uuid-6194539b-03e0-4f33-986b-4da09834e82f

Резолв позиции — три уровня

1. Radarr (по названию)              ← самый надёжный
2. TMDB поиск по названию (+год ±1)
2b. эвристика КП по названию
2c. kp_id из ссылки в .md → КП → TMDB   ← машинный, однозначный (проверяется ПЕРВЫМ из КП)
3. промах → маркер ❓ в .md

KP_SIM_THRESHOLD = 0.75 — порог совпадения названия в КП.

⚠️ watchlist.json использует channels.*.items[].name, НЕ title. Поле title там отсутствует — не путать при парсинге.

🔴 Кириллица в ключе каталога Lampa → ModInit.goInit чистит [^a-z0-9\-] → HTTP 400. Нужен plugin: watchlist + числовой tmdb_id (format: "long.TryParse(...)"). Проверять через /catalog/card?...&type=movie, НЕ /card.

Маркеры в боевом .md

Маркер Значение
❓ не резолвлен (нет в TMDB/КП)
🗓️ ещё не вышел
👎 не надо
[x] смотрел
⭐ приоритет (обходит фильтр ужасов и рейтинг)

mark_missing.py работает в обе стороны: нет позиции в missing.json → маркер ❓ снимается.

Полный разбор генератора: personal/plans/watchlist-tmdb-missing-marker. Деплой и плагин: personal/tech/lampac-deployment.

Notes