[2026-09-26] eagle: personal/plans/balda-mm-phase4-agents.md personal/plans/music-tags-review/music-tags-review.md personal/projects/balda/balda-mm-delegation-plan.md personal/tech/eagle-dashboard-skill.md personal/tech/macos-docker-virtiofs-mount-limits.md personal/tech/music-tags-repair-truenas.md

This commit is contained in:
Alexey Martemyanov committed 2026-09-26 15:35:55 +06:00
1 parent f2501dcff8
commit 5c939ed811
6 files changed
+391 -8

No files matched your search

+125
View File
@@ -0,0 +1,125 @@
# eagle-dashboard (скилл) — зеркало знаний
> Создан 2026-09-26. Зеркало скилла `eagle-dashboard` для случаев, когда скилл не загружен
> или нужен текстовый поиск по хранилищу. Источник истины — сам скилл
> (`~/.hermes/hermes-whale/skills/devops/eagle-dashboard/SKILL.md`).
> Дополнять **вместе** со скиллом, не расходясь.
---
## Что это
Eagle Dashboard — сервис-реестр + health-checks + управление lifecycle на Mac.
Живёт в `~/Developer/eagle-dash/`, поднят как launchd-агент, UI на http://localhost:8880.
**Карта файлов (проверено 2026-09-26):**
| Файл | Строк | Роль |
|---|---|---|
| `main.py` | 889 | FastAPI роуты |
| `templates/index.html` | 1248 | Alpine.js UI + все вкладки |
| `health.py` | 181 | health-чеки |
| `supervisor.py` | 290 | lifecycle процессов |
**Вкладки:** `services` · `crons` · `pages` · `files` — роутинг через `?tab=`.
Планируется 5-я: `agents` (управление `~/agents`).
**Health-типы:** `http` · `port` · `process` · `launchctl` · `none`.
**Регистрация сервиса:** `services.yaml`, поле `health.type`.
---
## 🔴 Критичные правила lifecycle
- **НЕ убивать процессы.** Ни `kill`, ни `pkill`, ни `lsof -ti :8880 | xargs kill`.
- **Рестарт только по команде пользователя:** `launchctl kickstart gui/$(id -u)/com.eagle.dashboard`.
- **`launchctl bootout` — запрещено** (требует ребута для восстановления).
- **Hermes gateway (Eagle/Whale) — рестарт только через Dashboard API:**
`POST /api/services/hermes/restart`. Не `kill`, не `launchctl`.
- После рестарта — подождать 5 с, проверить `lsof`; если порт пуст — читать
`gateway.log`, НЕ рестартовать повторно.
- **Правки кода** → снять `__pycache__` + `launchctl kickstart -k` (иначе старый `.pyc`).
- «Стой»/«стоп» от пользователя → **ноль действий**, даже диагностики.
---
## 🔴 Вкладка `agents` (планируется) — ограничения Docker на macOS
Вкладка управляет `~/agents`, который монтируется в контейнер-исполнитель.
**Три ограничения доказаны живыми тестами 2026-09-26** (alpine:3.20, linux/arm64):
### 1. Симлинк в контейнер НЕ проезжает
Симлинк внутри bind-mounted каталога **битый** в контейнере: цель резолвится
относительно корня контейнера, где ничего не смонтировано.
```
host: -v /tmp/symtest/bind:/mnt с /mnt/target -> /tmp/symtest/outside
контейнер: cat /mnt/target/file.txt
→ No such file or directory
```
**«Подключить внешний проект симлинком» — невозможно.**
### 2. Live `mount --bind` (nsenter) контейнеру не виден
`nsenter -t 1 -m mount --bind` внутри VM → `BIND_OK`, но контейнер видит **пусто**.
Причина: Docker Desktop на macOS отдаёт файлы через **virtiofs**, который фиксирует
маппинг путей **на старте контейнера**. Проверено 3 прогонами (новый каталог, пауза 5 с,
каталог-до-старта) — все отрицательные.
⚠️ Отдельная ловушка: `nsenter -t 1 -m` заходит в **VM**, путей мира macOS там нет —
хост-каталоги для VM надо создавать **внутри VM**.
**Следствие: recreate обязателен.** `docker compose up -d` = 2-3 сек, `--privileged` не нужен.
### 3. Сетевые шары (SMB/NFS) в контейнер не протянуть
`mount_smbfs` в macOS → virtiofs его не видит → `-v /Volumes/share:/data` не работает.
Рекомендация: **`rsync` в `~/agents`** вместо прямого монтирования.
### Матрица выбора
| Способ | Папку? | Через тома? | Видно в контейнере | Итог |
|---|---|---|---|---|
| symlink | да | да | ✗ битый | ❌ |
| hardlink | **✗ нет** | ✗ нет | — | ✓ только файлы внутри тома |
| live `mount --bind` | да | да | ✗ | ❌ на macOS |
| **`-v` + recreate** | да | да | ✓ | ✅ рабочий путь |
| rsync-копия | да | да | ✓ | ✓ для шар/NAS |
Hardlink **не линкует каталоги** и **не пересекает ФС** (`EXDEV`). Проверять `st_dev`.
### Правила для вкладки
- Вкладка рулит **манифестом монтажей** (`~/Developer/agents-mounts.yaml`) → генерирует
`compose.override.yaml` → `docker compose up -d`. **Не** создаёт симлинки.
- Вкладка **никогда не удаляет** содержимое `~/agents` — только отвязка от контейнера.
- Секреты/auth CLI — **вне** `~/agents` (`~/cli-auth`), отдельным `-v`, иначе агент
с rw на `/agents` читает свои же токены.
Полный разбор: [[personal/tech/macos-docker-virtiofs-mount-limits]].
Применение: [[personal/plans/balda-mm-phase4-agents]].
---
## Известные питфоллы (кратко)
| Питфолл | Суть | Фикс |
|---|---|---|
| `_launchd_toggle()` | `launchctl list` = 0 даже когда процесс не запущен → кнопка инвертирована | проверять ключ `Disabled` в plist |
| `_msg` timeout | `setTimeout` держит stale-объект после `fetchCrons()` | захватывать source+id в замыкание, искать заново |
| `_list_launchd_crons()` | таймерные launchd-джобы показывали Resume (pid=None) | `"enabled": enabled` (plist загружен = включён) |
| EX_CONFIG (78) | launchd-джоб молча не работает | `~` не раскрывается → полный путь; либо скрипт сам отдаёт 78 |
| `lsof` not found | PATH под launchd = `/usr/bin:/bin` | полный путь `/usr/sbin/lsof` |
| `__pycache__` | старый `.pyc` отдаётся после правки | снять кэш + `kickstart -k` |
| PID для `http`/`port` | PID берётся через `lsof -ti :port` | если пусто — это норма для остановленного сервиса |
---
## Связанные заметки
- [[personal/tech/macos-docker-virtiofs-mount-limits]] — virtiofs: что не проезжает в контейнер
- [[personal/plans/balda-mm-phase4-agents]] — вкладка `agents` в контексте проекта
- скилл `eagle-dashboard` — источник истины
@@ -0,0 +1,167 @@
# macOS Docker Desktop — что НЕ проезжает в контейнер (virtiofs)
> Создан 2026-09-26. Статус: **проверено живыми тестами** (alpine:3.20, linux/arm64,
> Docker Desktop на macOS 26.6.1, Apple Silicon).
> Тема: границы проброса путей из мира macOS в контейнер.
> Все выводы получены прогонами, не по памяти. Команды воспроизведения — в §5.
---
## 0. Главный вывод одной строкой
На macOS Docker Desktop отдаёт контейнеру файлы **не через Linux `mount`**, а через
**virtiofs** — демон, который строит маппинг путей **в момент старта контейнера**
и знает только пути **мира macOS**, известные ему на старте.
Всё, что появляется **позже** и **внутри VM** (ручной `mount`, шара, новый каталог),
изнутри VM видно корректно — **до контейнера не доходит**. Это не кэш: рестарт
не помогает, `sleep` не помогает.
📌 На Linux-хосте (нативный Docker) этих ограничений **нет** — там
`nsenter`-трюки с `mount --bind` работают. Все выводы ниже — про **macOS**.
---
## 1. Симлинки в контейнер НЕ проезжают
Симлинк, чья цель лежит **вне** смонтированного каталога, в контейнере **битый**.
```
host: /tmp/symtest/outside/file.txt = "REAL CONTENT ON HOST"
bind: -v /tmp/symtest/bind:/mnt (внутри /mnt/target -> /tmp/symtest/outside)
контейнер: cat /mnt/target/file.txt
→ can't open '/mnt/target/file.txt': No such file or directory
```
Сам симлинк внутри **виден** (`lrwxrwxrwx target -> /tmp/symtest/outside`), но цель
резолвится **относительно корня контейнера**, где этого пути нет. Docker монтирует
только реальные пути — симлинки не разворачивает.
**Следствие:** папка с симлинками на внешние цели в контейнере нерабочая, даже если
на хосте всё открывается.
---
## 2. Live `mount --bind` (nsenter) контейнеру НЕ виден
Схема обычная для Linux-хоста:
```bash
docker run --rm --privileged --pid=host alpine nsenter -t 1 -m \
mount --bind /host/source /host/agents/target
```
⚠️ **Первая ловушка (до самого mount):** `nsenter -t 1 -m` заходит в **VM**, а не в
macOS. Пути **мира macOS там не существуют**. Поэтому хост-путь для VM нужно
создавать **внутри VM** — например, в том же `nsenter -t 1 -m sh -c '...'`.
Проверено **тремя** прогонами, результат одинаковый:
| Прогон | Условие | Результат |
|---|---|---|
| 1 | каталог создан **после** старта контейнера + live bind | в контейнере пусто |
| 2 | ожидание 5 с (гипотеза «кэш атрибутов virtiofs») | пусто |
| 3 | каталог существовал **до** старта контейнера + bind **поверх** него | **пусто** |
Изнутри VM всё корректно:
```
VM: cat /tmp/bindtest2/agents/existing/f.txt → "DATA2" ✓ файл есть
mount: BIND_OK ✓ смонтировалось
контейнер: cat /agents/existing/f.txt → No such file or directory ✗
```
**Вывод:** live-bind для добавления каталогов в контейнер на macOS **недоступен**.
Единственный рабочий путь — **`-v` в compose + пересоздание контейнера**
(`docker compose up -d` = 2-3 сек, `--privileged` не нужен).
---
## 3. Сетевые шары (SMB/NFS) в контейнер НЕ протянуть тем же путём
- `mount_smbfs`/`mount_nfs` в macOS создаёт точку в **мире macOS** → virtiofs её
не видит → `-v /Volumes/share:/data` **не работает**.
- mount **внутри VM** через `nsenter` технически проходит, но:
- **не переживает рестарт Docker** (нужен startup-хук);
- требует `--privileged`;
- SMB/NFS не дают hardlink между экспортами, блокировки файлов ведут себя иначе —
сборщики/редакторы на шаре работают хуже, чем на локальном диске.
**Рекомендация:** не тянуть шару в контейнер. Если данные на NAS — **`rsync` в
локальную папку** по требованию. Копия переживает всё и не требует privileged.
---
## 4. Hardlink — чего он НЕ может (независимо от Docker)
Это свойство ФС, не Docker:
1. **Hardlink на каталог не делается.** Ни в APFS, ни в ext4, ни в NTFS.
`ln dir1 dir2` → `Operation not permitted`. У каталога появилась бы ссылка на
себя → цикл в дереве. Запрещено на уровне syscall.
2. **Между томами не работает.** Только внутри одной ФС (один `st_dev`),
иначе `EXDEV: Cross-device link`.
**Следствие:** «подключить проект-каталог hardlink'ом извне» — **физически
невозможно**. Hardlink годится только для **файлов внутри одного тома**.
---
## 5. Матрица: чем подключать путь в контейнер
| Способ | Папку? | Через тома? | Видно в контейнере (macOS) | Итог |
|---|---|---|---|---|
| symlink | да | да | ✗ битый | ❌ для внешних целей |
| hardlink | **✗ нет** | ✗ нет | — | ✓ только файлы внутри тома |
| live `mount --bind` (nsenter) | да | да | ✗ | ❌ на macOS |
| **`-v` в compose + recreate** | да | да | ✓ | ✅ **рабочий путь** |
| rsync-копия | да | да | ✓ | ✓ когда данные на шаре/NAS |
---
## 6. Команды воспроизведения
```bash
# 1. Симлинк в контейнер
mkdir -p /tmp/symtest/outside /tmp/symtest/bind
echo "REAL CONTENT ON HOST" > /tmp/symtest/outside/file.txt
ln -sfn /tmp/symtest/outside /tmp/symtest/bind/target
docker run --rm --platform linux/arm64 -v /tmp/symtest/bind:/mnt alpine:3.20 \
sh -c 'ls -la /mnt/; cat /mnt/target/file.txt' # → No such file or directory
# 2. Live bind (внимание: пути создавать ВНУТРИ VM)
docker run -d --name t -v /tmp/bindtest2/agents:/agents alpine:3.20 sleep 300
docker run --rm --privileged --pid=host alpine:3.20 nsenter -t 1 -m sh -c \
'mkdir -p /tmp/bindtest2/agents/existing /tmp/bindtest2/ext2
echo DATA2 > /tmp/bindtest2/ext2/f.txt
mount --bind /tmp/bindtest2/ext2 /tmp/bindtest2/agents/existing'
docker exec t cat /agents/existing/f.txt # → No such file or directory
# 3. Hardlink на каталог (должен упасть)
ln /tmp/a /tmp/b # → Operation not permitted
```
⚠️ Тестовые `docker run` тянут образы — гонять их на Mac, не на слабом хосте
(Kraken: buildkit-кэш и слои лягут на диск зря).
---
## 7. Что делать в реальном проекте
1. **Внешние каталоги** — только через **`-v` в compose** + `docker compose up -d`.
Манифест монтажей (список `name` + `host_path`) генерирует
`compose.override.yaml` — composefile не правится скриптом вручную.
2. **auth/секреты CLI — на хосте, ВНЕ рабочего каталога агента.** Иначе агент,
которому рабочий каталог смонтирован на запись, получает доступ к своим же
токенам. Монтировать отдельным `-v`.
3. **Шары** — `rsync` в локальную папку, не прямой mount.
4. **Hardlink** — только файлы внутри одного тома, с проверкой `st_dev`.
5. **Не строить архитектуру на live-mount'ах** — на macOS это тупик.
---
## Связанные заметки
- [[personal/plans/balda-mm-phase4-agents]] — применение этих выводов (Фаза 4)
- [[personal/projects/balda/balda-mm-delegation-plan]] — общий план проекта
- скилл `eagle-dashboard` — сервис-реестр, health-типы, launchd
- скилл `docker-service-administration` — lifecycle контейнеров
+76 -3
View File
@@ -19,13 +19,86 @@ related:
# Music Tags Repair — TrueNAS (7906 файлов)
> **Статус: ЭТАПЫ 1–2 ЗАВЕРШЕНЫ (2026-09-26). CSV v5 + ноты ревью отданы Alex. Правки файлов НЕ начаты.**
> **Статус: ЭТАПЫ 1–3 ЗАВЕРШЕНЫ (2026-09-26). CSV v5 + ноты ревью отданы Alex, блок 1 разобран вручную. Правки файлов НЕ начаты.**
> Следующий шаг — апрув Alex по нотам ревью, затем бэкап + применение.
> Прогонялись **четыре волны** проверок: полный аудит всех строк (`audit_plan.py` + `final_gate.py`) → 6 багов,
> Прогонялись **пять волн** проверок: полный аудит всех строк (`audit_plan.py` + `final_gate.py`) → 6 багов,
> третья волна по замечаниям Alex → 3 бага (en-dash + подстроковый поиск стоп-слов),
> **четвёртая волна (ручной проход) → 10 багов (track-номера, disc-track, восклицательные знаки, регистр)**.
> **четвёртая волна (ручной проход) → 10 багов (track-номера, disc-track, восклицательные знаки, регистр)**,
> **пятая волна (ручной разбор блока 1 глазами) → 28 файлов, 10 артистов вписано вручную**.
> Итог v5: 6773 `fix`, mojibake = 0, 121 тест PASS, блок «требует решения» 296 → 28.
## 🖐 ПЯТАЯ ВОЛНА: РУЧНОЙ РАЗБОР БЛОКА 1 (2026-09-26)
Alex: **«Ты проверил все вручную?»** → **«Без правила берешь и руками правишь. Без скриптов!!»** →
**«В md файлах блядь на аппрув! Какого хуя ты в id3 собрался лазить?!»** → **«ХУЯРЬ!»**
Разбор всех **28 файлов блока «требует решения»** — вручную, по одному, без правил и регексов.
Результат записан в `personal/plans/music-tags-review/1-needs-decision.md` (vault, 6843 байта).
### 🔴 ГЛАВНАЯ НАХОДКА ПЯТОЙ ВОЛНЫ: артист в скобках, разделителя нет
Файл `01 Hit The Road Jack (Kub vs Ray Charles).mp3` — теги пустые, в имени:
`01` + название + артист **в скобках**. Разделителя ` - ` между artist и title нет,
поэтому `split_artist_title()` не сработал → **artist остался пустым, хотя он очевиден**.
Правило «нет ` - ` → artist пусто» оставляет **тысячи** файлов без артиста там, где он есть:
в скобках, после `vs`, `feat.`, в имени папки. Это **системная дыра**, а не один файл.
> 🔴 **ПИТФОЛЛ: отсутствие ` - ` НЕ означает отсутствие артиста.**
> Ищи артиста в скобках (`vs`, `feat.`, `&`, `with`), в префиксе до названия, в имени папки.
> Alex: «Тебе разве не очевидно что hit the road jack это название а kub vs ray charles это артист?»
### Разбор 28 файлов вручную
**А. Артист вписан руками — 10 файлов (очевидно из контекста):**
| Файл | artist | title | Откуда ясно |
|---|---|---|---|
| `01 Hit The Road Jack (Kub vs Ray Charles)` | **Kub vs Ray Charles** | Hit the Road Jack | артист в скобках |
| `Pirates of the Caribbean` | **Hans Zimmer** feat. Dimitri Vegas & Like Mike | He's a Pirate | artist уже в title |
| `Interstellar` | **Hans Zimmer** | No Time for Caution | artist уже в title |
| `Кольщик` | **Михаил Круг** | Кольщик | известная песня |
| `Пропаганда-Супер детка` | **Пропаганда** | Супер детка | имя = artist-title |
| `detskiy_hor_benassi_satisfaction` | **Детский хор + Benny Benassi** | Satisfaction | имя файла |
| `06 Benassi I am a Dj` | **Benassi** | I Am a DJ | имя = artist + title |
| `Mymij_troll_kontrabanda` | **Мумий Тролль** | Контрабанда | имя файла |
| `lil jon` | **Lil Jon** | *(названия нет)* | имя файла |
| `Dj Riga ceh-126 Track` | **DJ Riga** | ceh-126 Track | имя = artist + title |
**Б. Только title, artist пусто — 18 файлов** (Jellyfin → Unknown Artist, не угадываю).
### ⚪ Что НЕ угадано (осознанно)
- `Казанова`, `Младший лейтенант`, `Остаёмся зимовать` — теги мертвы, в имени только название.
Похоже на Сектор Газа / Вороваек, но **не пишу**.
- `НАШЕСТВИЕ 2009`, `Kazantip 2008` — это **события/фестивали**, не артисты. Перенесены в примечание.
- `Sensation White`, `Вертолёт башку сорвёт!(dj karas extra club)` — artist разведён (`Matisse`, `DJ Karas`),
остальное оставлено.
### 🔴 ПИТФОЛЛ ПРАВОК MD: два подряд `patch` съедают строку
При чистке дубля (`БОДРЯЧКОМ` попал в два блока) второй `patch` с тем же `old_string` **съел
закрывающую строку** блока → осиротевшая `- \`album\`: ⟨повреждено⟩ → —`. Пришлось читать файл
с `offset` и удалять точечно вторым патчем.
> ⚠️ **После каждого `patch` в теле md — читать окрестность (`read_file` с offset) и сверять
> число чекбоксов.** Проверка: `grep -c '^- \[ \]'` = ожидаемое (28), `grep -c '^> - \['` = 0.
**Верификация:** 28 чекбоксов, 28 уникальных файлов, 0 битых (`> - [ ]` — нет).
### 📊 Цифры пятой волны
| Метрика | Значение |
|---|---:|
| Блок 1 «требует решения» | 28 |
| Artist вписан вручную (А) | **10** |
| Только title, artist пусто (Б) | **18** |
| Файлов без artist всего | **8** (источника нет) |
| Размер `1-needs-decision.md` | 6843 байта |
> ⚠️ **Важно: правки в ID3 НЕ делались.** Alex явно запретил лезть в теги до апрува —
> работа только в md-файлах Obsidian. Применение тегов = отдельный шаг после «go» + бэкап.
## 🔴 ЧЕТВЁРТАЯ ВОЛНА: РУЧНОЙ ПРОХОД И TRACK-НОМЕРА (2026-09-26)
Alex: **«Ты блядь руками пройди и пофикси все что мне предлагал кроме того что неочевидно!