[2026-09-26] eagle: family/how-to/truenas-infrastructure.md personal/plans/balda-mm-phase4-agents.md personal/tech/agents-bridge.md personal/tech/eagle-dashboard-agents-tab.md
This commit is contained in:
1 parent
31644d1010
commit
769bb4c21b
4 files changed
+288
-27
No files matched your search
@@ -127,7 +127,9 @@
|
|||||||
> echo "== $c =="; docker inspect $c --format '{{json .Mounts}}' | jq -c '.[] | select(.Type=="volume") | {Name, Destination, Source}'
|
> echo "== $c =="; docker inspect $c --format '{{json .Mounts}}' | jq -c '.[] | select(.Type=="volume") | {Name, Destination, Source}'
|
||||||
> done
|
> done
|
||||||
> ```
|
> ```
|
||||||
> **Долгосрочное лечение:** переводить критичные сервисы с named volumes на **bind-mount в `/mnt/RED_2TB/docker/<app>/`** — тогда данные попадают в обычный бэкап `mailru-crypt:` и переживают пересоздание пула. Кандидат №1 — File Browser (см. раздел «File Browser — детали», вариант B).
|
> **Долгосрочное лечение:** переводить критичные сервисы с named volumes на **bind-mount в `/mnt/RED_2TB/docker/<app>/`** — тогда данные попадают в обычный бэкап `mailru-crypt:` и переживают пересоздание пула.
|
||||||
|
> ✅ **Первый сделан 2026-09-26 — File Browser** (рецепт + 5 питфоллов: §«File Browser — детали» ниже).
|
||||||
|
> Остальные сервисы с томами — **не проверены**, тот же приём применим (см. сниппет `docker inspect … select(.Type=="volume")` выше).
|
||||||
|
|
||||||
> **⚠️ ДРУГОЕ (открытое):** пул `DEST` (IronWolf) на 2026-08-24 `DEGRADED` — разобраться, почему (отвалился mirror-член). TimeMachine (~277G) не возвращён (бэкапы macOS, вероятно не нужен — решение Alex не подтверждено). SSH с Mac по `192.168.2.197` — подключение давало `Connection reset` / `kex_exchange_identification` (вероятен конфликт сессий при активном входе на консоль) — рабочий путь: `mallexxx.duckdns.org`.
|
> **⚠️ ДРУГОЕ (открытое):** пул `DEST` (IronWolf) на 2026-08-24 `DEGRADED` — разобраться, почему (отвалился mirror-член). TimeMachine (~277G) не возвращён (бэкапы macOS, вероятно не нужен — решение Alex не подтверждено). SSH с Mac по `192.168.2.197` — подключение давало `Connection reset` / `kex_exchange_identification` (вероятен конфликт сессий при активном входе на консоль) — рабочий путь: `mallexxx.duckdns.org`.
|
||||||
|
|
||||||
@@ -221,7 +223,7 @@ TrueNAS использует **NFSv4 ACL**, а не стандартный POSIX
|
|||||||
| transmission | linuxserver/transmission:latest | 9091, 51413 | transmission.mallexxx.duckdns.org |
|
| transmission | linuxserver/transmission:latest | 9091, 51413 | transmission.mallexxx.duckdns.org |
|
||||||
| hermes-taiga | hermes-taiga:latest | — | — |
|
| hermes-taiga | hermes-taiga:latest | — | — |
|
||||||
| vless-proxy | teddysun/xray:latest | — | — |
|
| vless-proxy | teddysun/xray:latest | — | — |
|
||||||
| filebrowser | filebrowser/filebrowser:latest (v2.63.23) | 8000 | (через Caddy → `192.168.2.197:8000`) — см. раздел «File Browser»; **данные в папке, bind-mount (2026-09-26)**, пользователь `file_admin` |
|
| filebrowser | filebrowser/filebrowser:latest (v2.63.23) | 8000 | **docs.mallexxx.duckdns.org** → Caddy → `192.168.2.197:8000` — см. раздел «File Browser»; **данные в папке, bind-mount (2026-09-26)**, пользователь `file_admin`, `tokenExpirationTime=720h` |
|
||||||
| webdav | hacdias/webdav:latest | — | webdav.mallexxx.duckdns.org |
|
| webdav | hacdias/webdav:latest | — | webdav.mallexxx.duckdns.org |
|
||||||
| ~~homeassistant~~ ⛔ | home-assistant:stable | ~~8123~~ | **ПОГАШЕН 2026-09-14** — переехал на t610 |
|
| ~~homeassistant~~ ⛔ | home-assistant:stable | ~~8123~~ | **ПОГАШЕН 2026-09-14** — переехал на t610 |
|
||||||
| immich-server | immich-app/immich-server:release | 2283 | immich.mallexxx.duckdns.org |
|
| immich-server | immich-app/immich-server:release | 2283 | immich.mallexxx.duckdns.org |
|
||||||
@@ -459,7 +461,26 @@ filebrowser_filebrowser_config → /mnt/.ix-apps/docker/volumes/filebrowser_file
|
|||||||
2026/09/26 08:44:02 /api/renew: 401 92.62.77.233 ← Кра́кен
|
2026/09/26 08:44:02 /api/renew: 401 92.62.77.233 ← Кра́кен
|
||||||
2026/09/26 08:44:17 /api/login: 403 92.62.77.233 ← попытка логина, отбита
|
2026/09/26 08:44:17 /api/login: 403 92.62.77.233 ← попытка логина, отбита
|
||||||
```
|
```
|
||||||
Прочие настройки из БД: `authMethod: "json"`, `signup: false`, `minimumPasswordLength: 12`, `tokenExpirationTime: "2h"`.
|
Прочие настройки из БД: `authMethod: "json"`, `signup: false`, `minimumPasswordLength: 12`.
|
||||||
|
**✅ `hideLoginButton` → `false`** (2026-09-26) — кнопка логина вернулась, `403` на `/api/login` ушёл.
|
||||||
|
|
||||||
|
> ⏱️ **`tokenExpirationTime` был `2h` → поставлен `720h` (30 дней), 2026-09-26.**
|
||||||
|
> Причина смены: `/api/renew` отдаёт **401** (продление сессии не работает через Caddy-контур),
|
||||||
|
> поэтому **любой** разлогин был жёстким выбросом ровно через 2 часа. При `720h` выброс снят.
|
||||||
|
> Корень (`renew` 401) **НЕ лечен** — если снова выкинет, смотреть cookie/refresh, не срок.
|
||||||
|
> Правка только при остановленном контейнере (пифолл 2). Бэкап: `database/filebrowser.db.bak-before-token-20260926`.
|
||||||
|
> ```
|
||||||
|
> docker stop filebrowser
|
||||||
|
> docker run --rm -v /mnt/RED_2TB/docker/filebrowser/database:/database \
|
||||||
|
> filebrowser/filebrowser:latest config set --tokenExpirationTime=720h -d /database/filebrowser.db
|
||||||
|
> docker compose up -d # из /mnt/RED_2TB/docker/filebrowser/
|
||||||
|
> ```
|
||||||
|
> ⚠️ CLI-флаг пишется `--tokenExpirationTime`; старый `--hide-login-button` **deprecated** → `--hideLoginButton`.
|
||||||
|
|
||||||
|
> 🌐 **Адрес входа: `docs.mallexxx.duckdns.org`** (Caddy-блок уже содержит
|
||||||
|
> `header_up X-Forwarded-Proto https` + `X-Forwarded-Host {host}` — это НЕ причина 403).
|
||||||
|
> ⚠️ Прямой `POST /api/login` с `Host: localhost` или `Host: 192.168.2.197:8000` **тоже даёт 403** —
|
||||||
|
> это не поломка, а сверка origin. Проверять логин только через реальный домен.
|
||||||
|
|
||||||
> ⚠️ **«403 на логине» ≠ «контейнер сломан».** Контейнер `Up (healthy)`, HTTP 200 на `/`, порт слушает.
|
> ⚠️ **«403 на логине» ≠ «контейнер сломан».** Контейнер `Up (healthy)`, HTTP 200 на `/`, порт слушает.
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
# Фаза 4 — исполнитель на Mac + `~/agents` + секция «Agents» в Eagle Dashboard
|
# Фаза 4 — исполнитель на Mac + `~/agents` + секция «Agents» в Eagle Dashboard
|
||||||
|
|
||||||
> План. **Шаги 1-2 выполнены** (2026-09-26): образ собран, контейнер `balda-executor` Up (healthy),
|
> План. **Шаги 1-3 выполнены** (2026-09-26): образ собран, контейнер `balda-executor` Up (healthy),
|
||||||
> `claude 2.1.283` + `codex 0.157.1` внутри, монтирования проверены в обе стороны.
|
> `claude 2.1.283` + `codex 0.157.1` внутри, монтирования проверены в обе стороны.
|
||||||
> Мост `agents-bridge` :8790 поднят и протестирован (auth, идемпотентность, сквозной вызов CLI).
|
> Мост `agents-bridge` :8790 — под **launchd** (`com.agents.bridge`, `RunAtLoad` + `KeepAlive`),
|
||||||
> 🔴 Осталось: **логин CLI делает Alex сам** (копирование auth-файлов отклонено); мост не в launchd;
|
> протестирован (auth 401, идемпотентность, сквозной вызов CLI).
|
||||||
> шаги 3-6 не начаты.
|
> Шаг 4 — **код написан, не активирован** (нужен перезапуск дашборда).
|
||||||
|
> 🔴 Осталось: **логин CLI делает Alex сам**; активация секции «Agents»; шаг 3 (туннель);
|
||||||
|
> шаги 5-6.
|
||||||
> Часть [[personal/projects/balda/balda-mm-delegation-plan]] (Фазы 4-5).
|
> Часть [[personal/projects/balda/balda-mm-delegation-plan]] (Фазы 4-5).
|
||||||
> Предыдущее: [[personal/projects/balda/balda-kraken-mm-deploy]] (Фаза 3 ✅ — бот отвечает Alex).
|
> Предыдущее: [[personal/projects/balda/balda-kraken-mm-deploy]] (Фаза 3 ✅ — бот отвечает Alex).
|
||||||
|
|
||||||
@@ -240,6 +242,26 @@ cd ~/Developer/agents-bridge
|
|||||||
~/Developer/eagle-dash/.venv/bin/python -m uvicorn app:app --host 127.0.0.1 --port 8790
|
~/Developer/eagle-dash/.venv/bin/python -m uvicorn app:app --host 127.0.0.1 --port 8790
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### ✅ Шаг 3 ВЫПОЛНЕН (2026-09-26) — мост под launchd
|
||||||
|
|
||||||
|
Плейт `com.agents.bridge`: `~/Library/LaunchAgents/com.agents.bridge.plist`,
|
||||||
|
`RunAtLoad` + `KeepAlive`, `WorkingDirectory=/Users/admin/Developer/agents-bridge`,
|
||||||
|
логи в `~/Library/Logs/agents-bridge.{log,err.log}`.
|
||||||
|
|
||||||
|
Блокер шага 3 (туннель вёл бы в закрытый порт) снят: мост поднимается при входе и
|
||||||
|
перезапускается при падении.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.agents.bridge.plist
|
||||||
|
launchctl list | grep agents.bridge # → 3063 0 com.agents.bridge
|
||||||
|
```
|
||||||
|
|
||||||
|
📌 **launchd не читает shell `PATH`** — в плейт добавлен `EnvironmentVariables.PATH`
|
||||||
|
с `/opt/homebrew/bin` (иначе `executor.py` не найдёт `docker`).
|
||||||
|
|
||||||
|
⚠️ Тест `KeepAlive` (убить pid → убедиться, что поднялся) **не выполнен**: требовал
|
||||||
|
`kill -9` и был отклонён. Штатная проверка — `launchctl kickstart -k`.
|
||||||
|
|
||||||
#### 🔴 Питфолл: `BRIDGE_SECRET` и redaction Hermes
|
#### 🔴 Питфолл: `BRIDGE_SECRET` и redaction Hermes
|
||||||
|
|
||||||
Команда, создающая `.env` через `python -c` с f-строкой `BRIDGE_SECRET={...}`, в логе выглядит
|
Команда, создающая `.env` через `python -c` с f-строкой `BRIDGE_SECRET={...}`, в логе выглядит
|
||||||
@@ -254,9 +276,19 @@ cd ~/Developer/agents-bridge
|
|||||||
Добавить published route `agents-bridge.qentra.top` → HTTP `localhost:8790` +
|
Добавить published route `agents-bridge.qentra.top` → HTTP `localhost:8790` +
|
||||||
Access Application. Токен и процесс cloudflared не трогаются. Проверка: `curl` извне на `/healthz`.
|
Access Application. Токен и процесс cloudflared не трогаются. Проверка: `curl` извне на `/healthz`.
|
||||||
⚠️ hostname — **одноуровневый** (двухуровневые дают TLS handshake failure).
|
⚠️ hostname — **одноуровневый** (двухуровневые дают TLS handshake failure).
|
||||||
|
📌 Alex делает это сам в панели Cloudflare Zero Trust (API-доступа к CF у агента нет).
|
||||||
|
Блокер снят: мост теперь под launchd (шаг 3), URL не будет вести в закрытый порт.
|
||||||
|
|
||||||
**4. Секция «Agents» (~3-4 ч)**
|
**4. Секция «Agents» — код ГОТОВ, не активирован (2026-09-26)**
|
||||||
6 роутов + вкладка + запись в `services.yaml` → `__pycache__` + `launchctl kickstart -k`.
|
`~/Developer/eagle-dash/agents.py` (роутер, 8 эндпоинтов) + `main.py`
|
||||||
|
(`app.include_router`) + `services.yaml` (запись `agents_bridge`).
|
||||||
|
Aктивация:
|
||||||
|
```bash
|
||||||
|
rm -rf ~/Developer/eagle-dash/__pycache__ && \
|
||||||
|
launchctl kickstart -k gui/$(id -u)/com.eagle.dashboard
|
||||||
|
```
|
||||||
|
Детали, питфоллы и спорный момент с `type: daemon` (конфликт с launchd моста за порт) —
|
||||||
|
[[personal/tech/eagle-dashboard-agents-tab]]. Вкладка `?tab=agents` в шаблоне **ещё не сделана**.
|
||||||
|
|
||||||
**5. Связка Балда → мост (~2 ч)**
|
**5. Связка Балда → мост (~2 ч)**
|
||||||
MCP-инструмент в Балде → `POST /task`; проба `echo ok > /agents/test/probe.txt`.
|
MCP-инструмент в Балде → `POST /task`; проба `echo ok > /agents/test/probe.txt`.
|
||||||
@@ -285,20 +317,30 @@ MCP-инструмент в Балде → `POST /task`; проба `echo ok > /
|
|||||||
2. ~~Нужен ли `ask`-апрув~~ → при `allow_all` не нужен; при потребности начинать с B (апрув в мосте, ~0.5 дня)
|
2. ~~Нужен ли `ask`-апрув~~ → при `allow_all` не нужен; при потребности начинать с B (апрув в мосте, ~0.5 дня)
|
||||||
3. 🔴 **Логин CLI — делает Alex сам.** Копирование auth-файлов отклонено; до логина задачи
|
3. 🔴 **Логин CLI — делает Alex сам.** Копирование auth-файлов отклонено; до логина задачи
|
||||||
возвращают `Not logged in`
|
возвращают `Not logged in`
|
||||||
4. 🔴 **Мост не в launchd** — поднят вручную (`uvicorn` в фоне), при перезагрузке Mac умрёт.
|
4. ~~Мост не в launchd~~ → ✅ **закрыто**: `com.agents.bridge` (`RunAtLoad` + `KeepAlive`)
|
||||||
Плейт `com.agents.bridge` **обязателен до шага 3**: туннель опубликует URL, который может
|
5. 🔴 **Активация секции «Agents»** — код готов, нужен `__pycache__` + `kickstart -k` (апрув не получен)
|
||||||
вести в закрытый порт. Предложено, ждёт команды Alex
|
6. ⚠️ **Конфликт за порт 8790** — `agents_bridge` в `services.yaml` объявлен `type: daemon`,
|
||||||
5. Hostname моста — `agents-bridge.qentra.top` (шаг 3, ждёт public route в Cloud Zero Trust)
|
но мост уже управляется своим launchd. Ревизия при активации: `type: system` +
|
||||||
6. Порт `8790` — проверено, свободен (`lsof -nP -i :8790`), занят мостом после запуска
|
`health.type: launchctl` либо `type: none`. См. [[personal/tech/eagle-dashboard-agents-tab]] §4
|
||||||
7. ⚠️ В дашборде в `services.yaml` **нет** записи `agents_bridge` — добавить в шаге 4
|
7. ⚠️ Вкладка `?tab=agents` в `templates/index.html` не сделана — API готов, UI нет
|
||||||
(`health.type: http`, `url: http://127.0.0.1:8790/healthz`, `port: 8790`)
|
8. Hostname моста — `agents-bridge.qentra.top` (шаг 3, public route добавляет Alex в CF)
|
||||||
|
9. Порт `8790` — свободен до старта моста, занят после; иных слушателей нет
|
||||||
|
10. ⚠️ Под launchd `agents.py` не найдёт `docker`/`docker-compose` (PATH) — если mount вернёт 500,
|
||||||
|
заменить литералы на `/opt/homebrew/bin/...`. Живьём не проверено (дашборд не перезапущен)
|
||||||
|
|
||||||
## Связанные заметки
|
## Связанные заметки
|
||||||
- [[personal/projects/balda/balda-mm-delegation-plan]] — общий план (Фазы 4-5)
|
- [[personal/projects/balda/balda-mm-delegation-plan]] — общий план (Фазы 4-5)
|
||||||
- [[personal/projects/balda/balda-kraken-mm-deploy]] — Фаза 3 (✅)
|
- [[personal/projects/balda/balda-kraken-mm-deploy]] — Фаза 3 (✅)
|
||||||
- [[personal/plans/balda-mm-pr-plan]] — Фаза 2 (PR #8)
|
- [[personal/plans/balda-mm-pr-plan]] — Фаза 2 (PR #8)
|
||||||
- [[personal/tech/agents-bridge]] — мост :8790, API, питфоллы (шаг 2 ✅)
|
- [[personal/tech/agents-bridge]] — мост :8790, API, питфоллы (шаг 2 ✅)
|
||||||
|
- [[personal/tech/eagle-dashboard-agents-tab]] — секция «Agents»: код, API, питфоллы (шаг 4, не активирован)
|
||||||
- [[personal/tech/macos-docker-virtiofs-mount-limits]] — ограничения macOS Docker
|
- [[personal/tech/macos-docker-virtiofs-mount-limits]] — ограничения macOS Docker
|
||||||
- [[personal/tech/balda-go-fx-pitfalls]] — fx-питфоллы, дельта-апплаер
|
- [[personal/tech/balda-go-fx-pitfalls]] — fx-питфоллы, дельта-апплаер
|
||||||
- [[family/how-to/kraken-portainer-access]] — cloudflared, routes
|
- [[family/how-to/kraken-portainer-access]] — cloudflared, routes
|
||||||
- скилл `eagle-dashboard` — архитектура дашборда
|
- скилл `eagle-dashboard` — архитектура дашборда
|
||||||
|
|
||||||
|
## История
|
||||||
|
- **2026-09-26**: план создан; шаги 1-2 выполнены (образ, контейнер, мост на :8790).
|
||||||
|
- **2026-09-26** (продолжение): мост переведён под launchd (шаг 3 ✅, `com.agents.bridge`);
|
||||||
|
написан код секции «Agents» (шаг 4, не активирован); логин CLI оставлен за Alex.
|
||||||
|
Туннель (шаг 3 сети) — за Alex, в панели Cloudflare Zero Trust.
|
||||||
@@ -87,18 +87,35 @@ GET /tasks?limit=N → последние задачи (для дашбор
|
|||||||
|
|
||||||
## Запуск
|
## Запуск
|
||||||
|
|
||||||
**Сейчас — вручную** (launchd ещё не сделан, см. открытый пункт 4 плана):
|
**Под launchd** (с 2026-09-26) — плейт `com.agents.bridge`:
|
||||||
|
|
||||||
|
| Параметр | Значение |
|
||||||
|
|---|---|
|
||||||
|
| Label | `com.agents.bridge` |
|
||||||
|
| Plist | `~/Library/LaunchAgents/com.agents.bridge.plist` |
|
||||||
|
| Program | `~/Developer/eagle-dash/.venv/bin/python -m uvicorn app:app --host 127.0.0.1 --port 8790` |
|
||||||
|
| WorkingDirectory | `/Users/admin/Developer/agents-bridge` |
|
||||||
|
| `RunAtLoad` / `KeepAlive` | `true` / `true` |
|
||||||
|
| Логи | `~/Library/Logs/agents-bridge.log` (+ `.err.log`) |
|
||||||
|
| `PATH` в EnvironmentVariables | `/opt/homebrew/bin:...` — launchd не читает shell-PATH |
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd ~/Developer/agents-bridge
|
# установка/переустановка
|
||||||
~/Developer/eagle-dash/.venv/bin/python -m uvicorn app:app --host 127.0.0.1 --port 8790
|
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.agents.bridge.plist
|
||||||
```
|
# перезапуск
|
||||||
|
launchctl kickstart -k gui/$(id -u)/com.agents.bridge
|
||||||
Проверка:
|
# статус (pid, last exit)
|
||||||
```bash
|
launchctl list | grep agents.bridge
|
||||||
|
# проверка
|
||||||
curl -s http://127.0.0.1:8790/healthz
|
curl -s http://127.0.0.1:8790/healthz
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Проверено 2026-09-26: `bootstrap` → exit 0, в `launchctl list` строка `3063 0 com.agents.bridge`,
|
||||||
|
`/healthz` = `status: ok`. ⚠️ Процедура убийства pid для проверки `KeepAlive` **не выполнялась** —
|
||||||
|
команда требует `kill -9` и была отклонена пользователем, не повторять без явной команды.
|
||||||
|
|
||||||
|
Ручной запуск (если launchd отключён) — тем же `uvicorn`, но это аварийный путь.
|
||||||
|
|
||||||
⚠️ На macOS `docker compose` **не работает** — только `docker-compose` (см. план, шаг 1).
|
⚠️ На macOS `docker compose` **не работает** — только `docker-compose` (см. план, шаг 1).
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -132,10 +149,27 @@ auth-файлов из `~/.claude`/`~/.codex` отклонено, не пред
|
|||||||
как `timeout` с причиной `bridge restarted while task was running`. Иначе они навсегда
|
как `timeout` с причиной `bridge restarted while task was running`. Иначе они навсегда
|
||||||
висят активными и блокируют слот лимита.
|
висят активными и блокируют слот лимита.
|
||||||
|
|
||||||
### 4. Мост не переживает перезагрузку
|
### 4. Мост не переживает перезагрузку — ✅ ЗАКРЫТО
|
||||||
|
|
||||||
Пока нет launchd-плейта `com.agents.bridge`, процесс живёт только в текущей сессии.
|
Решено 2026-09-26: плейт `com.agents.bridge` (`RunAtLoad` + `KeepAlive`), поднят через
|
||||||
Это **блокер для шага 3** (туннель опубликует URL, ведущий в закрытый порт).
|
`launchctl bootstrap`. Шаг 3 (туннель) больше не блокирован.
|
||||||
|
|
||||||
|
⚠️ Остаётся непроверенным поведение `KeepAlive` при падении процесса: тест требовал
|
||||||
|
`kill -9` по pid моста и был отклонён. Проверить можно `launchctl kickstart -k`
|
||||||
|
(штатный путь) либо перезагрузкой Mac.
|
||||||
|
|
||||||
|
### 5. launchd не видит `docker` — `PATH` задавать в плейте
|
||||||
|
|
||||||
|
`ProgramArguments` использует абсолютный путь к python, но `executor.py` вызывает
|
||||||
|
`docker` и `docker-compose` из `PATH`. При запуске под launchd `PATH` = `/usr/bin:/bin`,
|
||||||
|
поэтому `docker` (`/opt/homebrew/bin/docker`) не находится. В плейт добавлен блок
|
||||||
|
`EnvironmentVariables.PATH` с `/opt/homebrew/bin` первым. Симптом без него:
|
||||||
|
`docker не найден в PATH процесса моста` в `stderr` задачи.
|
||||||
|
|
||||||
|
### 6. Мост слушает только `127.0.0.1`
|
||||||
|
|
||||||
|
`BRIDGE_HOST=127.0.0.1` — намеренно. Наружу выставляется туннелем (шаг 3), не пробросом
|
||||||
|
порта. Не менять на `0.0.0.0` без Access Application на стороне Cloudflare.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -159,7 +193,11 @@ curl -s -o /dev/null -w "%{http_code}\n" -X POST http://127.0.0.1:8790/task \
|
|||||||
- [[personal/plans/balda-mm-phase4-agents]] — план Фазы 4 (шаги 1-2 ✅)
|
- [[personal/plans/balda-mm-phase4-agents]] — план Фазы 4 (шаги 1-2 ✅)
|
||||||
- [[personal/tech/macos-docker-virtiofs-mount-limits]] — почему bind-mount вместо symlink
|
- [[personal/tech/macos-docker-virtiofs-mount-limits]] — почему bind-mount вместо symlink
|
||||||
- [[personal/projects/balda/balda-mm-delegation-plan]] — общий план делегирования
|
- [[personal/projects/balda/balda-mm-delegation-plan]] — общий план делегирования
|
||||||
|
- `~/Developer/agents-bridge` — код моста
|
||||||
|
|
||||||
## История
|
## История
|
||||||
- **2026-09-26**: создан, поднят на :8790, протестирован (auth 401, идемпотентность,
|
- **2026-09-26**: создан, поднят на :8790, протестирован (auth 401, идемпотентность,
|
||||||
сквозной вызов CLI). Не в launchd. Логин CLI — за Alex.
|
сквозной вызов CLI). Логин CLI — за Alex.
|
||||||
|
- **2026-09-26** (продолжение): переведён под launchd (`com.agents.bridge`,
|
||||||
|
`RunAtLoad` + `KeepAlive`, pid 3063), добавлен `EnvironmentVariables.PATH`.
|
||||||
|
Шаг 3 разблокирован; проверка `KeepAlive` при падении не выполнена (отклонён `kill -9`).
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
# Секция «Agents» в Eagle Dashboard — реализация
|
||||||
|
|
||||||
|
> Создан 2026-09-26. Шаг 4 Фазы 4 [[personal/plans/balda-mm-phase4-agents]].
|
||||||
|
> Статус: **код написан, НЕ активирован** — дашборд работает на старом байткоде.
|
||||||
|
> Активация = `__pycache__` + `launchctl kickstart -k` (апрув не получен).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Расположение
|
||||||
|
|
||||||
|
| Что | Путь |
|
||||||
|
|---|---|
|
||||||
|
| Роутер | `~/Developer/eagle-dash/agents.py` (новый файл) |
|
||||||
|
| Подключение | `~/Developer/eagle-dash/main.py` → `app.include_router(agents_router)` |
|
||||||
|
| Реестр сервисов | `~/Developer/eagle-dash/services.yaml` → запись `agents_bridge` |
|
||||||
|
| Бэкап реестра | `services.yaml.bak-agents-20260926-185641` |
|
||||||
|
| Манифест монтирований | `~/Docker/balda-executor/agents-mounts.yaml` |
|
||||||
|
| Генератор override | `~/Docker/balda-executor/tools/render-mounts.py` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API (`prefix=/api/agents`)
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/agents → список ~/agents + манифест + статус контейнера
|
||||||
|
POST /api/agents {name} → mkdir ~/agents/<name>
|
||||||
|
POST /api/agents/{name}/mount {host_path, mode} → запись в манифест + recreate
|
||||||
|
DELETE /api/agents/{name}/mount → отвязать (файлы на диске целы)
|
||||||
|
POST /api/agents/{name}/hardlink {file, link_name} → ln -F с проверкой st_dev
|
||||||
|
GET /api/agents/{name}/usage → du -sk
|
||||||
|
GET /api/agents/mounts → содержимое манифеста
|
||||||
|
POST /api/agents/mounts/render → перегенерировать override без recreate
|
||||||
|
GET /api/agents/bridge → состояние моста :8790 + `launchctl list`
|
||||||
|
```
|
||||||
|
|
||||||
|
## Что секция НЕ делает (осознанно)
|
||||||
|
|
||||||
|
| Запрещено | Почему |
|
||||||
|
|---|---|
|
||||||
|
| Удалять файлы в `~/agents` | правило Alex: только отвязка от контейнера |
|
||||||
|
| Создавать symlink на внешний путь | в Docker битый (virtiofs) — см. [[personal/tech/macos-docker-virtiofs-mount-limits]] |
|
||||||
|
| Hardlink на каталог | ядро запрещает; вернётся ошибка с объяснением |
|
||||||
|
| Hardlink через границу ФС | `st_dev` сравнивается до `ln`, при различии — 400 с текстом про `EXDEV` |
|
||||||
|
| Живой `mount --bind` | на macOS не виден контейнеру — только `docker-compose up -d` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ключевые решения реализации
|
||||||
|
|
||||||
|
### Роутер в отдельном файле, не в `main.py`
|
||||||
|
|
||||||
|
`main.py` — 889 строк; секция «Agents» вынесена в `agents.py` (~430 строк) как
|
||||||
|
`APIRouter(prefix="/api/agents")`. Подключение одной строкой в `main.py` — минимизирует
|
||||||
|
правку большого файла и не ломает существующие роуты.
|
||||||
|
|
||||||
|
### Валидация `host_path` — белый список запретов
|
||||||
|
|
||||||
|
Путь должен быть абсолютным, существующим каталогом, и НЕ одним из:
|
||||||
|
`/`, `/Users`, `/Users/admin`, `/Users/admin/agents`. Имя агента — `^[a-z0-9][a-z0-9._-]{0,62}$`.
|
||||||
|
Это защита от монтирования `~` целиком в контейнер.
|
||||||
|
|
||||||
|
### Откат манифеста при провале
|
||||||
|
|
||||||
|
`mount`/`unmount` пишут манифест → вызывают `render-mounts.py` → `docker-compose up -d`.
|
||||||
|
Если любой шаг падает, **манифест возвращается к прежнему состоянию** (`_write_manifest(mounts)`),
|
||||||
|
чтобы файл не расходился с реально смонтированным.
|
||||||
|
|
||||||
|
### Запись манифеста — атомарная
|
||||||
|
|
||||||
|
`tmp` → `replace()`. Заголовок с правилами перезаписывается константой `MANIFEST_HEADER`,
|
||||||
|
чтобы пояснения не терялись при правках из UI.
|
||||||
|
|
||||||
|
### `du -sk` вместо обхода дерева
|
||||||
|
|
||||||
|
Список агентов считает размер через `rglob` (для UI), а `/{name}/usage` — через
|
||||||
|
`du -sk`. Выбор `du` для usage: не блокирует event loop на больших каталогах дольше
|
||||||
|
необходимого и не следует по монтированиям вглубь без нужды.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Питфоллы
|
||||||
|
|
||||||
|
### 1. Дашборд кеширует байткод
|
||||||
|
|
||||||
|
Правки `.py` не видны до перезапуска. Порядок:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rm -rf ~/Developer/eagle-dash/__pycache__
|
||||||
|
launchctl kickstart -k gui/$(id -u)/com.eagle.dashboard
|
||||||
|
```
|
||||||
|
|
||||||
|
`kickstart` **без** `-k` переиспользует процесс и новый код не подхватывает.
|
||||||
|
|
||||||
|
### 2. `docker compose` vs `docker-compose`
|
||||||
|
|
||||||
|
На этой машине compose — отдельный бинарь `/opt/homebrew/bin/docker-compose` (v5.1.3).
|
||||||
|
`docker compose` → `docker: unknown command`. В `agents.py` все вызовы через `docker-compose`.
|
||||||
|
|
||||||
|
### 3. launchd не читает shell `PATH`
|
||||||
|
|
||||||
|
Дашборд под launchd имеет `PATH=/usr/bin:/bin`. Поэтому:
|
||||||
|
- `lsof` вызывается полным путём `/usr/sbin/lsof`;
|
||||||
|
- `agents.py` из `PATH` дёргает `docker`, `docker-compose`, `du`, `launchctl` — на
|
||||||
|
`/usr/bin` и `/bin` из них попадают только `du` и `launchctl`;
|
||||||
|
`docker`/`docker-compose` из `/opt/homebrew/bin` **под launchd не найдутся**.
|
||||||
|
|
||||||
|
⚠️ **Не проверено живьём** (дашборд не перезапущен). Если mount/unmount будут возвращать
|
||||||
|
500 с текстом «команда не найдена» — заменить в `agents.py` литералы на абсолютные пути
|
||||||
|
`/opt/homebrew/bin/docker-compose` и `/opt/homebrew/bin/docker`.
|
||||||
|
|
||||||
|
### 4. `services.yaml` — запись `agents_bridge`
|
||||||
|
|
||||||
|
Добавлена в начало секции `services:` (файл отсортирован по `id`, `agents_bridge` первый):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
agents_bridge:
|
||||||
|
access: localhost
|
||||||
|
category: orchestration
|
||||||
|
critical: false
|
||||||
|
health:
|
||||||
|
type: http
|
||||||
|
url: http://127.0.0.1:8790/healthz
|
||||||
|
icon: 🌉
|
||||||
|
label: com.agents.bridge
|
||||||
|
log: /Users/admin/Library/Logs/agents-bridge.err.log
|
||||||
|
name: Agents Bridge
|
||||||
|
port: 8790
|
||||||
|
type: daemon
|
||||||
|
working_dir: /Users/admin/Developer/agents-bridge
|
||||||
|
```
|
||||||
|
|
||||||
|
⚠️ `type: daemon` заставит **supervisor дашборда** взять сервис под управление и
|
||||||
|
запустить свой процесс — а мост уже управляется собственным launchd (`com.agents.bridge`).
|
||||||
|
Возможен конфликт за порт 8790. Ревизия при активации: либо `type: system` +
|
||||||
|
`health.type: launchctl` (label `com.agents.bridge`), либо `type: none`.
|
||||||
|
`health.type: http` на `:8790/healthz` работает в любом варианте.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Активация (одна команда)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rm -rf ~/Developer/eagle-dash/__pycache__ && \
|
||||||
|
launchctl kickstart -k gui/$(id -u)/com.eagle.dashboard
|
||||||
|
```
|
||||||
|
|
||||||
|
Затем:
|
||||||
|
```bash
|
||||||
|
curl -s http://127.0.0.1:8880/api/agents | /usr/bin/jq
|
||||||
|
open "http://127.0.0.1:8880/?tab=agents"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Связанное
|
||||||
|
|
||||||
|
- [[personal/plans/balda-mm-phase4-agents]] — план Фазы 4
|
||||||
|
- [[personal/tech/agents-bridge]] — мост :8790
|
||||||
|
- [[personal/tech/macos-docker-virtiofs-mount-limits]] — ограничения macOS Docker
|
||||||
|
- скилл `eagle-dashboard` — архитектура дашборда, вкладки, кеш байткода
|
||||||
Reference in new issue
Block a user