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

793 lines
61 KiB
Markdown
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: Arr Stack — Kraken
created: '2026-05-23'
updated: '2026-09-22'
type: tech
namespace: family
tags:
- arr
- radarr
- sonarr
- prowlarr
- transmission
- jellyfin
- infra
- kraken
related:
- '[[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,...}/` не создаются автоматически.
Точная проверка наличия любого крон-джоба (не верить доке, смотреть систему):
```bash
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 # демон жив?
```
**Установка крон-джоба безопасно (с бэкапом):**
```bash
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 (рабочие вызовы):**
```bash
# список профилей 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). Проверять через:
```bash
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`:**
```bash
# 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`.
```bash
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 нет.
```bash
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:
```bash
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`.
```bash
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. Тело ответа:
```json
[{"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**.
```bash
# 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` существовал и не использовался.
Проверка, к какому проекту принадлежит контейнер:
```bash
sudo docker inspect radarr -f '{{index .Config.Labels "com.docker.compose.project"}} {{index .Config.Labels "com.docker.compose.project.config_files"}}'
```
Фикс (**данные в `config/` не трогаются**, только контейнер):
```bash
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`. Проверка перед пересозданием обязательна — считать торренты ДО и ПОСЛЕ:
```bash
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/`) при этом не трогаются.
Проверка принадлежности любого контейнера:
```bash
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]]. Здесь — операционная выжимка.
### Ручной запуск (пока крон не восстановлен)
```bash
ssh kraken
bash ~/scripts/watchlist-nightly.sh # pull → process-thumbs → resolve → sync-down → sync-up → push
```
Отдельные шаги:
```bash
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 байт за раз). `❓`-строки при этом НЕ дублировались — только под-буллеты под ними.
```python
# ФИКС: захват всего блока — маркер + под-буллеты
_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`), порядок не менять, строки фильмов не трогать.
```bash
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`
```python
# ПРАВИЛЬНО (как в 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` (порядок критичен, иначе скачает заново).
Проверка одним вызовом:
```bash
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
- See [[arr-stack-taiga]] for Taiga (secondary) arr stack
- Media pipeline pitfalls: [[media-pipeline-pitfalls]]
- FlareSolverr / Cloudflare / Prowlarr-питфоллы: [[prowlarr-flaresolverr-cloudflare]]
- Watchlist pipeline (полный flow, cron, баги): [[family/projects/watchlist-automation]]
- Синтез: [[concepts/kraken-media-stack]]