diff --git a/family/projects/watchlist-automation.md b/family/projects/watchlist-automation.md index 673f14c0..87a77952 100644 --- a/family/projects/watchlist-automation.md +++ b/family/projects/watchlist-automation.md @@ -12,6 +12,85 @@ tags: Полная цепочка: как `family/documents/movies-watchlist.md` превращается в скачанный контент. +## 🔴 2026-09-22: АВТОСКАЧКА НА HDD ВЫКЛЮЧЕНА (по требованию Alex) + +**Контекст.** Задача — Lampa на ТВ показывает каталог из `movies-watchlist.md` и играет +через TorrServer. Alex: «мы перестаем качать торренты на диск!» → автоскачка Radarr/Sonarr +выключена фактически. Модуль Catalog/Lampa — в [[personal/plans/lampac-watchlist-plugin]]. + +### Выполнено (замер до → после) + +| Сервис | Очередь | `monitored` | `rssSyncInterval` | +|---|---|---|---| +| **Radarr** (:7878) | 6 → **0** | 45 → **0** | 30 → **0** | +| **Sonarr** (:8989) | 19 → **0** | 13 → **0** | 15 → **0** | + +Бэкапы: `radarr.db` / `radarr.db.bak-20260922-123404`, `sonarr.db` / `.bak-20260922-123404` +(+ оба `config.xml`) в `/srv/...6194539b.../docker/{radarr,sonarr}/config/`. + +**Метаданные НЕ удалены** — это важно: 50 фильмов остались в Radarr с `tmdbId` (все 50), +постерами (46) и рейтингом. Они нужны каталогу Lampa. 24 файла на диске (Jellyfin) не тронуты. + +**Проверка пассивности:** Transmission за 45 с — NET `290GB/26.7GB`, BLOCK `214GB/393kB` +**не сдвинулись**. Логи Radarr (`grep -iE "search|download|grab"`) за 2 мин — пусто. + +### ⚠️ Корень в коде — `arr.py` (НЕ ИСПРАВЛЕН, на апруве) + +```python +# radarr_add(): +"monitored": True, +"addOptions": {"searchForMovie": True}, # ← включает скачку сразу + +# sonarr_add(): +"monitored": True, +"addOptions": {"searchForMissingEpisodes": True}, # ← то же для сериалов +``` + +🔴 **Снятие `monitored` вручную — временная мера.** Следующий запуск `sync-down` +(добавление из списка) **включит автоскачку заново**, потому что флаг зашит в код. +Фикс: `searchForMovie: False`, `searchForMissingEpisodes: False`. + +Похожий по классу инцидент уже был — захардкоженный `qualityProfileId` (см. питфоллы ниже). + +### Правильные вызовы API (проверено) + +```bash +# 1. отмена загрузки — файлы на диске НЕ трогаются, blocklist НЕ трогать +DELETE /api/v3/queue/?removeFromClient=true&blocklist=false + +# 2. снятие monitored — КЛЮЧ ПОЛЯ РАЗНЫЙ у Radarr и Sonarr! +PUT /api/v3/movie/editor {"movieIds": [...], "monitored": false} # Radarr +PUT /api/v3/series/editor {"seriesIds": [...], "monitored": false} # Sonarr, НЕ "ids" + +# 3. RSS off — отправлять ВЕСЬ объект конфига с id, иначе молча вернёт старое +PUT /api/v3/config/indexer {"rssSyncInterval": 0, "id": 1, ...остальное} +``` + +Ключи API: Radarr `cbcb8ec320104359920c0f611d503c7b`, Sonarr `15fbec32f1cb4b05b1c2b229ce27359b` +(из `` в `config.xml`). + +### 🔴 Питфоллы этой операции + +1. **`urllib` НЕ идёт по 307, а `curl -sL` — идёт.** Первый прогон: все 25 DELETE вернули + `HTTP Error 307`, **ничего не удалилось**. Штатный `urllib` на 307/308 превращает + DELETE в GET и теряет тело. Фикс — свой `HTTPRedirectHandler`, сохраняющий метод и тело + (код в [[personal/tech/lampac-deployment]], раздел Radarr). +2. **Sonarr группирует эпизоды сезона под одним `id`** — после удаления первой записи + остальные дают `404 Not Found`. Это **не сбой**, они ушли вместе с родительской. +3. **`PUT config/indexer` требует весь объект с `id`** — частичный `{"rssSyncInterval":0}` + молча возвращает прежнее значение (Radarr понадобился второй заход). +4. **Кириллица в inline-`jq` внутри `ssh '...'`** снова сломала команду + (`unexpected INVALID_CHARACTER`). Ключи объекта — латиницей. + +### Откат + +```bash +curl -sL -X PUT ".../api/v3/movie/editor" -d '{"movieIds":[...],"monitored":true}' +curl -sL -X PUT ".../api/v3/series/editor" -d '{"seriesIds":[...],"monitored":true}' +curl -sL -X PUT ".../api/v3/config/indexer" -d '{"rssSyncInterval":30,"id":1,...}' +cp radarr.db.bak-20260922-123404 radarr.db && docker restart radarr +``` + ## Полная цепочка ``` diff --git a/personal/plans/lampac-watchlist-plugin.md b/personal/plans/lampac-watchlist-plugin.md index 4839f3ec..8dcfd186 100644 --- a/personal/plans/lampac-watchlist-plugin.md +++ b/personal/plans/lampac-watchlist-plugin.md @@ -17,6 +17,8 @@ tags: - plugin - kraken - radarr + - sonarr + - torrserver title: Watchlist в UI Lampa — каталог из списка type: plan updated: '2026-09-22' @@ -46,6 +48,15 @@ updated: '2026-09-22' > просмотр через TorrServer, файлы на HDD не пишутся) → записано в **«ПЕРЕДЕЛКА 2»**. > План работ на апруве, **ничего не выполнено**. > +> **Обновление 2026-09-22 (поздний вечер): ✅ ШАГИ 0–4 ПЕРЕДЕЛКИ 2 ВЫПОЛНЕНЫ.** +> Автоскачка на HDD **выключена фактически**: Radarr (45→**0** monitored, очередь 6→**0**, +> RSS 30→**0**) и Sonarr (13→**0**, 19→**0**, 15→**0**). Метаданные целы — 50 фильмов, +> **46 с постерами**, 24 файла на диске не тронуты. Проверено: Transmission не сдвинулся +> ни на байт за 45 с, логи Radarr пусты. Бэкапы `radarr.db`/`sonarr.db`/`config.xml` — метка +> `20260922-123404`. Осталось 4 правки (шаги 5–8): постеры через `resolver.py`, убрать +> `menu` из YAML, фикс `plugin.js`, фикс `arr.py`. См. «✅ Порядок работ» и +> «🔴 Питфоллы выполнения». **Всё на апруве, ничего сверх не трогалось.** +> > **Аудит «как это реализуемо» — ✅ ЗАВЕРШЁН 2026-09-22.** Проверено всё: > TorrServer **на диск не пишет** (`UseDisk: false`, RAM-кэш 96 МБ) — главный вопрос закрыт; > поиск торрентов работает через **Jackett-API самого Lampac** (отдельный Jackett не нужен); @@ -642,6 +653,134 @@ ssh kraken 'sed -i "s/\"catalog\": true/\"catalog\": false/" \ Для строк без рейтинга (например `- [ ] Gods don't give gifts`) — сортировать как `0`, в конец группы. Опционально: добирать через TMDB (кэш `tmdbproxy` в Lampac) — решить при реализации. +## ✅ РЕШЕНИЕ НАЙДЕНО 2026-09-22 — полная архитектура (проверено) + +### Что уже есть в системе (нашёл, проверено) + +**Модуль `watchlist_sync/resolver.py`** (`/srv/.../watchlist-sync/`) — **уже решает задачу +постеров и ID.** Умеет: +- поиск через **TMDB** (`api.themoviedb.org/3`) в `ru-RU` и `en-US` +- поиск через **Кинопоиск** (`api.poiskkino.dev/v1.4`) +- возвращает `Candidate`: `tmdb_id`, `kp_id`, `title`, `year`, `score`, `rating`, **`genres`** +- `is_horror()` — определяет ужасы по TMDB `genre_id=27` (для сортировки 🧟‍♀️) +- кэш в `.resolve_cache.json` +- различает `ok` / `ambiguous` (score < 0.75) / `not_found` + +**Модуль `watchlist_sync/arr.py`** — клиент Radarr/Sonarr. 🔴 **ВОТ ИСТОЧНИК СКАЧКИ:** +```python +"monitored": True, +"addOptions": {"searchForMovie": True}, # ← качает сразу после добавления +``` +Для сериалов: `"addOptions": {"searchForMissingEpisodes": True}` — то же самое. + +### Ключевая связка ID + +| База | Где есть | Формат | +|---|---|---| +| **Кинопоиск (kp)** | в `.md`: 68 строк со ссылками `kinopoisk.ru/film//` | `11825624` | +| **TMDB** | в Radarr (`tmdbId`), в resolver (`Candidate.tmdb_id`) | `1292695` | +| **IMDb** | в Radarr (`imdbId`), в `.md` только рейтинг | `tt31728330` | + +⚠️ **Прямой связи kp↔tmdb в файле нет.** Связка делается через **`resolver.py`**: он по +названию находит и `kp_id`, и `tmdb_id` одновременно. Это его основная работа. + +### Замер Radarr (после шагов 1–3, 2026-09-22) + +| Метрика | Значение | +|---|---| +| Фильмов | **50** | +| `monitored` | **0** ← автоскачка выключена | +| `rssSyncInterval` | **0** ← в RSS не ходит | +| Очередь | **0** | +| С `tmdbId` | 50 (все) | +| С постером | **46** | +| С рейтингом TMDB | 50 (все) | +| С файлом на диске | 24 (не трогали) | + +Sonarr: 13 сериалов, `monitored` 0, RSS 0, очередь 0. + +### Поля Radarr для каталога (проверено, `GET /api/v3/movie`) + +```json +{ + "tmdbId": 1292695, + "imdbId": "tt31728330", + "title": "They Will Kill You", + "originalTitle": "They Will Kill You", + "year": 2026, + "titleSlug": "1292695", + "hasFile": true, + "genres": ["Horror"], + "ratings": {"tmdb": {"value": 6.945}}, + "images": [{"coverType": "poster", + "remoteUrl": "https://image.tmdb.org/t/p/original/6oI4oQKTWMVUlr8Ivqydp28Ruu6.jpg"}] +} +``` + +**Названия в Radarr — латиницей** (`They Will Kill You`), в `.md` — по-русски. Перевод +делает **TMDB Proxy** (`proxy_tmdb = true` в `lampainit.js`) — уже включён. + +### КАК ИГРАЕТ LAMPA (проверено — на диск НЕ пишется) + +| Компонент | Значение | Смысл | +|---|---|---| +| TorrServer | `/ts/echo` → `MatriX.135` | жив, встроен в Lampac | +| **`UseDisk`** | **`false`** | 🔴 **на диск НЕ пишет** | +| `CacheSize` | 100663296 (96 МБ) | кэш в RAM | +| `TorrentsSavePath` | `""` (пусто) | пути сохранения нет | +| `TorrentDisconnectTimeout` | 180 | раздача живёт 3 мин после стопа | +| Занятые торренты | `[]` (пусто) | ничего не висит | + +**Поиск торрентов:** Lampa → `jackett_url = 192.168.1.14:9118` → **Lampac сам отдаёт +Jackett-совместимый API**: +```bash +curl "http://192.168.1.14:9118/api/v2.0/indexers/all/results?apikey=1&Query=matrix" +# → {"Results":[{"Tracker":"rutor","Title":"...","MagnetUri":"magnet:?xt=...","Seeders":0}]} +``` +✅ Проверено: вернул реальные результаты. Отдельный Jackett **не нужен** (его нет в системе). + +🔴 **`parser_torrent_type = 'jackett'`** в `lampainit.js` — это НЕ отдельный Jackett, +а эндпоинт внутри Lampac. Так и задумано производителем. + +### Итоговая архитектура (то, что нужно поставить) + +``` +family/documents/movies-watchlist.md ← источник (360 фильмов) + │ + ├─► resolver.py ──► TMDB + Кинопоиск + │ │ tmdb_id + постер + рейтинг + жанры + │ ▼ + │ wwwroot/watchlist-.json ← каталог ДЛЯ LAMPA (с постерами!) + │ │ + │ ▼ + │ sites/watchlist.yaml (main: только секции СВЕРХУ, БЕЗ menu) + │ │ + │ ▼ + │ Lampa: плагин catalog.js → секции с постерами СВЕРХУ родного CUB + │ + └─► arr.py (searchForMovie: False) ──► Radarr/Sonarr + └─► метаданные в базу БЕЗ скачки + +Просмотр: Lampa → TorrServer (RAM, UseDisk:false) → на HDD ничего +``` + +**Разделение ролей:** +- **resolver.py** — постеры/ID для **каталога** (360 фильмов, включая те, что не в Radarr) +- **Radarr** — справочник того, что уже добавлено (50), источник `hasFile` +- **TorrServer** — воспроизведение, на диск не пишет + +### Три правки (на апруве) + +| # | Файл | Правка | Зачем | +|---|---|---|---| +| 1 | `watchlist_sync/arr.py` | `searchForMovie: True` → **`False`** (+ у дочки то же для сериалов) | добавление без скачки | +| 2 | `catalog/sites/watchlist.yaml` | убрать блок `menu`, оставить `main` | родной CUB не подменяется | +| 3 | генератор каталога | брать постер/ID через `resolver` | реальные элементы вместо заглушек | + +⚠️ Правка №1 **может уже не понадобиться**: шаги 1–3 (unmonitor + RSS 0) закрыли скачку +на уровне состояния. Но `searchForMovie: True` в коде **включит её снова** при следующем +добавлении из списка. Поэтому фикс нужен — иначе вернётся. + ## 🎯 ПЕРЕДЕЛКА 2 — Radarr как источник метаданных без автоскачки (план Alex, 2026-09-22) ### Логика Alex (дословно) @@ -875,29 +1014,111 @@ Radarr/Sonarr после переделки = ЧИСТЫЙ СПРАВОЧНИК (monitored: false + rssSyncInterval: 0) — не ищут, не качают ``` -### ⚠️ Порядок работ (обновлён с учётом Sonarr) +### ✅ Порядок работ — ШАГИ 0–3 ВЫПОЛНЕНЫ 2026-09-22 | # | Действие | Сервис | Статус | |---|---|---|---| -| 0 | Бэкап `radarr.db` + `config.xml` (+ то же для Sonarr) | оба | ⏳ | -| 1 | Отменить очередь: 6 записей Radarr + **19 Sonarr** | оба | ⏳ | -| 2 | `monitored: false` — 45 Radarr + **13 Sonarr** | оба | ⏳ | -| 3 | `rssSyncInterval: 0` | оба | ⏳ | -| 4 | Проверка: monitored 0, очередь 0, RSS 0, Transmission тих | оба | ⏳ | -| 5 | Генератор каталога → данные из **Radarr API** (TMDB ID + постер) | — | ⏳ | -| 6 | В `sites/watchlist.yaml` убрать `menu`, оставить `main` | — | ⏳ | -| 7 | Фикс `plugin.js` (`enumerable:true`) | — | ⏳ | -| 8 | Добавление 310 фильмов из `.md` в Radarr с `monitored: false` | Radarr | ⏳ | +| 0 | Бэкап `radarr.db` + `config.xml` + `sonarr.db` + `config.xml` | оба | ✅ **сделано** | +| 1 | Отменить очередь: 6 Radarr + 19 Sonarr | оба | ✅ **сделано** (оба 0) | +| 2 | `monitored: false` — 45 Radarr + 13 Sonarr | оба | ✅ **сделано** (оба 0) | +| 3 | `rssSyncInterval: 0` | оба | ✅ **сделано** (оба 0) | +| 4 | Проверка: monitored 0, очередь 0, RSS 0, Transmission тих | оба | ✅ **сделано** | +| 5 | Генератор каталога → постеры/ID через **`resolver.py`** | — | ⏳ ждёт апрува | +| 6 | В `sites/watchlist.yaml` убрать `menu`, оставить `main` | — | ⏳ ждёт апрува | +| 7 | Фикс `plugin.js` (`enumerable:true`) | — | ⏳ ждёт апрува | +| 8 | Фикс `arr.py`: `searchForMovie` → `False` | — | ⏳ ждёт апрува | **Не трогаем:** Prowlarr, Transmission, 24 файла на диске, Jellyfin, остальные контейнеры. -### ❓ Открытый вопрос к Alex +### 📋 Как выполнены шаги 1–3 (скрипты и команды — воспроизводимо) -Radarr/Sonarr нужны **как справочник** (тогда постеры сохраняются) или -**выключаются совсем** (тогда каталог Lampa теряет постеры — их брать неоткуда)? -Ответа нет. +**Шаг 0 — бэкапы (метка `20260922-123404`)** +```bash +D=/srv/dev-disk-by-uuid-6194539b-03e0-4f33-986b-4da09834e82f/docker +TS=$(date +%Y%m%d-%H%M%S) +sudo cp $D/radarr/config/radarr.db $D/radarr/config/radarr.db.bak-$TS +sudo cp $D/radarr/config/config.xml $D/radarr/config/config.xml.bak-$TS +sudo cp $D/sonarr/config/sonarr.db $D/sonarr/config/sonarr.db.bak-$TS +sudo cp $D/sonarr/config/config.xml $D/sonarr/config/config.xml.bak-$TS +``` +Результат: `radarr.db` 4 374 528 Б, `sonarr.db` 4 870 144 Б + оба `config.xml` 650/648 Б. -### План работ (на апруве Alex) +**Шаг 1 — очистка очереди.** Скрипт `/tmp/arr_clear_queue.py` (на Mac → `scp` → запуск на Кра́кене). +`DELETE /api/v3/queue/?removeFromClient=true&blocklist=false`. +Результат: Radarr 6 → **0**, Sonarr 19 → **0**. + +**Шаг 2 — снятие monitored.** +- Radarr: `PUT /api/v3/movie/editor` с ключом **`movieIds`** → 45 → **0** +- Sonarr: `PUT /api/v3/series/editor` с ключом **`seriesIds`** → 13 → **0** + +Скрипты: `/tmp/arr_disable_autograb.py` (Radarr, упал на Sonarr с HTTP 500), +`/tmp/sonarr_unmonitor.py` (Sonarr, исправленный ключ). + +**Шаг 3 — `rssSyncInterval: 0`.** Скрипт `/tmp/radarr_rss_off.py`. +Sonarr: 15 → 0 с первого раза. Radarr: **со второго** — первый заход вернул 30 +(не отправлял `id`), фикс — отправлять **весь объект конфига с `id: 1`**. + +### 📊 Состояние ПОСЛЕ выполнения (замер 2026-09-22) + +| Метрика | Radarr | Sonarr | +|---|---|---| +| Всего записей | 50 фильмов | 13 сериалов | +| `monitored` | **0** (было 45) | **0** (было 13) | +| `rssSyncInterval` | **0** (было 30) | **0** (было 15) | +| Очередь | **0** (было 6) | **0** (было 19) | +| С `tmdbId` | 50 (все) | — | +| С постером | **46** | — | +| С рейтингом TMDB | 50 (все) | — | +| С файлом на диске | 24 (не трогали) | — | + +**Проверка пассивности:** Transmission за 45 секунд — NET `290GB/26.7GB` и +BLOCK `214GB/393kB` **не сдвинулись ни на байт**. Логи Radarr (`grep search|download|grab`) +за 2 минуты — **пусто**. TorrServer: список торрентов `[]`, `echo` → `MatriX.135`. + +### 🔴 Питфоллы выполнения (найденные в этой сессии) + +1. **`urllib` НЕ идёт по 307 автоматически, а `curl -sL` — идёт.** + Симптом: все 25 DELETE в первом прогоне вернули `HTTP Error 307: Temporary Redirect`, + **ничего не удалилось**, состояние не изменилось. + **Фикс:** свой `HTTPRedirectHandler` подкласс, который для 307/308 сохраняет + **метод и тело** (штатный `urllib` превращает DELETE в GET): + ```python + class RedirectKeepMethod(urllib.request.HTTPRedirectHandler): + def redirect_request(self, req, fp, code, msg, headers, newurl): + if code in (307, 308): + new = urllib.request.Request(newurl, data=req.data, + method=req.get_method()) + for k, v in req.headers.items(): + new.add_header(k, v) + return new + return super().redirect_request(req, fp, code, msg, headers, newurl) + ``` + Тот же корень, что у `curl` без `-L` (питфолл в памяти) — только на стороне Python. + +2. **Sonarr группирует эпизоды сезона под одним `id`.** + Симптом: после удаления первой записи остальные возвращают `HTTP 404: Not Found`. + Это **не сбой** — они исчезли вместе с родительской. Итог всё равно 0. + +3. **Ключ поля в `*/editor` различается между Radarr и Sonarr.** + Radarr `/movie/editor` → **`movieIds`**; Sonarr `/series/editor` → **`seriesIds`**. + `ids` → HTTP 500 Internal Server Error (Sonarr). + +4. **`PUT /api/v3/config/indexer` требует весь объект с `id`.** + Частичный `{"rssSyncInterval": 0}` молча возвращает старое значение. + Отправлять весь объект, полученный `GET`, с изменённым полем. + +5. **Кириллица в inline-`jq` внутри `ssh '...'`** снова сломала команду + (`unexpected INVALID_CHARACTER`) — 6 ошибок в одном вызове. Ключи объекта + писать латиницей (`total`, `monitored`, `queue`), не `всего`/`очередь`. + +### ❓ Открытый вопрос к Alex — ЗАКРЫТ + +Был вопрос «Radarr/Sonarr нужны как справочник или выключаются совсем?». +**Alex ответил резко:** «Как ты блядь думаешь мне нужны блядь постеры или нет блядь?! +Меня не ебет откуда оно их берет сделай чтобы работало как поставлено в задаче». +→ **Остаются как справочник метаданных.** Вопрос постеров больше не задавать. + +### План работ (на апруве Alex) — ШАГИ 0–4 ВЫПОЛНЕНЫ **Шаг 0 — бэкап (обязательно, до всего)** ```bash diff --git a/personal/tech/lampac-deployment.md b/personal/tech/lampac-deployment.md index ab8b7ed6..922628c1 100644 --- a/personal/tech/lampac-deployment.md +++ b/personal/tech/lampac-deployment.md @@ -240,21 +240,173 @@ volumes: Проверка синтаксиса ПЕРЕД заливкой: `node --check /tmp/catalog-plugin.js`. Статус: **не применено**, ждёт апрува Alex. -### Radarr как источник данных (постеры, TMDB ID) +### Radarr как источник данных (постеры, TMDB ID) — ✅ ПРОВЕРЕНО 2026-09-22 Фильмы уже резолвлены в Radarr — там есть TMDB ID и постеры. Это **правильный источник** для каталога; `.md` их не содержит. ```bash -# API-ключ -grep -oE "[^<]+" /srv/dev-disk-by-uuid-6194539b-.../docker/radarr/config.xml +# API-ключ Radarr +grep -oE "[^<]+" \ + /srv/dev-disk-by-uuid-6194539b-.../docker/radarr/config/config.xml +# → cbcb8ec320104359920c0f611d503c7b # ⚠️ -L обязателен, иначе 307 и пустое тело curl -sL "http://localhost:7878/api/v3/movie?apikey=$KEY" | jq length # → 50 + +# Sonarr, порт 8989, ключ 15fbec32f1cb4b05b1c2b229ce27359b +curl -sL "http://localhost:8989/api/v3/series?apikey=$SKEY" | jq length # → 13 ``` -Поля ответа: `tmdbId`, `title`, `year`, `hasFile`, `images[].remoteUrl` (постер TMDB), -`images[].coverType == "poster"`. Кэш постеров на диске: Radarr `/config/MediaCover/`. +**Поля ответа Radarr (для каталога нужны все):** +`tmdbId`, `imdbId`, `title`, `originalTitle`, `year`, `titleSlug`, `hasFile`, +`genres`, `ratings.tmdb.value`, `images[] | select(.coverType=="poster") | .remoteUrl`. +Кэш постеров на диске: Radarr `/config/MediaCover/` (46 каталогов). + +```json +{ + "tmdbId": 1292695, "imdbId": "tt31728330", + "title": "They Will Kill You", "year": 2026, + "hasFile": true, "genres": ["Horror"], + "ratings": {"tmdb": {"value": 6.945}}, + "images": [{"coverType": "poster", + "remoteUrl": "https://image.tmdb.org/t/p/original/6oI4oQKTWMVUlr8Ivqydp28Ruu6.jpg"}] +} +``` + +⚠️ **Названия в Radarr латиницей** (`They Will Kill You`), в `.md` — по-русски. +Перевод делает **TMDB Proxy** (`proxy_tmdb = true` в `lampainit.js`), уже включён. + +⚠️ **Связки kp ↔ tmdb в данных нет.** В `.md` — 68 ссылок `kinopoisk.ru/film//`, +в Radarr — `tmdbId`. Прямого соответствия нет ни в одном файле. **Мост — модуль +`watchlist_sync/resolver.py`** (см. ниже). + +### `watchlist_sync/resolver.py` — резолвер постеров (уже написан!) + +Путь: `/srv/dev-disk-by-uuid-49e8f586-3839-4c5d-a1e1-58bfc3579ade/watchlist-sync/watchlist_sync/` + +Уже решает задачу постеров/ID для каталога: +- поиск в **TMDB** (`api.themoviedb.org/3`, языки `ru-RU` + `en-US`) +- поиск в **Кинопоиске** (`api.poiskkino.dev/v1.4`) +- возвращает `Candidate`: `tmdb_id`, `kp_id`, `title`, `year`, `score`, `rating`, `genres` +- `is_horror()` — ужасы по TMDB `genre_id=27` (нужно для сортировки 🧟‍♀️) +- кэш `.resolve_cache.json`; различает `ok` / `ambiguous` (score < 0.75) / `not_found` + +### 🔴 `watchlist_sync/arr.py` — ИСТОЧНИК АВТОСКАЧКИ (в коде!) + +```python +# radarr_add(): +"monitored": True, +"addOptions": {"searchForMovie": True}, # ← качает СРАЗУ после добавления + +# sonarr_add(): +"monitored": True, +"addOptions": {"searchForMissingEpisodes": True}, # ← то же для сериалов +``` + +⚠️ **Даже если снять `monitored` вручную, следующий запуск добавления из списка +включит автоскачку снова** — флаг зашит в код. Фикс: `searchForMovie` → `False`, +`searchForMissingEpisodes` → `False`. **Статус: не применён, на апруве.** + +### Как выключить автоскачку на HDD — ✅ ВЫПОЛНЕНО 2026-09-22 + +| Сервис | Очередь | `monitored` | `rssSyncInterval` | +|---|---|---|---| +| **Radarr** | 6 → **0** | 45 → **0** | 30 → **0** | +| **Sonarr** | 19 → **0** | 13 → **0** | 15 → **0** | + +Метаданные **целы**: 50 фильмов, 46 с постерами, 50 с `tmdbId` и рейтингом, +24 файла на диске **не тронуты**. Бэкапы — метка `20260922-123404`. + +**Проверка пассивности:** Transmission за 45 с — NET `290GB/26.7GB`, BLOCK `214GB/393kB` +не сдвинулись. Логи Radarr (`search|download|grab`) за 2 мин — пусто. + +**Правильные эндпоинты (проверено):** +```bash +# отмена загрузки — файлы на диске НЕ трогаются +DELETE /api/v3/queue/?removeFromClient=true&blocklist=false + +# снятие monitored — КЛЮЧ ПОЛЯ РАЗНЫЙ! +PUT /api/v3/movie/editor {"movieIds": [...], "monitored": false} # Radarr +PUT /api/v3/series/editor {"seriesIds": [...], "monitored": false} # Sonarr, НЕ "ids" + +# RSS off — отправлять ВЕСЬ объект конфига с id, иначе молча вернёт старое +PUT /api/v3/config/indexer {"rssSyncInterval": 0, "id": 1, ...остальное} +``` + +**Откат:** +```bash +curl -sL -X PUT ".../api/v3/movie/editor" -d '{"movieIds":[...],"monitored":true}' +curl -sL -X PUT ".../api/v3/series/editor" -d '{"seriesIds":[...],"monitored":true}' +curl -sL -X PUT ".../api/v3/config/indexer" -d '{"rssSyncInterval":30,"id":1,...}' +cp radarr.db.bak-20260922-123404 radarr.db && docker restart radarr +``` + +### 🔴 Питфолл: `urllib` не идёт по 307, `curl -sL` идёт + +Симптом: **все** DELETE/PUT из Python-скриптов вернули +`HTTP Error 307: Temporary Redirect`, **ничего не изменилось**. +Штатный `urllib` на 307/308 превращает DELETE в GET и теряет тело. + +**Фикс — свой redirect-хендлер:** +```python +class RedirectKeepMethod(urllib.request.HTTPRedirectHandler): + def redirect_request(self, req, fp, code, msg, headers, newurl): + if code in (307, 308): + new = urllib.request.Request(newurl, data=req.data, + method=req.get_method()) + for k, v in req.headers.items(): + new.add_header(k, v) + return new + return super().redirect_request(req, fp, code, msg, headers, newurl) + +_OPENER = urllib.request.build_opener(RedirectKeepMethod) +# далее _OPENER.open(req) вместо urllib.request.urlopen(req) +``` + +Прочие грабли сессии: +- **Sonarr группирует эпизоды сезона под одним `id`** — после удаления первой записи + остальные дают `404`, это **не сбой**, они ушли с родительской. +- **Кириллица внутри inline-`jq` в `ssh '...'`** ломает команду + (`unexpected INVALID_CHARACTER`). Ключи объекта — латиницей: `total`, `monitored`, `queue`. + +## TorrServer внутри Lampac — на HDD НЕ пишет (проверено 2026-09-22) + +| Параметр | Значение | Смысл | +|---|---|---| +| `UseDisk` | **`false`** | 🔴 **на диск не пишет вообще** | +| `CacheSize` | 100663296 (96 МБ) | кэш в RAM | +| `TorrentsSavePath` | `""` | пути сохранения нет | +| `TorrentDisconnectTimeout` | 180 | раздача живёт 3 мин после остановки | + +```bash +# конфиг TorrServer +docker exec lampac cat /lampac/data/ts/settings.json | jq '.BitTorr | {UseDisk, CacheSize, TorrentsSavePath}' + +# жив ли +curl -s http://192.168.1.14:9118/ts/echo # → MatriX.135 +curl -s -X POST http://192.168.1.14:9118/ts/torrents \ + -H "Content-Type: application/json" -d '{"action":"list"}' # → [] +``` + +## 🔴 Поиск торрентов: отдельного Jackett НЕТ, его роль играет Lampac + +`lampainit.js` прописывает: +```js +Lampa.Storage.set('parser_torrent_type', 'jackett'); +Lampa.Storage.set('jackett_url', '192.168.1.14:9118'); // ← сам Lampac! +Lampa.Storage.set('jackett_key', '1'); +``` + +Контейнера **Jackett в системе нет**. Lampac отдаёт Jackett-совместимый API сам: + +```bash +# ⚠️ на КОРНЕ, БЕЗ префикса /jackett (с ним → 404) +curl "http://192.168.1.14:9118/api/v2.0/indexers/all/results?apikey=1&Query=matrix" +# → {"Results":[{"Tracker":"rutor","Title":"...","MagnetUri":"magnet:?xt=...","Seeders":0}]} +``` +✅ Проверено: вернул реальные результаты с магнит-ссылками. +**Отдельный Jackett ставить НЕ нужно** — так задумано производителем. ## Занятые порты на Кра́кене (2026-09-22) `7878` Radarr · `8096` Jellyfin · `8191` FlareSolverr · `8989` Sonarr · `9000` Portainer ·