Files
obsidian-vault/personal/tech/eagle-dashboard-skill.md
T

7.3 KiB
Executable File
Raw Blame History

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 если пусто — это норма для остановленного сервиса

Связанные заметки