8.1 KiB
Executable File
title, namespace, type, created, updated, tags, related
| title | namespace | type | created | updated | tags | related | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| skill — truenas-administration | personal | tech | 2026-09-26 | 2026-09-27 |
|
|
Зеркало навыка truenas-administration
Навык: ~/.hermes/hermes-whale/skills/devops/truenas-administration/SKILL.md
(umbrella: ACL/PUID, ZFS panic recovery, tree comparison, named-volumes→bind-mount).
Ниже — дельта. 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), (Авария), (Несчастный Случай) «остались в базе» — они существовали
только внутри бэкапа.
Фикс (боевой конфиг → порядок: бэкап ДО → правка → проверка → коммит ПОСЛЕ):
# 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"
Затем docker restart navidrome (в контейнере curl /api/scanner/status → 404, эндпоинта
нет; подхват идёт по рестарту + watcher). После скана проверить, что игнор сработал —
игнорируемые получают missing=1, их НЕ удаляют:
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 ✅ (до фикса было 8167/306).
🔴 ОБЯЗАТЕЛЬНЫЙ ФИЛЬТР в ЛЮБОМ запросе к
media_file:missing=0 AND path NOT LIKE 'backup%'Без него читаешь бэкап-копии и делаешь ЛОЖНЫЕ выводы. Эта ошибка дала неверный отчёт «скобки не исправлены» — бэкап был выдан за живое дерево. 🔑 Правило на будущее (Alex: «Да»): любой новыйbackup*под библиотекой — в.ndignoreв том же прогоне, что его создал. Файл создан 03:49, бэкапы — 05:17 и 06:19; список просто не обновили. Скрипты бэкапа обязаны дописывать себя сами.
1. Создание НОВОГО каталога под /mnt/<POOL>/docker/ — из контейнера
/mnt/RED_2TB/docker/ = root:root. truenas_admin (uid 950) не пишет туда;
sudo требует пароль (sudo -n → password required). Но docker доступен (юзер в группе docker).
docker run --rm -u 0 -v /mnt/RED_2TB/docker:/docker alpine:latest sh -c \
'mkdir -p /docker/tagwriter/data && chown -R 950:950 /docker/tagwriter && chmod 755 /docker/tagwriter'
Затем scp проекта обычным путём (владелец 950).
Правило: любой новый каталог под docker/ создавать ИЗ КОНТЕЙНЕРА (-u 0), не через ssh под 950.
2. 🔴 Права файлов при scp → COPY в Dockerfile
scp кладёт файлы как -rw------- (600). COPY сохраняет режим → внутри образа
/app/<script>.py = root:root 600 → пользователь образа (uid 950) получает
python3: can't open file '/app/x.py': [Errno 13] Permission denied, хотя на хосте права 644.
Фикс: в Dockerfile RUN chmod 755 /app/<script>.py && chmod 755 /app + chmod 644 файлов проекта на хосте.
Проверка: docker run --rm --entrypoint sh -u 950:950 <img> -c 'id; ls -la /app'.
3. 🔴🔴 NFD на диске vs NFC в данных — путь молча НЕ находится
Батч-задача сверяла манифест (CSV) с реальными файлами → 47 «отсутствующих» файлов, которые
лежат на диске. Причина: имена на диске в NFD (разложенная форма: й = и + combining breve
U+0306, то же для ё), манифест — в NFC. os.path.exists() → False; ls ничего не показывает;
выглядит как «файл удалён».
Поражённые: Али-Баба и сорок разбойников, Принц и нищий (Диск 2), Пеппи Длинный Чулок,
Летучий корабль, Голубой щенок — любое имя с й/ё.
# ✅ индекс диска: NFC(путь) -> реальный путь
idx = {}
for root, dirs, files in os.walk(BASE):
for n in files:
real = os.path.join(root, n)
idx[unicodedata.normalize("NFC", real)] = real
real = idx.get(unicodedata.normalize("NFC", wanted)) # None ⇒ действительно нет
Правило: не звать os.path.exists() на не-ASCII путь из манифеста — строить NFC-индекс и искать по нему.
Тот же класс, что кейс combining breve для Принц и нищий (Диск 2); применимо к любой
батч-задаче с кириллическими путями, не только к музыке. Приёмка: MISSING 47 → 0.
Применение
Оба питфолла воспроизвелись при сборке контейнера personal/plans/tagwriter-container
(применение тегов MP3, 7041 файл). Оттуда же — команды docker compose --profile tools run ….