diff --git a/family/how-to/navidrome-truenas-android.md b/family/how-to/navidrome-truenas-android.md index 8bbcedf1..eae0e9e1 100755 --- a/family/how-to/navidrome-truenas-android.md +++ b/family/how-to/navidrome-truenas-android.md @@ -2,9 +2,11 @@ **Статус:** ✅ **РАЗВЁРНУТ 2026-09-27** — Navidrome на TrueNAS, работает. ✅ **2026-09-27 (сессия 22):** теги 306 исправлены, **БД Navidrome снесена и построена заново**, скобочных сирот **0**, треков **8167**. +✅ **2026-09-27 (сессия 29-кватер):** `.ndignore` дополнен 2 бэкап-папками → живая музыка **7861** (= плану), бэкапы помечены `missing=1`. 🔴 **Ни `restart`, ни пересоздание контейнера НЕ дают полного скана** (`fullScan=false`) — теги не перечитываются. Рабочий способ: **снести `navidrome.db`** (тогда старт → `fullScan=true`). +🔑 **Любой запрос к `media_file` — ТОЛЬКО с `missing=0 AND path NOT LIKE 'backup%'`** (иначе всплывают бэкапы → ложные «дефекты»). **Дата:** 2026-05-10 -**Обновлено:** 2026-09-27 (v7 — БД пересоздана, 0 сирот, треков 8167) +**Обновлено:** 2026-09-27 (v8 — `.ndignore` дополнен, живая музыка 7861; найден корень дефекта `NON_ARTIST` в `build_fix_plan2.py`) --- @@ -417,13 +419,83 @@ sqlite3 navidrome-merged.db "SELECT count(*) FROM album; SELECT count(*) FROM me **Результат сессии 24:** 7861 трек сверено, расхождений `album` **50** + `albumartist` **9** (все — дефект самого плана, а не применения). Разбор → [[personal/plans/music-artist-albumartist-repair]] §СЕССИЯ 24. -### 🔴 ПИТФОЛЛ: бэкап-папки ВНУТРИ `/music` попадают в БД +### ✅ ИСПРАВЛЕНО 2026-09-27 (сессия 29-кватер): `.ndignore` дополнен, бэкапы ушли из БД -`backup_before_306/20260927-051727/` и родственные проиндексированы Navidrome — -**306 «лишних» треков** в БД, которых нет в плане. `.ndignore` их **не покрывает** (в файле -перечислены только `backup`, `backup_conv40`, `backup_fix`, `backup_raw`). -**Держать бэкапы музыки ВНЕ `/music`** либо дописывать каждую новую бэкап-папку в `.ndignore` -до старта сканирования (после скана запись не убирается автоматически — только через снос БД). +**Причина, почему бэкапы были в БД:** `.ndignore` создан **03:49**, а две бэкап-папки появились +**позже** — `backup_before_306` в **05:17**, `backup_album_root_20260927-album-root-229` в **06:19**. +Файл не обновлялся после создания новых бэкапов. Остальные 4 папки были в списке с самого начала. + +**Было (43 байта, 4 строки):** +``` +backup +backup_conv40 +backup_fix +backup_raw +``` + +**Стало (103 байта, 6 строк)** — дописаны 2 отсутствовавшие: +``` +backup +backup_album_root_20260927-album-root-229 +backup_before_306 +backup_conv40 +backup_fix +backup_raw +``` + +**Порядок правки (боевой конфиг на NAS):** +```bash +# 1. бэкап файла +ssh truenas_admin@mallexxx.duckdns.org \ + "cp /mnt/RED_2TB/storage/Music/.ndignore /mnt/RED_2TB/storage/Music/.ndignore.bak-\$(date +%Y%m%d-%H%M%S)" +# 2. перечислить ВСЕ бэкап-папки и сверить со списком (2 отсутствовали) +ssh truenas_admin@mallexxx.duckdns.org "cd /mnt/RED_2TB/storage/Music && ls -d */ | grep -i backup" +# 3. новый файл собрать ЛОКАЛЬНО (write_file) → scp → cp НА МЕСТО (НЕ sed!) +scp /tmp/ndignore-new truenas_admin@mallexxx.duckdns.org:/tmp/ndignore-new +ssh truenas_admin@mallexxx.duckdns.org "cp /tmp/ndignore-new /mnt/RED_2TB/storage/Music/.ndignore" +# 4. рестарт + скан +ssh truenas_admin@mallexxx.duckdns.org "docker restart navidrome && sleep 20 && docker logs --tail 25 navidrome | grep -i 'scan completed'" +``` + +**Результат (факт из `sqlite3` на NAS):** + +| Метрика | До | После | +|---|---:|---:| +| `media_file` всего | 8167 | **8396** | +| из них `missing=1` (игнор) | — | **535** | +| **живая музыка** (`missing=0 AND path NOT LIKE 'backup%'`) | 7861 | **7861** ✅ | + +Лог скана: `Scanner: Finished scanning all libraries duration=5.19s` → `Scan completed`. +Игнорируемые файлы Navidrome **помечает `missing=1`, НЕ удаляет** — записи остаются в БД. + +> 🔴 **ПРАВИЛО (закреплено в скилле `truenas-administration` Area 3, RULE 0):** +> **любой новый `backup*` под `/mnt/RED_2TB/storage/Music/` обязан быть дописан в `.ndignore` +> В ТОМ ЖЕ ПРОГОНЕ, что его создал.** Скрипты бэкапа (`backup_306.py` и подобные) должны +> сами обновлять `.ndignore` после создания папки. + +### 🔴 ПИТФОЛЛ (главный, метод): ЗАПРОС К `media_file` БЕЗ ФИЛЬТРА БЭКАПА ДАЁТ ЛОЖНЫЕ ВЫВОДЫ + +**Эта ошибка дала неверный отчёт «скобки не исправлены».** Alex спросил, почему `(zvezda)`, +`(Несчастный Случай)`, `(Авария)` до сих пор в базе, если скобки правили. Запрос был сделан +**без фильтра** — и вытащил 306 файлов бэкапа (`backup_before_306/20260927-051727/...`), +которые и содержали старые скобочные значения. Я выдал бэкап за живое дерево. + +**В живом дереве этих альбомов со скобками НЕТ — правка отработала.** Проверка после фикса: +`SELECT DISTINCT album FROM media_file WHERE missing=0 AND path NOT LIKE 'backup%' AND (album LIKE '(%' OR album LIKE '%)')` +→ **0 альбомов-обёрток**. Оставшиеся 17 значений вида «название (уточнение)» +(`Hey Baby (Single)`, `Wider (Domestic RMX)`, `Outcasts (DEMOS)`) — **осмысленные части +названий, правки не требуют** (скобка в КОНЦЕ = уточнение, не обёртка вокруг всего имени). + +> 🔑 **ОБЯЗАТЕЛЬНЫЙ ФИЛЬТР для любого запроса к `media_file`:** +> `WHERE missing=0 AND path NOT LIKE 'backup%'` +> Без него всплывают файлы бэкапов → ложные «дефекты», которых нет. + +### 🔴 ДВА КЛАССА СКОБОК — НЕ СМЕШИВАТЬ + +| Класс | Пример | Правка | +|---|---|---| +| Скобка **вокруг всего** названия | `(Авария)`, `(zvezda)`, `(Несчастный Случай)`, `(Высоцкий)`, `(Морские)` | ✅ правится → `Авария`, `zvezda`, … | +| Скобка **в конце** как уточнение | `Бумер (2003)`, `Муза (EP)`, `Война (Акустика)`, `Outcasts (DEMOS)` | ❌ НЕ правится — часть названия | ## Остаточные дефекты после деплоя @@ -434,8 +506,60 @@ sqlite3 navidrome-merged.db "SELECT count(*) FROM album; SELECT count(*) FROM me | 3 | `TPE2`/`albumartist` — ремонт по 7861 файлу | 🟡 разброс 7→2 папки; ⏳ 2 папки ждут решения | | 4 | Symfonium на Android | ⏳ не установлен | | 5 | Lidarr | ⏳ не начат (отложен) | -| 6 | ⏭ Перегенерация `DIFF-new-vs-38-v2.csv` | 🔴 план изменился после генерации | -| 7 | Валидатор **смысла** `new_album` | 🔴 в пайплайне отсутствует | +| 6 | ⏭ Перегенерация `DIFF-new-vs-38-v2.csv` | ✅ **заменено** — актуальный диф `DIFF-new-vs-38-v6.csv` (1674 строки); v2/v3/v4/v5 сломаны/черновики | +| 7 | Валидатор **смысла** `new_album` | 🔴 в пайплайне отсутствует — **корень найден, см. ниже** | +| 8 | ✅ **`.ndignore` — 2 бэкап-папки не были в списке** | ✅ **ИСПРАВЛЕНО** — `backup_before_306`, `backup_album_root_…-229` дописаны; живая музыка **7861** | + +## 🔴 КОРЕНЬ ДЕФЕКТА «мусор в `album`» НАЙДЕН (сессия 29-кватер): хардкод `NON_ARTIST` в генераторе плана + +**Генератор плана найден через **Zulip DB** (не по локальному поиску):** +`~/Developer/personal-os/tools/music-tags/build_fix_plan2.py` (29690 байт). + +**Цепочка генерации:** +``` +tag_inventory2.py → tag-inventory3.csv (7861 строк — съём тегов с диска) +build_fix_plan2.py → APPLY-PLAN-FINAL.csv (7861 строк — план правок) + → APPLY-PLAN.csv (боевой канон) +``` +Скрипт вызывался: `python3 build_fix_plan2.py INVENTORY.csv OUT.csv [HOST_ROOT]`. + +**Строки 692-697 — место дефекта:** +```python +if not new_al and d_album: + new_al = d_album +if new_al and norm(new_al) in NON_ARTIST and d_album: + new_al = d_album # 🔴 ЗДЕСЬ +``` + +**Два независимых провала:** + +1. **`norm()` = только `strip().lower()`** (строки 258-259) — не убирает пунктуацию. +2. **`norm(new_al) in NON_ARTIST`** — сравнение **ВСЕЙ СТРОКИ** с элементами множества + (точное совпадение), а не поиск слова. +3. **`NON_ARTIST`** (строки 28-33) содержит `избранное`, `сборник`, `сборка`, `разное`, `детские`, + `хиты`, `лучшее` — **но НЕ содержит `песни`**. + +| `cur_album` | `norm()` | `in NON_ARTIST` | Причина провала | +|---|---|---|---| +| `Песни` (18 файлов: Никулин, Руки вверх, Little Big) | `песни` | ✗ | слова `песни` нет в списке | +| `Храбрый Зайчик. Песни для детей` (1: Миансарова) | `храбрый зайчик. песни для детей` | ✗ | фраза, а в списке одиночные слова | +| `песни` строчными (3: Наутилус) | `песни` | ✗ | + регистр: даже будь `Песни` в списке, не совпало бы | +| `Избранное` | `избранное` | ✅ | есть в списке — потому и отработало | + +**Всего пропущено 44 файла в 5 значениях:** `Песни` 18 · `Избранное` 16 · +`Супер ДискотЭка Радио Рекорд` 6 · `песни` 3 · `Храбрый Зайчик. Песни для детей` 1. + +> 🔑 **ДЕФЕКТ НЕ В КОРОТКОМ СПИСКЕ — ДЕФЕКТ В ТОЧНОМ СРАВНЕНИИ.** +> Даже если бы `песни` добавили в `NON_ARTIST`, `Храбрый Зайчик. Песни для детей` всё равно +> прошёл бы: это не слово из списка, а целая фраза. **Проверка обязана быть по ВХОЖДЕНИЮ +> слова, а не по равенству строки** — тот же принцип, что и в правиле корня +> ([[personal/plans/music-artist-albumartist-repair]] § СЕССИЯ 28: `nleaf in nartist`). +> **Это 4-е повторение одного метод-урока:** лишняя строгость ключа сравнения убивает детектор молча. + +**Диагностика:** `/tmp/find_junk_miss.py` → 5 значений / 44 файла, все помечены `НЕТ В JUNK`. + +**Статус:** фикс **не внесён** — ждёт решения Alex (править только album-детектор +или сразу все подобные по тому же принципу вхождения). ## ⚠️ Расхождение счётчика: 8043 в Navidrome vs 7861 mp3 на диске diff --git a/personal/plans/music-artist-albumartist-repair.md b/personal/plans/music-artist-albumartist-repair.md index 576ae0ae..4c675f07 100755 --- a/personal/plans/music-artist-albumartist-repair.md +++ b/personal/plans/music-artist-albumartist-repair.md @@ -4,7 +4,7 @@ created: '2026-09-27' updated: '2026-09-27' type: plan namespace: personal -status: session29ter-APPLY-PLAN-=-RC-RC-verify-7861-rashozhdeniy-genre-7861-track-1699-pokrytie-polnoe-AWAITING-ALEX +status: session29quater-ndignore-FIXED-live-music-7861-generator-FOUND-build_fix_plan2-NON_ARTIST-EXACT-MATCH-defect-AWAITING-ALEX tags: - music - metadata @@ -288,6 +288,149 @@ leaf='!Классика' artist='Фредерик Шопен' aa='Various --- +## 🔴 СЕССИЯ 29-КВАТЕР (2026-09-27) — ГЕНЕРАТОР ПЛАНА НАЙДЕН ЧЕРЕЗ ZULIP DB + `.ndignore` ИСПРАВЛЕН + +> **ТРИГГЕР (последовательность):** «**ладно ок. то есть план с базой сходится так?**» → +> «**и почему у нас альбомы (zvezda), (несчастный случай), (Авария) до сих пор в базе — мы же +> правили блядь скобки в альбомах?!**» → «**бэкапы же исключены из базы? разве нет?**» → +> «**ты блядь исключал из базы. ты туда блядь специальные ignore файлы блядь положил. +> почему они блядь в базе**» → «**Пиздец блядь. Конечно дописывать блядь**» → +> «**Теперь на основе скрипта который генерил apply список на 74xx записей, найди комяк который +> не поймал зайчик песни для детей и Юрий Никулин "песни"**» → «**нет. иди в зулип базу и ищи +> каким скриптом генерили APPLY-PLAN.csv**» + +### 🔴 ПРОЦЕССНЫЙ ПРОВАЛ: 3 РАЗА ОТВЕТИЛ «НЕ ЗНАЮ» ВМЕСТО ПОХОДА НА NAS + +Alex спросил «бэкапы же исключены из базы? разве нет?» — я ответил теорией (что бэкап лежит внутри +дерева) и **не пошёл смотреть факт**. Затем «**ты блядь исключал из базы, ты туда ignore файлы +клал — почему они в базе**» → снова «я не нашёл ignore-файл» (искал только в `~/Downloads` и `/tmp`, +**на NAS не смотрел**). Затем «**В смысле БЛЯДЬ не знаю?! Файлы блядь там или нет?!**» → +только тогда `ssh ... ls .ndignore`. + +**🔑 ПРАВИЛО: «почему X до сих пор в базе/на NAS» = вопрос о ФАКТЕ.** +Первый tool-call — `ssh` на NAS, а не рассуждение. `grep` по локальным папкам НЕ заменяет +проверку боевого хоста. Промедление прочитано как «серишь». +(Повторение урока скилла `zulip-db-forensics`: «live diagnostics first, history for context second».) + +### ✅ `.ndignore` — ФАКТ И ФИКС (детали → [[family/how-to/navidrome-truenas-android]]) + +Файл **существовал**, но содержал только **4 строки** из **6** существующих бэкап-папок. +Причина: `.ndignore` создан **03:49**, `backup_before_306` — **05:17**, +`backup_album_root_20260927-album-root-229` — **06:19**. Файл не обновляли после новых бэкапов. + +Фикс: бэкап файла → собрать полный список ЛОКАЛЬНО → `scp` → `cp` на место → `docker restart navidrome`. + +| Метрика | До | После | +|---|---:|---:| +| `media_file` всего | 8167 | **8396** | +| `missing=1` (игнор) | — | **535** | +| **живая музыка** | 7861 | **7861** ✅ | + +### 🔴🔴 ГЛАВНЫЙ МЕТОД-ПИТФОЛЛ СЕССИИ: ЗАПРОС К БД БЕЗ ФИЛЬТРА БЭКАПА + +**Я выдал бэкап за живое дерево и отчитался «скобки НЕ исправлены».** Запрос +`SELECT DISTINCT album FROM media_file WHERE album LIKE '(%'` **без фильтра** вытащил 306 файлов +из `backup_before_306/20260927-051727/`, где старые скобочные значения и лежат. + +**В живом дереве `(zvezda)`, `(Авария)`, `(Несчастный Случай)` ОТСУТСТВУЮТ — правка отработала.** +Факт после фикса: скобок-обёрток **0**. + +``` +ОБЯЗАТЕЛЬНЫЙ ФИЛЬТР: WHERE missing=0 AND path NOT LIKE 'backup%' +``` + +**Два класса скобок (не смешивать):** обёртка вокруг всего названия (`(Авария)`, `(zvezda)`) — +правится; скобка в КОНЦЕ как уточнение (`Бумер (2003)`, `Муза (EP)`, `Outcasts (DEMOS)`) — +часть названия, НЕ правится (осталось 17 таких значений). + +### ✅ РАЗБОР ВОПРОСА «ПОЧЕМУ НЕТ УЧЁТА ТРЕКОВ»: покрытие план↔БД ПОЛНОЕ + +«**то есть план с базой сходится так?**» — **по путям ДА, по тегам НЕТ.** +`/tmp/gap_db_plan.py` (двусторонний): БД без backup **7861**, план **7861**, уникальных путей **7861**, +`ЕСТЬ В БД, НЕТ В ПЛАНЕ` = **0**, `ЕСТЬ В ПЛАНЕ, НЕТ В БД` = **0**. +Но `RC-VERIFY.csv`: **0 файлов** совпали по всем 6 полям — по тегам расходятся все 7861 (детали § СЕССИЯ 29-ТЕР). + +### 🔑 ГЕНЕРАТОР ПЛАНА НАЙДЕН — ТОЛЬКО ЧЕРЕЗ ZULIP DB + +Локальный `grep -l "APPLY-PLAN" *.py` дал **63 файла-потребителя**, но **ни одного генератора** +(все — правки/аудиты поверх готового плана). Zulip DB показал цепочку: + +``` +tag_inventory2.py → tag-inventory3.csv (7861 строк — съём тегов с диска) +build_fix_plan2.py → APPLY-PLAN-FINAL.csv (7861 строк — план правок) + → APPLY-PLAN.csv (боевой канон) +``` + +**Путь: `~/Developer/personal-os/tools/music-tags/build_fix_plan2.py`** (29690 байт). +Вызов: `python3 build_fix_plan2.py INVENTORY.csv OUT.csv [HOST_ROOT]`. + +> 🔑 **ПРАВИЛО: артефакт созданный давней сессией — искать в Zulip DB, а не только локально.** +> Локальный файл-потребитель — не генератор. Скрипт-производитель назван в сообщении сессии создания. +> Ключ поиска: `m.content ILIKE '%<имя_артефакта>%' AND m.content ILIKE '%write_file%'`. + +### 🔴 КОРЕНЬ ДЕФЕКТА «мусор в `album`»: `NON_ARTIST` + ТОЧНОЕ СРАВНЕНИЕ + +Вопрос Alex: **почему не пойманы `Храбрый Зайчик. Песни для детей` (Миансарова) и `Песни` (Никулин)?** + +**Строки 692-697 `build_fix_plan2.py`:** +```python +if not new_al and d_album: + new_al = d_album +if new_al and norm(new_al) in NON_ARTIST and d_album: + new_al = d_album # 🔴 +``` + +**Три причины (диагностика `/tmp/find_junk_miss.py`):** + +| # | Причина | Факт | +|---|---|---| +| 1 | `NON_ARTIST` **не содержит** `песни` | в списке есть `сборник песен`, но не `песни` | +| 2 | **ТОЧНОЕ сравнение всей строки** (`norm(new_al) in NON_ARTIST`), не поиск слова | `Храбрый Зайчик. Песни для детей` — фраза, в списке одиночные слова → мимо | +| 3 | `norm()` = только `strip().lower()` — регистр значим | `песни` строчными ≠ `Песни` | + +`Избранное` прошло **только потому, что буквально совпало** с элементом списка. + +**Пропущено 44 файла в 5 значениях:** +`Песни` **18** (Никулин 3, Руки вверх 2, Little Big …) · `Избранное` **16** (Пилот, Агата Кристи) · +`Супер ДискотЭка Радио Рекорд` **6** · `песни` **3** (Наутилус `2000 - песни`) · +`Храбрый Зайчик. Песни для детей` **1** (Миансарова). + +**🔑 ДЕФЕКТ НЕ В КОРОТКОМ СПИСКЕ, А В ТОЧНОМ СРАВНЕНИИ.** Даже с `песни` в `NON_ARTIST` +Миансарова всё равно прошла бы. Нужна **проверка по вхождению слова**, а не по равенству строки. + +> 🔑 **Это 4-е повторение ОДНОГО метод-урока:** лишняя строгость ключа сравнения молча убивает детектор. +> 1. Сессия 28: `leaf not in (artist, aa)` на равенство → потеряно 908 файлов. +> 2. Сессия 29: `was` в ключе схлопывания → схлопывание не работало никогда. +> 3. Сессия 29-бис: правило корня перебивало верный `album` из плана (`Sbornik_Novij_Rok`). +> 4. Сессия 29-кватер: `norm(new_al) in NON_ARTIST` на точное равенство → 44 файла мусора. +> **ВСЕГДА: `norm()` + вхождение. НИКОГДА: равенство.** + +### 🛠 СКРИПТЫ ЭТОГО БЛОКА + +``` +/tmp/find_junk_miss.py ⭐ находит значения, НЕ попавшие в хардкод NON_ARTIST (5 значений / 44 файла) +/tmp/gap_db_plan.py ⭐ двусторонний gap план↔БД (0 / 0) +/tmp/find_zaychik.py 🔍 поиск 'Храбрый зайчик' в плане +/tmp/ndignore-new новый .ndignore (6 строк, 103 байта) — собран локально + +~/Developer/personal-os/tools/music-tags/build_fix_plan2.py ⭐ ГЕНЕРАТОР ПЛАНА (29690 байт) +~/Developer/personal-os/tools/music-tags/tag_inventory2.py источник инвентаря +``` + +### ⏳ СТАТУС БЛОКА + +✅ **Сделано:** `.ndignore` дополнен (боевая правка на NAS), скан, живая музыка **7861**; +генератор найден, корень дефекта доказан по коду и данным. + +🔴 **Открыто (3, все ждут решения Alex):** +1. **Фикс `NON_ARTIST`** — править только album-детектор, или сразу все подобные + (для `artist`/`albumartist`) по принципу вхождения? +2. **Новый apply csv** + новый дифф по `new_*` — задача поставлена, **не выполнена** + (ждёт ответа по п.1: генератор должен быть исправлен ДО генерации нового csv). +3. **Дефект №5** из § СЕССИЯ 29-БИС (`nartist in nleaf` ловит `50 Cent` в `Eminem,50cent`). + +--- + ## 🔴 СЕССИЯ 29-ТЕР (2026-09-27) — ПЕРЕОСМЫСЛЕНИЕ: `APPLY-PLAN.csv` = RC, СВЕРКА `new_*` vs БД > **ТРИГГЕР:** «**стоп блядь. полный назад. возвращаемся блядь к исходной.**» → diff --git a/personal/tech/music-tags-repair-truenas.md b/personal/tech/music-tags-repair-truenas.md index 5f6708b6..77951a9f 100755 --- a/personal/tech/music-tags-repair-truenas.md +++ b/personal/tech/music-tags-repair-truenas.md @@ -75,6 +75,23 @@ related: > перечитала теги. Разбор — § СЕССИЯ 29-ТЕР. > ✅ **Покрытие план↔БД ПОЛНОЕ:** 7861 = 7861, `GAP-db-not-in-plan.csv` и > `GAP-plan-not-in-db.csv` **оба пусты** (`/tmp/gap_db_plan.py`). +> +> ✅ **СЕССИЯ 29-КВАТЕР — ДВА ГЛАВНЫХ РЕЗУЛЬТАТА:** +> 1. 🔴 **`.ndignore` ИСПРАВЛЕН** — не хватало 2 бэкап-папок (`backup_before_306`, +> `backup_album_root_…-229`, созданы ПОЗЖЕ файла). Живая музыка = **7861** = плану. +> Детали → [[family/how-to/navidrome-truenas-android]]. +> 2. 🔑 **ГЕНЕРАТОР ПЛАНА НАЙДЕН** (только через **Zulip DB**): +> `~/Developer/personal-os/tools/music-tags/build_fix_plan2.py`. +> Корень дефекта «мусор в `album`»: `norm(new_al) in NON_ARTIST` — **точное сравнение всей +> строки** + `NON_ARTIST` без слова `песни` → пропущено **44 файла** (Никулин `Песни` 18, +> Миансарова `Храбрый Зайчик. Песни для детей` 1, `Избранное` 16, …). +> Разбор — [[personal/plans/music-artist-albumartist-repair]] § СЕССИЯ 29-КВАТЕР. +> +> 🔴🔴 **ПИТФОЛЛ (метод, сессия 29-кватер): ЗАПРОС К `media_file` БЕЗ ФИЛЬТРА БЭКАПА.** +> Запрос `album LIKE '(%'` без фильтра вытащил 306 файлов бэкапа → **ложный отчёт «скобки не +> исправлены»**. В живом дереве скобок-обёрток **0**. **ОБЯЗАТЕЛЬНО:** +> `WHERE missing=0 AND path NOT LIKE 'backup%'`. + > 🔑 **ПИТФОЛЛ: «Чёрный кот» пишется через Ё** — `grep "черный кот"` даёт 0 и создаёт > ложное «файла нет». Правило: искать и через `ё`, и через `е`, и без гласной. > 🔴 **Питфолл БД:** колонка — **`track_number`**, не `track`; возвращается **int**; diff --git a/personal/tech/skill-truenas-administration.md b/personal/tech/skill-truenas-administration.md index 05bf7582..0b9fc3cb 100755 --- a/personal/tech/skill-truenas-administration.md +++ b/personal/tech/skill-truenas-administration.md @@ -3,16 +3,19 @@ title: skill — truenas-administration namespace: personal type: tech created: '2026-09-26' -updated: '2026-09-26' +updated: '2026-09-27' tags: - truenas - skill - docker - acl - nfc + - navidrome + - ndignore related: - '[[personal/plans/tagwriter-container]]' - '[[personal/tech/music-tags-repair-truenas]]' + - '[[family/how-to/navidrome-truenas-android]]' --- # Зеркало навыка `truenas-administration` @@ -20,7 +23,50 @@ related: Навык: `~/.hermes/hermes-whale/skills/devops/truenas-administration/SKILL.md` (umbrella: ACL/PUID, ZFS panic recovery, tree comparison, named-volumes→bind-mount). -Ниже — дельта, добавленная **2026-09-26** (сессия music-tags волна 38 + сборка `tagwriter`). +Ниже — дельта. **2026-09-26:** волна 38 + сборка `tagwriter`. **2026-09-27:** добавился RULE 0 +(Area 3) про `.ndignore`. + +## 0. 🔴 RULE 0 (2026-09-27): любой новый `backup*` → в `.ndignore` В ТОМ ЖЕ ПРОГОНЕ + +Область: `/mnt/RED_2TB/storage/Music/` — корень библиотеки Navidrome (монтируется как `/music`). +Navidrome игнорирует **только то, что перечислено в `.ndignore`**. Бэкап, которого там нет, +**сканируется как обычная музыка** и пачкает КАЖДЫЙ запрос к БД: расхождения счётчиков, +фантомные находки («альбом со скобками до сих пор в базе», хотя правка отработала), +дифф, который выглядит сломанным, хотя он верный. + +**Проверенный провал 2026-09-27:** `.ndignore` создан **03:49** и содержал 4 папки +(`backup`, `backup_conv40`, `backup_fix`, `backup_raw`). Бэкапы, созданные **позже**, в файл +не попали: `backup_before_306` (**05:17**), `backup_album_root_20260927-album-root-229` (**06:19**). +Итог: БД **8167** против плана **7861**, разница **306** = неотфильтрованный бэкап. +Альбомы `(zvezda)`, `(Авария)`, `(Несчастный Случай)` «остались в базе» — они существовали +**только внутри бэкапа**. + +**Фикс (боевой конфиг → порядок: бэкап ДО → правка → проверка → коммит ПОСЛЕ):** +```bash +# 1. бэкап файла +ssh truenas_admin@mallexxx.duckdns.org \ + "cp /mnt/RED_2TB/storage/Music/.ndignore /mnt/RED_2TB/storage/Music/.ndignore.bak-\$(date +%Y%m%d-%H%M%S)" +# 2. перечислить ВСЕ бэкап-папки и сверить со списком +ssh truenas_admin@mallexxx.duckdns.org "cd /mnt/RED_2TB/storage/Music && ls -d */ | grep -i backup" +# 3. полный файл собрать ЛОКАЛЬНО (write_file) → scp → cp НА МЕСТО (НЕ sed на NAS!) +scp /tmp/ndignore-new truenas_admin@mallexxx.duckdns.org:/tmp/ndignore-new +ssh truenas_admin@mallexxx.duckdns.org "cp /tmp/ndignore-new /mnt/RED_2TB/storage/Music/.ndignore" +``` +Navidrome подхватывает файл по рестарту; после скана проверить, что игнор сработал — +игнорируемые получают **`missing=1`, их НЕ удаляют**: +```bash +sqlite3 /mnt/RED_2TB/docker/navidrome/data/navidrome.db \ + "SELECT COUNT(*) FROM media_file; -- всего + SELECT COUNT(*) FROM media_file WHERE missing=1; -- игнор/отсутствуют + SELECT COUNT(*) FROM media_file WHERE missing=0 AND path NOT LIKE 'backup%';" -- живая музыка +``` +**Контрольное число:** живая музыка = числу строк плана (**7861** на 2026-09-27). +Факт после фикса: всего **8396**, `missing=1` **535**, живая **7861** ✅. + +> 🔴 **ОБЯЗАТЕЛЬНЫЙ ФИЛЬТР в ЛЮБОМ запросе к `media_file`:** +> `missing=0 AND path NOT LIKE 'backup%'` +> Без него читаешь бэкап-копии и делаешь ЛОЖНЫЕ выводы. Эта ошибка дала неверный отчёт +> «скобки не исправлены» — бэкап был выдан за живое дерево. ## 1. Создание НОВОГО каталога под `/mnt//docker/` — из контейнера