Files
obsidian-vault/personal/tech/skill-truenas-administration.md

8.1 KiB
Executable File
Raw Permalink Blame History

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
skill
docker
acl
nfc
navidrome
ndignore
personal/plans/tagwriter-container
personal/tech/music-tags-repair-truenas
family/how-to/navidrome-truenas-android

Зеркало навыка 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 ….