126 lines
7.3 KiB
Markdown
Executable File
126 lines
7.3 KiB
Markdown
Executable File
# 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` — источник истины
|