# 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` — источник истины