[2026-09-26] eagle: personal/plans/tagwriter-container.md personal/plans/tagwriter-container.md.bak2
This commit is contained in:
1 parent
1a8bc429dd
commit
59d2ad1737
2 files changed
+168
-14
No files matched your search
@@ -4,7 +4,7 @@ namespace: personal
|
||||
related:
|
||||
- '[[personal/plans/music-tags-wave38-fixes]]'
|
||||
- '[[personal/tech/music-tags-repair-truenas]]'
|
||||
status: blocked
|
||||
status: ready-to-apply
|
||||
tags:
|
||||
- music
|
||||
- truenas
|
||||
@@ -17,7 +17,7 @@ updated: '2026-09-26'
|
||||
---
|
||||
# tagwriter — контейнер применения тегов MP3 (TrueNAS)
|
||||
|
||||
_Создан: 2026-09-26. **Статус: 🔴 БЛОКЕР — нет прав на `/mnt/RED_2TB/docker/`.**_
|
||||
_Создан: 2026-09-26. **Статус: ✅ контейнер собран, dry-run прошёл (0 MISSING) → ждёт бэкап + apply.**_
|
||||
_Основание: [[personal/plans/music-tags-wave38-fixes]]._
|
||||
|
||||
## Задача
|
||||
@@ -26,17 +26,19 @@ _Основание: [[personal/plans/music-tags-wave38-fixes]]._
|
||||
в **UTF-8/UTF-16**. Alex: «Собирай tagwriter. С docker compose, in docker folder.»
|
||||
Чужие контейнеры (`transmission`/`immich`/`jellyfin`) — **НЕ трогать** (прямой запрет Alex).
|
||||
|
||||
## 🔴 БЛОКЕР
|
||||
## ✅ БЛОКЕР РЕШЁН: каталог создан ИЗ КОНТЕЙНЕРА
|
||||
|
||||
`/mnt/RED_2TB/docker/` принадлежит **root** (`drwxr-xr-x root root`).
|
||||
`truenas_admin` (uid 950) **не может писать** туда, `sudo` требует пароль (`sudo -n` → password required).
|
||||
При этом `docker` доступен (юзер в группе `docker`, uid 999) → образ собрать можно,
|
||||
но **проектную папку в `docker/` создать нельзя**.
|
||||
`/mnt/RED_2TB/docker/` принадлежит **root**, `truenas_admin` (uid 950) туда **не пишет**, `sudo` требует пароль.
|
||||
`docker` доступен (юзер в группе `docker`).
|
||||
|
||||
**Решения (ждёт Alex):**
|
||||
1. Alex создаёт `/mnt/RED_2TB/docker/tagwriter/` + `data/` → заливаем и собираем как задумано.
|
||||
2. Собираем в `/tmp/tagwriter` (там писать можно), папку в `docker/` переносит Alex.
|
||||
3. Другой путь, доступный на запись uid 950.
|
||||
**Решение (выполнено):** каталог создан из одноразового root-контейнера с монтированием родителя:
|
||||
|
||||
```bash
|
||||
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'
|
||||
```
|
||||
|
||||
**Правило:** любые каталоги в `/mnt/RED_2TB/docker/` создавать ИЗ КОНТЕЙНЕРА (`-u 0`), не через ssh под uid 950.
|
||||
|
||||
## Разведка окружения (факты, 2026-09-26)
|
||||
|
||||
@@ -86,12 +88,55 @@ _Основание: [[personal/plans/music-tags-wave38-fixes]]._
|
||||
|
||||
## Порядок работ
|
||||
|
||||
1. ⏳ **Сборка образа** — блокер по папке (см. выше).
|
||||
2. ⏳ **Бэкап** — `--backup` (7905 файлов → `/music/backup/`; 48 ГБ при 960 ГБ свободных).
|
||||
3. ⏳ **Dry-run** — `--dry-run`, 0 записей. **Приёмка Alex.**
|
||||
1. ✅ **Сборка образа** — `docker compose --profile tools build` (выполнено).
|
||||
2. ⏳ **Бэкап** — `--backup` (7041 файлов → `/music/backup/`; 48 ГБ при 960 ГБ свободных). **Следующий шаг.**
|
||||
3. ✅ **Dry-run** — `--dry-run`, 0 записей, **MISSING 0** (выполнено, ждёт приёмки Alex).
|
||||
4. ⏳ **Apply** — `--apply` (только по «go»).
|
||||
5. ⏳ **Read-back** — `--readback` + глазами в Jellyfin.
|
||||
|
||||
## ✅ СОСТОЯНИЕ НА 2026-09-26 (факты)
|
||||
|
||||
| Шаг | Статус |
|
||||
|---|---|
|
||||
| Каталог `/mnt/RED_2TB/docker/tagwriter/` + `data/` | ✅ создан (владелец 950), **из контейнера** (`docker run -u 0 -v /mnt/RED_2TB/docker:/docker alpine mkdir`) — `truenas_admin` не может писать в `docker/` напрямую, `sudo` требует пароль |
|
||||
| Проект залит (`Dockerfile`, `docker-compose.yml`, `apply_tags.py`, `data/tag-fix-plan.csv`) | ✅ |
|
||||
| Образ `tagwriter:latest` (`docker compose --profile tools build`) | ✅ собран (python:3.12-alpine + mutagen 1.47.0) |
|
||||
| **DRY-RUN** | ✅ **не найдено: 0**, к обработке **7041** (файлов на диске 8483) |
|
||||
| Бэкап | ⏳ не делался (48 ГБ, свободно 960 ГБ) |
|
||||
| Применение (`--apply`) | ⏳ ждёт «go» |
|
||||
|
||||
### 🔴 ПИТФОЛЛ 1: права файлов при COPY в образ
|
||||
|
||||
Файлы заливались `scp` как `-rw-------` (600) → `COPY` сохранил их root:root 600
|
||||
→ uid 950 не читал `/app/apply_tags.py` (`Permission denied`).
|
||||
**Фикс:** `RUN chmod 755 /app/apply_tags.py && chmod 755 /app` в Dockerfile + `chmod 644` на файлах проекта.
|
||||
Проверка: `docker run --rm --entrypoint sh -u 950:950 tagwriter:latest -c 'ls -la /app'`.
|
||||
|
||||
### 🔴🔴 ПИТФОЛЛ 2 (КЛЮЧЕВОЙ): NFD на диске vs NFC в CSV
|
||||
|
||||
Первый dry-run дал **47 MISSING**. Причина: имена на диске в **NFD**
|
||||
(разложенная форма: `и` + combining breve `U+0306`), в CSV — **NFC**.
|
||||
|
||||
Пораженные папки: `Али-Баба и сорок разбойников` (+`_2`), `Принц и нищий`
|
||||
(+`(Диск 2)`), `Пеппи Длинный Чулок`, `Летучий корабль`, `Голубой щенок`, `Юрий Никулин`.
|
||||
|
||||
**Фикс:** индекс диска `{NFC(путь): реальный_путь}` через `os.walk`;
|
||||
поиск идёт по `NFC(CSV-путь)`. **MISSING 47 → 0**.
|
||||
**Правило: NFC-нормализовать ОБЕ стороны, не только CSV.**
|
||||
|
||||
## Команды
|
||||
|
||||
```bash
|
||||
cd /mnt/RED_2TB/docker/tagwriter
|
||||
docker compose --profile tools build # сборка
|
||||
docker compose --profile tools run --rm tagwriter --dry-run # план, 0 записей
|
||||
docker compose --profile tools run --rm tagwriter --backup # бэкап 48 ГБ в /music/backup/
|
||||
docker compose --profile tools run --rm tagwriter --apply # запись тегов
|
||||
docker compose --profile tools run --rm tagwriter --readback # проверка
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Питфоллы
|
||||
|
||||
- 🔴 `/mnt/RED_2TB/docker/` — **root-owned**, uid 950 не пишет (это и есть блокер).
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
---
|
||||
created: '2026-09-26'
|
||||
namespace: personal
|
||||
related:
|
||||
- '[[personal/plans/music-tags-wave38-fixes]]'
|
||||
- '[[personal/tech/music-tags-repair-truenas]]'
|
||||
status: blocked
|
||||
tags:
|
||||
- music
|
||||
- truenas
|
||||
- docker
|
||||
- id3
|
||||
- plan
|
||||
title: tagwriter — контейнер применения тегов MP3
|
||||
type: plan
|
||||
updated: '2026-09-26'
|
||||
---
|
||||
# tagwriter — контейнер применения тегов MP3 (TrueNAS)
|
||||
|
||||
_Создан: 2026-09-26. **Статус: 🔴 БЛОКЕР — нет прав на `/mnt/RED_2TB/docker/`.**_
|
||||
_Основание: [[personal/plans/music-tags-wave38-fixes]]._
|
||||
|
||||
## Задача
|
||||
|
||||
Применить `tag-fix-plan.csv` (7905 строк) к MP3 на `/storage/Music`, **без пересжатия аудио**,
|
||||
в **UTF-8/UTF-16**. Alex: «Собирай tagwriter. С docker compose, in docker folder.»
|
||||
Чужие контейнеры (`transmission`/`immich`/`jellyfin`) — **НЕ трогать** (прямой запрет Alex).
|
||||
|
||||
## 🔴 БЛОКЕР
|
||||
|
||||
`/mnt/RED_2TB/docker/` принадлежит **root** (`drwxr-xr-x root root`).
|
||||
`truenas_admin` (uid 950) **не может писать** туда, `sudo` требует пароль (`sudo -n` → password required).
|
||||
При этом `docker` доступен (юзер в группе `docker`, uid 999) → образ собрать можно,
|
||||
но **проектную папку в `docker/` создать нельзя**.
|
||||
|
||||
**Решения (ждёт Alex):**
|
||||
1. Alex создаёт `/mnt/RED_2TB/docker/tagwriter/` + `data/` → заливаем и собираем как задумано.
|
||||
2. Собираем в `/tmp/tagwriter` (там писать можно), папку в `docker/` переносит Alex.
|
||||
3. Другой путь, доступный на запись uid 950.
|
||||
|
||||
## Разведка окружения (факты, 2026-09-26)
|
||||
|
||||
| Что | Факт |
|
||||
|---|---|
|
||||
| `/storage/Music` | 48 ГБ, владелец `truenas_admin` uid/gid **950** |
|
||||
| Свободно на `/mnt/RED_2TB` | **960 ГБ** (бэкап 48 ГБ влезет) |
|
||||
| python3 на хосте NAS | 3.11.9, **нет pip, нет ensurepip, нет venv** |
|
||||
| mutagen | **нигде** (ни на хосте, ни в контейнерах) |
|
||||
| ffmpeg | только `immich-server` (**музыки не видит**) |
|
||||
| `/storage/Music` видят | `jellyfin`, `radarr`, `sonarr`, `transmission` |
|
||||
| python3 в чужих контейнерах | только `transmission` (3.14.7) — **чужой, не трогаем** |
|
||||
| Docker Hub | доступен (`python:3.12-alpine` скачался) |
|
||||
| docker compose | v2.29.1 |
|
||||
| `media_net` | есть |
|
||||
| ACL Music | `owner@ grup@` ALLOW, NOINHERIT |
|
||||
|
||||
**Вывод:** пишем теги **своим контейнером**. `pip` есть внутри образа → mutagen ставится при сборке.
|
||||
|
||||
## Решение: контейнер `tagwriter`
|
||||
|
||||
- **Образ:** `python:3.12-alpine` + `pip install mutagen==1.47.0`
|
||||
- **Пользователь:** `user: "950:950"` (владелец Music; иначе запись невозможна)
|
||||
- **Монтирование:** `/mnt/RED_2TB/storage/Music` → `/music` (rw), `/mnt/RED_2TB/docker/tagwriter/data` → `/app/data`
|
||||
- **Теги:** ID3v2.3, `encoding=1` (**UTF-16**), `v2_version=3` — только тег-блок, аудио не пересжимается
|
||||
- **Профиль** `tools` — контейнер one-shot, не поднимается автоматически
|
||||
|
||||
## Файлы проекта (созданы локально)
|
||||
|
||||
| Файл | Назначение |
|
||||
|---|---|
|
||||
| `metrics/apply_tags.py` (в проекте — `apply_tags.py`) | `--backup` / `--dry-run` / `--apply` / `--readback` / `--limit N` |
|
||||
| `docker-compose.yml` | сервис `tagwriter`, профиль `tools`, монтирование Music |
|
||||
| `Dockerfile` | `python:3.12-alpine` + mutagen, `USER 950:950` |
|
||||
|
||||
Локальные исходники: `~/Downloads/music-tags/tagwriter/`.
|
||||
|
||||
## Скрипт `apply_tags.py` — логика
|
||||
|
||||
- Читает `/app/data/tag-fix-plan.csv` (кладут в `data/`).
|
||||
- **Пропускает `skip` и `no_change`** — правит только `fix`.
|
||||
- **NFC-нормализация** путей (`unicodedata.normalize("NFC")`) — имена на диске могут быть в NFD.
|
||||
- **Маппинг путей**: `/storage/Music/` → `/music/`.
|
||||
- `--backup` идемпотентен (существующий файл бэкапа не перезаписывает).
|
||||
- `--apply` **отказывается работать без бэкапа** (`ABORT: нет бэкапа`).
|
||||
- Фреймы: `TIT2`/`TPE1`/`TALB`/`TRCK`, перед записью `delall` по каждому FrameID.
|
||||
|
||||
## Порядок работ
|
||||
|
||||
1. ⏳ **Сборка образа** — блокер по папке (см. выше).
|
||||
2. ⏳ **Бэкап** — `--backup` (7905 файлов → `/music/backup/`; 48 ГБ при 960 ГБ свободных).
|
||||
3. ⏳ **Dry-run** — `--dry-run`, 0 записей. **Приёмка Alex.**
|
||||
4. ⏳ **Apply** — `--apply` (только по «go»).
|
||||
5. ⏳ **Read-back** — `--readback` + глазами в Jellyfin.
|
||||
|
||||
## Питфоллы
|
||||
|
||||
- 🔴 `/mnt/RED_2TB/docker/` — **root-owned**, uid 950 не пишет (это и есть блокер).
|
||||
- 🔴 `sudo` без пароля **недоступен**.
|
||||
- 🔴 Имена файлов на диске **не в UTF-8** (mojibake в `ls`) → только python-скрипт, NFC.
|
||||
- 🔴 Пути в CSV контейнерные (`/storage/Music/…`) → мапить в `/music/…`.
|
||||
- ⚠️ `ffmpeg` для записи тегов **не нужен** — mutagen работает с тег-блоком напрямую.
|
||||
- ⚠️ uid контейнера обязан быть `950`, иначе запись в Music упадёт.
|
||||
- ⚠️ Команды на NAS требуют апрува — таймаут апрува = команда не выполнена (не повторять вслепую).
|
||||
|
||||
## Связано
|
||||
|
||||
- [[personal/plans/music-tags-wave38-fixes]] — 4 правки CSV, выполнены.
|
||||
- [[personal/tech/music-tags-repair-truenas]] — техдок ремонта 7905 тегов.
|
||||
- [[personal/tech/music-tags-rename-audit]] — финальный аудит CSV.
|
||||
Reference in new issue
Block a user