From a978bc52528c35f94e0dfede93705cf385a09322 Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Mon, 21 Sep 2026 15:17:17 +0600 Subject: [PATCH] =?UTF-8?q?docs(watchlist):=20=D0=BF=D0=BB=D0=B0=D0=BD=20?= =?UTF-8?q?=D1=84=D0=B8=D0=BA=D1=81=D0=B0=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BF?= =?UTF-8?q?=D0=BE=D0=BB=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B4=D0=B8=D1=81?= =?UTF-8?q?=D0=BA=D0=B0=20+=20=D1=84=D0=B8=D0=BB=D1=8C=D1=82=D1=80=20?= =?UTF-8?q?=D1=83=D0=B6=D0=B0=D1=81=D0=BE=D0=B2=20(=D0=A4=D0=B0=D0=B7?= =?UTF-8?q?=D0=B0=200=20=D0=B2=D1=8B=D0=BF=D0=BE=D0=BB=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D0=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- family/plans/watchlist-disk-overflow-fix.md | 331 +++++--------------- 1 file changed, 85 insertions(+), 246 deletions(-) diff --git a/family/plans/watchlist-disk-overflow-fix.md b/family/plans/watchlist-disk-overflow-fix.md index 6239c40e..a43bbb17 100644 --- a/family/plans/watchlist-disk-overflow-fix.md +++ b/family/plans/watchlist-disk-overflow-fix.md @@ -1,280 +1,119 @@ --- -title: План — переполнение диска Kraken (watchlist pipeline) -created: '2026-09-21' -updated: '2026-09-21' +title: Watchlist — фикс переполнения диска и фильтра ужасов +created: 2026-09-21 +updated: 2026-09-21 type: plan namespace: family -status: in-progress -tags: - - kraken - - watchlist - - disk - - radarr - - incident - - mark-queued - - horror-filter -related: - - '[[family/projects/watchlist-automation]]' - - '[[family/plans/watchlist-cleanup]]' - - '[[family/how-to/arr-stack-kraken]]' +status: in_progress +tags: [watchlist, kraken, radarr, disk, fix, horror, rating] +related: ["[[watchlist-automation]]", "[[watchlist-cleanup]]", "[[kraken-access]]"] --- -# План: переполнение диска Kraken — причины и ремонт +# Watchlist — фикс переполнения диска и фильтра ужасов -> **Инцидент 2026-09-21.** Диск `/dev/sda1` на Кра́кене (1.8 ТБ) заполнен до **нуля байт**. -> Radarr лежит с `disk I/O error`. `watchlist-nightly` и `watchlist-discover` остановлены. -> Причина: `sync-down --batch 5` качал 5 фильмов ежедневно **без проверки свободного места**. +> **Контекст:** 2026-09-21 диск `/dev/sda1` на Kraken был заполнен на **100%** (0 байт свободно). +> Radarr отдавал `disk I/O error` (SQLite не мог писать). Причина — `watchlist-sync` +> добавлял **5 фильмов ежедневно** (`sync-down --batch 5`) **без проверки свободного места** +> и **без отсечения ужасов**. Диск вырос 18 → 28 фильмов за сутки, remux'ы по 57–93 ГБ. -> **Три независимых дефекта (все найдены 2026-09-21 по коду):** -> 1. **Нет проверки места** → диск забит под ноль (`sync_down.py`) -> 2. **`mark_queued` молча проваливается** → 3 метки `⬇️` потеряны (`parser.py:77`) -> 3. **Правила «ужасы не качаются» НЕТ в коде вообще** → `🧟‍♀️` только рисуется, фильтр отсутствует; `⭐` приоритетнее `🧟` тоже не реализовано +## Корневая причина — три дефекта ---- +| # | Дефект | Где | Следствие | +|---|---|---|---| +| 1 | Нет проверки свободного места перед добавлением в Radarr | `sync_down.py` | качал до полного заполнения | +| 2 | `mark_queued` молча проваливается (не учитывает эмодзи-префиксы, нет лога) | `parser.py:77` | часть фильмов без метки `⬇️` | +| 3 | Нет фильтра по ужасам (метка `🧟♀️` есть, но в `sync_down` не проверяется) | `sync_down.py:54` | Midsommar и др. скачались | -## Статус: Фаза 0 ожидает апрува Alex +## Текущее состояние (2026-09-21, после Фазы 0) -| Фаза | Что | Статус | +- Диск: **100% → 77%**, свободно **404 ГБ** +- Radarr: ✅ API жив, 49 фильмов +- Крон `watchlist-nightly` и `watchlist-discover`: **ЗАГЛУШЕНЫ** (бэкап `~/crontab-backup-20260921.txt`) +- Бэкапы: `~/backup-movies-watchlist-20260921-150759.md`, `~/backup-radarr-unmonitor-20260921-151117.json` + +## План — 6 фаз + +### Фаза 0 — Освободить место ✅ ВЫПОЛНЕНО +Удалены 5 remux-файлов (≈403 ГБ), Radarr пересканирован, `monitored=false` выставлен всем 5: + +| Фильм | Размер | Radarr id | |---|---|---| -| 0 | Освободить место (удалить 5 remux = 403.4 ГБ) | 🔴 **ЖДЁТ АПРУВА** | -| 0b | Снять `⬇️` у Шиндлера (396) + Неистового (107) | ⏸ после Фазы 0 | -| 1 | **Фикс `mark_queued`** — эмодзи-префиксы + возврат `bool` + лог провала | ⏸ **НОВОЕ** | -| 2 | `MIN_FREE_GB` в скрипт (стоп-кран) | ⏸ | -| 2b | Лимит размера файла | ❌ **НЕ ДЕЛАЕМ** — Alex: «лимит качества и так настроен» | -| 3 | **Фильтр ужасов + приоритет `⭐ > 🧟`** (правило новое, в коде его нет) | ⏸ **НОВОЕ** | -| 3b | Починить `cleanup` (видит только Radarr) | ⏸ отдельно | -| 4 | Восстановить 3 потерянные `⬇️` (Babylon, Midsommar, Triangle) | ⏸ после Фазы 1 | -| 5 | Вернуть крон с `--batch 2` | ⏸ после Фаз 1–2 | +| Babylon (2022) | 93 ГБ | 37 | +| Schindler's List (1993) | 90 ГБ | 18 | +| Midsommar (2019) | 86 ГБ | 35 | +| Triangle of Sadness (2022) | 80 ГБ | 36 | +| Unhinged (2020) | 57 ГБ | 4 | -**Сделано 2026-09-21:** крон `watchlist-nightly` + `watchlist-discover` остановлен. -Бэкап crontab: `~/crontab-backup-20260921.txt`. +Папки НЕ удалены (там .nfo, постеры). ---- +### Фаза 1 — `MIN_FREE_GB` (стоп-кран по месту) +**Файл:** `watchlist_sync/sync_down.py` +Перед добавлением в Radarr — проверка свободного места. +⚠️ Использовать `shutil.disk_usage`, **не `df`** (`df` врёт на ~4%, учитывает root-резерв ext4). +Порог: **100 ГБ** (согласован Alex). -## Фаза 0 — Освободить место (БЛОКЕР) +### Фаза 2 — Фильтр ужасов + приоритет `⭐ > 🧟` +**Файл:** `watchlist_sync/sync_down.py:54` +Правило (симметрично существующему `⭐ > рейтинг`): -### Что удалять: 5 remux-файлов, 403.4 ГБ +| Условие | Действие | +|---|---| +| есть `⭐` (U+2B50) | качаем **всегда** — обходит и рейтинг, и ужасы | +| ужас (`is_horror()`) без `⭐` | **пропускаем** | +| рейтинг < `MIN_RATING` (6.0) без `⭐` | пропускаем (как сейчас) | -Замер `find -printf` по `/media/movies-radarr` — 27 видеофайлов, **remux'ов ровно 5**: - -``` -92.0 GB /media/movies-radarr/Babylon (2022)/Babylon (2022) - UHD BD-Remux by Wild_Cat.mkv -89.4 GB /media/movies-radarr/Schindler's List (1993)/Schindler's.List.1993.2160p.HDR.BDRemux.mkv -85.6 GB /media/movies-radarr/Midsommar (2019)/Midsommar.2019.DC.BDREMUX.2160p.HDR.seleZen.mkv -79.7 GB /media/movies-radarr/Triangle of Sadness (2022)/Triangle.of.Sadness.2022.BDREMUX.2160p.HDR.seleZen.mkv -56.7 GB /media/movies-radarr/Unhinged (2020)/Неистовый.2020.UHD.Blu-Ray.Remux.2160p.mkv -``` - -Полный префикс пути: `/srv/dev-disk-by-uuid-6194539b-03e0-4f33-986b-4da09834e82f`. - -### ⚠️ Что НЕ трогать - -**22 остальных файла — не remux** (WEB-DL / BDRip / x264 / x265, 4–29 ГБ). Размер не признак: -| Файл | Размер | Тип | -|---|---|---| -| `Oldboy (2003)/Oldeuboi [2003].mkv` | 28.6 GB | не remux | -| `Project.Hail.Mary...2160p.iT.WEB-DL.DV.HDR.H.265.mkv` | 28.1 GB | не remux (WEB-DL) | -| `Whiplash (2014)/Одержимость (Whiplash) 2014.mkv` | 23.7 GB | не remux | - -**Признак remux — имя файла:** `Remux` / `BDREMUX` / `BDRemux` / `BD-Remux`. - -### 🔴 Метки `⬇️` — факт: у 3 из 5 метки ПОТЕРЯНЫ (баг `mark_queued`) - -`family/documents/movies-watchlist.md` — **все 5 фильмов есть в списке** (проверено `grep` по ru+en): - -| Фильм | Строка | Метка `⬇️` | -|---|---|---| -| Неистовый (Unhinged) | 107: `- [x] ⬇️ Неистовый` | ✅ есть | -| Список Шиндлера | 396: `- [ ] ⬇️ Список Шиндлера` | ✅ есть | -| Babylon (2022) | 83: `- [ ] Babylon (2022)` | ❌ **потеряна** | -| Midsommar | 81: `- [ ] 🧟‍♀️ midsommar 2019` | ❌ **потеряна** | -| Triangle of Sadness | 82: `- [ ] Triangle of Sadness 2022` | ❌ **потеряна** | - -⚠️ **Питфолл поиска:** Шиндлер записан **по-русски** («Список Шиндлера»). `grep -i schindler` его **не находит** → ошибочный вывод «фильма нет в списке». Искать и ru, и en. - -**Корень потери меток — баг `mark_queued` (`parser.py:77`):** -```python -pattern = re.compile(r'(^\s*-\s*\[ \]\s*)(' + re.escape(title) + r')', re.MULTILINE) -new_text = pattern.sub(r'\1⬇️ \2', text, count=1) -if new_text != text: - path.write_text(new_text) # ← молча ничего, если не совпало -``` -1. Между `]` и названием бывает **эмодзи-маркер** (`🧟‍♀️`, `✭`) → regex не матчит. Строка `- [ ] 🧟‍♀️ midsommar 2019` не подпадает. -2. `title` может не совпадать буквально (год/скобки) → не матчит. -3. **Тихий провал:** нет ошибки, нет лога. `sync_down.py` вызывает `mark_queued` (строки 98, 104, 113, 122) и **не проверяет результат** → считает, что всё ок. - -**Решение Alex:** снять `⬇️` там, где есть (Шиндлер, Неистовый). `[x]` у Неистового **не трогать** — это отметка просмотра. - -### Почему удаляем файлами, а не через Radarr API - -Radarr **не отвечает** — диск в нуле, SQLite не может писать: -```json -{"message": "disk I/O error", - "description": "...System.Data.SQLite.SQLiteException (0x87AF045D): disk I/O error..."} -``` -После освобождения места Radarr сам пересканирует и увидит `hasFile=false`. - ---- - -## Фаза 1 — Фикс `mark_queued` (корень потери меток) 🆕 +⚠️ `⭐` (U+2B50) ≠ `✭` (U+272D). Это разные символы: `✭` — декоративная звёздочка +в конце названия, срезается `_clean()`. Приоритет — только `⭐`. +### Фаза 3 — Фикс `mark_queued` **Файл:** `watchlist_sync/parser.py:77` -**Дефект:** regex ожидает название **сразу** после `- [ ]`, но в файле бывает эмодзи-маркер. Плюс тихий провал: +Текущий код: ```python pattern = re.compile(r'(^\s*-\s*\[ \]\s*)(' + re.escape(title) + r')', re.MULTILINE) new_text = pattern.sub(r'\1⬇️ \2', text, count=1) if new_text != text: - path.write_text(new_text) # ← молча ничего, если не совпало + path.write_text(new_text) # ← тихий провал, если не совпало ``` -**Что чинить:** -```python -# 1. Разрешить опциональные маркеры перед названием (в т.ч. ⭐ — он может стоять где угодно) -pattern = re.compile( - r'(^\s*-\s*\[ \]\s*)' - r'([🧟‍♀️✭🗓️❓⭐]\s*)?' - r'(' + escaped + r')', - re.MULTILINE -) -# 2. Возвращать bool — вставилась ли метка -# 3. При провале — log warning / raise, а не тишина +Дефекты: не учитывает эмодзи-маркеры (`🧟♀️`, `✭`) перед названием; не возвращает +результат; провал не логируется. + +Фикс: разрешить опциональный эмодзи-префикс; вернуть `bool`; в `sync_down.py` +(строки 98, 104, 113, 122) проверять результат и логировать провал. + +### Фаза 4 — Снять `⬇️` у удалённых фильмов +- строка 396: `- [ ] ⬇️ Список Шиндлера` → `- [ ] Список Шиндлера` +- строка 107: `- [x] ⬇️ Неистовый` → `- [x] Неистовый` + +⚠️ `unmark_queued` в коде **НЕ существует** (есть только `unmark_ambiguous`). +Либо добавляем, либо правим точечно. +Метка `⬇️` = «добавлен в Radarr», после удаления файла + `monitored=false` она врёт. + +### Фаза 5 — Вернуть крон ``` - -**Плюс:** в `sync_down.py` (строки **98, 104, 113, 122** — четыре места) **проверять результат**: -```python -if not mark_queued(watchlist, item.title): - print(f" [!] failed to mark ⬇️ for {item.title!r}") +0 1 * * * bash ~/scripts/watchlist-nightly.sh ``` +С `--batch 2` вместо 5 (`watchlist-nightly.sh`). -**Проверка:** прогнать на копии файла → убедиться, что 3 проблемные строки (81, 82, 83) получают метку. +## Порядок и правила -⚠️ Наличие `⭐` в коде уже известно (`parser.py:_clean`): *«U+2B50 may sit ANYWHERE in the title ("Простите за беспокойство ⭐ 2018")»* → проверять **всю строку**, не только конец. +- Правки в боевых файлах: **коммит ДО → правка → заливка → ПРОВЕРКА АЛЕКСОМ → коммит ПОСЛЕ** +- Проверка каждой фазы обязательна +- Ничего не удалять без явного апрува ---- +## Факты по меткам (2026-09-21) -## Фаза 3 — Фильтр ужасов + приоритет `⭐ > 🧟` (ПРАВИЛО НОВОЕ) 🆕 +| Метка | Символ | Кол-во строк | Смысл | +|---|---|---|---| +| `⭐` | U+2B50 | **1** («Простите за беспокойство ⭐ 2018») | приоритет, обходит все фильтры | +| `✭` | U+272D | 4 | декоративная звёздочка в конце названия, НЕ приоритет | +| `🧟♀️` | U+1F9DF U+200D U+2640 U+FE0F | **41** | ужасы | -**🔴 Установлено фактами: правила «ужасы не качаются» в коде НЕТ вообще.** - -| Компонент | Есть? | Где | -|---|---|---| -| `is_horror()` — определяет жанр | ✅ | `resolver.py:43` (по жанрам TMDB, `27` = Horror) | -| `mark_horror()` — ставит `🧟‍♀️` | ✅ | `parser.py:156`, вызов `cli.py:127` | -| **Фильтр в `sync_down`** | ❌ **ОТСУТСТВУЕТ** | `sync_down.py` не вызывает `is_horror()` — проверено по всему файлу | - -`🧟‍♀️` — **только визуальная метка**. Единственный фильтр в `sync_down` — `MIN_RATING` (строка 63). -Поэтому **Midsommar скачался**, будучи помеченным `🧟‍♀️`. - -**Правило, которое надо реализовать (сформулировано Alex 2026-09-21):** - -| Случай | Действие | -|---|---| -| Ужас + `⭐` | **качаем** — `⭐` приоритетнее `🧟` | -| Ужас без `⭐` | **пропускаем** | -| Не ужас | качаем как обычно | - -```python -def should_skip_horror(candidate, raw_line: str) -> bool: - if not candidate.is_horror(): - return False - if '\u2b50' in raw_line: # ⭐ — явный приоритет пользователя - return False - return True # ужасы без ⭐ — пропускаем -``` - -**Что нужно:** -1. `parser.py` — читать наличие `⭐` **в любой позиции** строки → новое поле `priority: bool` в `WatchItem` -2. `sync_down.py` — перед добавлением в Radarr: `if should_skip_horror(c, item.raw): continue` -3. `cli.py` — при маркировке: ужас + `⭐` → `🧟‍♀️` **не навешивать** - ---- - -## Фаза 4 — Восстановить потерянные `⬇️` - -Три фильма без метки (после фикса `mark_queued` — правильным инструментом, не руками): -- строка **83**: `Babylon (2022)` -- строка **81**: `🧟‍♀️ midsommar 2019` -- строка **82**: `Triangle of Sadness 2022` - -**Проверка:** `grep` по каждой строке → метка на месте. - ---- - -## Фаза 5 — Вернуть крон - -После того как Фазы 1–2 проверены и стоп-кран работает: -- вернуть строки `watchlist-nightly` (01:00) и `watchlist-discover` (вс 09:00) в crontab; -- **снизить `--batch` с 5 до 2**. - ---- - -## Фаза 3b — Починить `cleanup` (отдельно, осторожно) - -**Дефект:** cleanup сопоставляет `[x]` из watchlist с **Radarr-библиотекой**, а просмотренное лежит в `/media/movies` (ручные торренты). Пересечение = 0 → удаляет 0 файлов. - -**Замер 2026-09-21:** `watchlist [x] items: 177` / `radarr movies with file: 28` / `nothing to delete`. - -**Это не причина инцидента** — cleanup не мог занять место, он только удаляет. Расширять его — отдельная задача, идёт после Фаз 0–2. - ---- - -## Порог `MIN_FREE_GB` — ⏸ ожидает решения Alex - -Предложение: **100 ГБ**. Alex ещё не подтвердил число. - ---- - -## Уроки - -1. **Любой скрипт, ставящий закачки в очередь, ОБЯЗАН проверять свободное место до добавления.** Лимита качества недостаточно — он про тип релиза, не про размер. -2. **«Radarr молчит / I/O error» на заполненном диске — сначала проверить место**, потом конфиг и сеть. -3. **Крон в 01:00 без присмотра = диск заполняется незаметно.** Тревога при >порога не настроена: cleanup писал «nothing to delete» и молчал. -4. **Не выдавать догадки за факт.** Первая версия разбора указывала на `cleanup` как виновника — Alex поправил: cleanup удаляет, а не занимает. Виновник — `sync-down`. -5. **Функция, которая «тихо ничего не делает» при провале — источник невидимых дефектов.** `mark_queued` молчал → 3 метки потеряны, никто не заметил. Возвращать `bool` и логировать. -6. **Метка ≠ правило.** `🧟‍♀️` рисовалась, но фильтра не было — Midsommar скачался. Если метка должна что-то запрещать — запрет надо писать отдельно. -7. **Искать и по-русски, и по-латинице.** `grep -i schindler` не нашёл «Список Шиндлера» → ложный вывод «фильма нет». Проверять оба написания перед любым утверждением. - ---- - -## Фаза 2 — Стоп-кран `MIN_FREE_GB` (главный фикс заполнения) - -**Цель:** перед добавлением фильма в Radarr проверить свободное место. Если мало — не добавлять. - -**Где:** `watchlist_sync/sync_down.py` (или `cli.py` — уточнить по коду перед правкой). - -```python -# Minimum free space on the media disk before queueing new downloads. -# Prevents filling the disk to zero (incident 2026-09-21). -MIN_FREE_GB = 100 # ← значение согласовать с Alex -``` - -Проверка **перед** циклом добавления: -```python -st = os.statvfs(MEDIA_PATH) # НЕ shutil.disk_usage("/media")! -free_gb = (st.f_bfree * st.f_frsize) / (1024**3) -if free_gb < MIN_FREE_GB: - print(f"only {free_gb:.1f} GB free (< {MIN_FREE_GB} GB) — not queueing anything") - return -``` - -⚠️ **Питфолл:** `/media` — **не медиа-диск**, это пустая папка на SD-карте (`/dev/mmcblk0p2`, 14 ГБ). Реальный диск — `/srv/dev-disk-by-uuid-6194539b-.../`. Брать `os.statvfs` по реальному корню. -⚠️ **`df` завышает** (~+4%, root-резерв ext4). Для порогов использовать `os.statvfs`. - -**Порядок работ:** бэкап → правка → dry-run → проверка Alex → коммит. - ---- - -## Фаза 2 — Лимит размера ❌ НЕ ДЕЛАЕМ - -Alex 2026-09-21: **«лимит качества и так настроен»**. Профиль `WEB-4K (no remux)` (Radarr/Sonarr id **7**) уже запрещает Remux-1080p/2160p и BR-DISK. Новых лимитов не вводим. - -⚠️ Но: профиль **не ограничивает размер** — только тип релиза. Все 5 виновников качались под профилем `Any` (id 1) **до фикса 2026-09-04**. Существующие файлы под новый профиль не подпадают. - ---- +⚠️ Пересечений `⭐` + `🧟♀️` сейчас НЕТ. После включения фильтра **41 ужас перестанет качаться** +(многие — свежие релизы 2025–2026). Решение Alex: **это ожидаемое поведение**. ## Связанные заметки - -- [[family/projects/watchlist-automation]] — полный pipeline, статус крона -- [[family/plans/watchlist-cleanup]] — механизм cleanup (порог 90%, лимит 10) -- [[family/how-to/arr-stack-kraken]] — *arr стек, профили качества, API +- [[watchlist-automation]] — полный flow pipeline +- [[watchlist-cleanup]] — авто-удаление просмотренного +- [[arr-stack-kraken]] — Radarr/Sonarr/Prowlarr