Files
obsidian-vault/family/plans/watchlist-disk-overflow-fix.md
T

281 lines
16 KiB
Markdown
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: План — переполнение диска Kraken (watchlist pipeline)
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]]'
---
# План: переполнение диска Kraken — причины и ремонт
> **Инцидент 2026-09-21.** Диск `/dev/sda1` на Кра́кене (1.8 ТБ) заполнен до **нуля байт**.
> Radarr лежит с `disk I/O error`. `watchlist-nightly` и `watchlist-discover` остановлены.
> Причина: `sync-down --batch 5` качал 5 фильмов ежедневно **без проверки свободного места**.
> **Три независимых дефекта (все найдены 2026-09-21 по коду):**
> 1. **Нет проверки места** → диск забит под ноль (`sync_down.py`)
> 2. **`mark_queued` молча проваливается** → 3 метки `⬇️` потеряны (`parser.py:77`)
> 3. **Правила «ужасы не качаются» НЕТ в коде вообще** → `🧟‍♀️` только рисуется, фильтр отсутствует; `⭐` приоритетнее `🧟` тоже не реализовано
---
## Статус: Фаза 0 ожидает апрува Alex
| Фаза | Что | Статус |
|---|---|---|
| 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 |
**Сделано 2026-09-21:** крон `watchlist-nightly` + `watchlist-discover` остановлен.
Бэкап crontab: `~/crontab-backup-20260921.txt`.
---
## Фаза 0 — Освободить место (БЛОКЕР)
### Что удалять: 5 remux-файлов, 403.4 ГБ
Замер `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` (корень потери меток) 🆕
**Файл:** `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) # ← молча ничего, если не совпало
```
**Что чинить:**
```python
# 1. Разрешить опциональные маркеры перед названием (в т.ч. ⭐ — он может стоять где угодно)
pattern = re.compile(
r'(^\s*-\s*\[ \]\s*)'
r'([🧟‍♀️✭🗓️❓⭐]\s*)?'
r'(' + escaped + r')',
re.MULTILINE
)
# 2. Возвращать bool — вставилась ли метка
# 3. При провале — log warning / raise, а не тишина
```
**Плюс:** в `sync_down.py` (строки **98, 104, 113, 122** — четыре места) **проверять результат**:
```python
if not mark_queued(watchlist, item.title):
print(f" [!] failed to mark ⬇️ for {item.title!r}")
```
**Проверка:** прогнать на копии файла → убедиться, что 3 проблемные строки (81, 82, 83) получают метку.
⚠️ Наличие `⭐` в коде уже известно (`parser.py:_clean`): *«U+2B50 may sit ANYWHERE in the title ("Простите за беспокойство ⭐ 2018")»* → проверять **всю строку**, не только конец.
---
## Фаза 3 — Фильтр ужасов + приоритет `⭐ > 🧟` (ПРАВИЛО НОВОЕ) 🆕
**🔴 Установлено фактами: правила «ужасы не качаются» в коде НЕТ вообще.**
| Компонент | Есть? | Где |
|---|---|---|
| `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**. Существующие файлы под новый профиль не подпадают.
---
## Связанные заметки
- [[family/projects/watchlist-automation]] — полный pipeline, статус крона
- [[family/plans/watchlist-cleanup]] — механизм cleanup (порог 90%, лимит 10)
- [[family/how-to/arr-stack-kraken]] — *arr стек, профили качества, API