[2026-09-26] eagle: personal/plans/balda-mm-phase4-agents.md personal/projects/balda/balda-mm-delegation-plan.md personal/tech/cloudflare-tunnel-ssh-published-route.md
This commit is contained in:
1 parent
5c939ed811
commit
3edbddf512
3 files changed
+152
-277
No files matched your search
@@ -1,299 +1,155 @@
|
||||
# Фаза 4 — исполнитель на Mac + `~/agents` + секция «Agents» в Eagle Dashboard
|
||||
|
||||
> Создан 2026-09-26. Статус: **ПЛАН — ожидает апрува Alex, работа не начата.**
|
||||
> Часть [[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).
|
||||
|
||||
---
|
||||
|
||||
## 0. Решения Alex (2026-09-26, этот заход)
|
||||
## 1. Решения
|
||||
|
||||
| # | Вопрос | Решение |
|
||||
|---|---|---|
|
||||
| 1 | Папка для проектов исполнителя | **`~/agents`** |
|
||||
| 2 | Подтверждения | **Full access** → `permissions.mode: allow_all` |
|
||||
| 3 | Управление hardlink/symlink в `~/agents` | **Новая секция в Eagle Dashboard** |
|
||||
| 1 | Папка для проектов исполнителя | `~/agents` |
|
||||
| 2 | Подтверждения | Full access → `permissions.mode: allow_all` |
|
||||
| 3 | Управление линками в `~/agents` | Новая секция «Agents» в Eagle Dashboard |
|
||||
| 4 | Auth CLI (claude/codex) | на хосте, `~/cli-auth`, **вне** `~/agents` |
|
||||
| 5 | Сеть Kraken → Mac | Cloudflare Tunnel (существующий cloudflared на Mac) |
|
||||
|
||||
---
|
||||
|
||||
## 1. 🔴 ПРОВЕРЕНО ЖИВЫМ ТЕСТОМ: симлинк в Docker НЕ работает
|
||||
## 2. Ограничения macOS Docker (проверено тестами)
|
||||
|
||||
Alex спросил: «Если в папку симлинк кинуть вовне — докер это прочтет?»
|
||||
**Ответ: НЕТ.** Проверено на живом Docker (alpine:3.20, linux/arm64), не по памяти.
|
||||
| Ограничение | Итог |
|
||||
|---|---|
|
||||
| Симлинк на внешний путь | битый в контейнере — цель резолвится от корня контейнера |
|
||||
| Live `mount --bind` внутри VM | контейнер не видит (virtiofs строит маппинг на старте) |
|
||||
| Сетевая шара SMB/NFS | в контейнер не протянуть (тот же virtiofs) |
|
||||
| Hardlink на каталог | физически невозможен (ядро запрещает) |
|
||||
| Hardlink через границу ФС | `EXDEV` |
|
||||
|
||||
```
|
||||
host: /tmp/symtest/outside/file.txt = "REAL CONTENT ON HOST"
|
||||
bind: -v /tmp/symtest/bind:/mnt (внутри /mnt/target -> /tmp/symtest/outside)
|
||||
**Следствие:** внешние проекты подключаются **только bind-mount'ом через `-v`** +
|
||||
`docker compose up -d` (2-3 сек). Hardlink — только для файлов внутри одного тома.
|
||||
|
||||
контейнер: cat /mnt/target/file.txt
|
||||
→ cat: can't open '/mnt/target/file.txt': No such file or directory
|
||||
```
|
||||
|
||||
Сам симлинк внутри виден (`lrwxrwxrwx link -> /outside`), но **цель резолвится
|
||||
относительно корня контейнера**, где этого пути нет. Docker монтирует только реальный
|
||||
путь — симлинки не разворачивает и не «протаскивает».
|
||||
|
||||
### Следствие для архитектуры
|
||||
|
||||
```
|
||||
Mac Контейнер-исполнитель
|
||||
~/agents/ /agents (bind-mount, rw)
|
||||
project-x/ ← РЕАЛЬНАЯ папка → видна
|
||||
src -> ~/Developer/proj-x ← СИМЛИНК → ✗ БИТЫЙ
|
||||
```
|
||||
|
||||
**Правило:** внутрь `~/agents` — только **реальные каталоги**. Симлинки не работают
|
||||
на внешние цели, hardlink'ом каталог подключить нельзя (см. §2) → внешние проекты —
|
||||
только **bind-mount**.
|
||||
Детали механизма: [[personal/tech/macos-docker-virtiofs-mount-limits]].
|
||||
|
||||
---
|
||||
|
||||
## 1a. 🔴 ПРОВЕРЕНО ЖИВЫМ ТЕСТОМ: live `mount --bind` на macOS Docker НЕ работает
|
||||
|
||||
Alex предложил: если bind на ходу возможен — делать и live, и в compose, чтобы контейнер
|
||||
не пересоздавать. **Проверено тремя прогонами — на macOS невозможно.**
|
||||
|
||||
```
|
||||
VM (nsenter -t 1 -m): /tmp/bindtest2/agents/existing/f.txt = "DATA2" ✓ файл есть
|
||||
mount --bind ... → BIND_OK
|
||||
контейнер (без рестарта): cat /agents/existing/f.txt
|
||||
→ No such file or directory ✗ не видит
|
||||
```
|
||||
|
||||
Проверено в трёх вариантах — результат одинаков:
|
||||
1. Каталог создан **после** старта контейнера + live bind → в контейнере пусто.
|
||||
2. Ожидание 5 с (гипотеза «кэш атрибутов virtiofs») → пусто.
|
||||
3. Каталог существовал **до** старта контейнера + bind поверх него → **пусто**.
|
||||
|
||||
**Причина:** на macOS Docker Desktop отдаёт контейнеру файлы не через Linux `mount`,
|
||||
а через **virtiofs** — отдельный демон, который строит маппинг путей **в момент старта
|
||||
контейнера**. `mount`, сделанный внутри VM через `nsenter`, для virtiofs невидим:
|
||||
изнутри VM всё корректно, до контейнера не доходит. Это не кэш — рестарт не поможет.
|
||||
|
||||
📌 **Следствие:** live-bind был бы возможен на Linux-хосте (там Docker нативный).
|
||||
На macOS — **только через `-v` в compose + reproduce контейнера**.
|
||||
`docker compose up -d` с новым `-v` = 2-3 сек, `--privileged` не нужен.
|
||||
|
||||
📌 **Тот же корень, что и с симлинками:** virtiofs-маппинг понимает только пути
|
||||
**мира macOS**, известные ему на старте. Всё, что появляется в VM позже (`mount`,
|
||||
шара, новый каталог) — в контейнер не проецируется.
|
||||
|
||||
### Сетевые шары (SMB/NFS) — тот же вывод
|
||||
|
||||
Смонтировать шару в macOS и дать `-v /Volumes/share:/data` **не сработает** (virtiofs
|
||||
не видит `mount_smbfs`-точку). Единственный рабочий вариант — mount внутрь VM через
|
||||
`nsenter`, но он не переживает рестарт Docker и требует `--privileged`.
|
||||
**Рекомендация: не тянуть шару в контейнер.** Если данные на NAS — `rsync` в `~/agents`
|
||||
по требованию вместо прямого монтирования.
|
||||
|
||||
---
|
||||
|
||||
## 2. Symlink / hardlink / bind-mount — что выбрать
|
||||
|
||||
| Способ | В Docker | Может пересечь ФС | Может указать на файл вне mount | Итог |
|
||||
|---|---|---|---|---|
|
||||
| **symlink** | ✗ битый | да | ✗ нет | ❌ не использовать для внешних целей |
|
||||
| **hardlink** | ✓ работает | **✗ нет** | ✗ нет | ✓ для файлов внутри `~/agents` |
|
||||
| **bind-mount** | ✓ работает | да | да | ✓ для внешних каталогов |
|
||||
| **APFS clonefile** | ✓ (реальная копия) | да | да | ✓ для «копии проекта» без расхода диска |
|
||||
|
||||
### Ключевое ограничение hardlink
|
||||
|
||||
Hardlink **не может пересечь границу файловой системы** и **не может ссылаться на
|
||||
каталог** (только на файл, и только на macOS — с `-F`). То есть замена симлинка
|
||||
«проект целиком» через hardlink технически невозможна, если проект лежит
|
||||
на другом томе или это директория.
|
||||
|
||||
**Вывод:** «линкование проекта извне» = **bind-mount**, а не линк. Линки — для
|
||||
внутренней организации одного тома.
|
||||
|
||||
📌 **Прямо отвечая на исходный замысел Alex:** hardlink'ом **нельзя** подключить
|
||||
проект-каталог извне — физически невозможно (каталоги не линкуются, ФС не пересекается).
|
||||
Поэтому в целевой архитектуре внешние проекты висят на **bind-mount'ах**
|
||||
(`~/Developer/<proj>:/agents/<name>`), а секция Dashboard управляет именно манифестом
|
||||
`agents-mounts.yaml`. Hardlink-операции остаются как вспомогательный инструмент
|
||||
для файлов внутри одного тома.
|
||||
|
||||
---
|
||||
|
||||
## 3. Целевая архитектура
|
||||
## 3. Архитектура
|
||||
|
||||
```
|
||||
Mattermost (Alex)
|
||||
│ «почини баг X в project-y»
|
||||
▼
|
||||
Балда-оркестратор (Kraken, DeepSeek, mode=allow_all)
|
||||
│ 1. читает Obsidian, формирует ТЗ
|
||||
│ 2. /goalkeeper → work → validate
|
||||
Балда-оркестратор (Kraken, DeepSeek, allow_all)
|
||||
│ читает Obsidian, формирует ТЗ, /goalkeeper → work → validate
|
||||
▼
|
||||
Мост (Mac, FastAPI, :8790) ← новый сервис, регистрируется в Eagle Dashboard
|
||||
│ HTTP + shared secret (WireGuard-хаб уже есть)
|
||||
│ идемпотентность + таймаут + лимит 1-2 параллельных задач
|
||||
Cloudflare Tunnel: agents-bridge.qentra.top → localhost:8790
|
||||
│ (существующий cloudflared на Mac, PID + LaunchAgent)
|
||||
▼
|
||||
Мост (Mac, FastAPI :8790)
|
||||
│ idempotency key, таймаут, лимит 1-2 параллельных задач, shared secret
|
||||
▼
|
||||
Контейнер-исполнитель (Mac, Docker)
|
||||
│ -v ~/agents:/agents:rw ← рабочая область, локальный диск
|
||||
│ -v ~/Developer/<proj>:/agents/<name> ← внешние проекты (bind, не линк)
|
||||
│ -v ~/cli-auth/claude:/root/.claude:rw ← auth CLI на ХОСТЕ (вне /agents!)
|
||||
│ -v ~/cli-auth/codex:/root/.codex:rw ← auth CLI на ХОСТЕ
|
||||
│ -v ~/agents:/agents:rw
|
||||
│ -v ~/Developer/<proj>:/agents/<name>:rw ← внешние проекты
|
||||
│ -v ~/cli-auth/claude:/root/.claude:rw ← auth на хосте
|
||||
│ -v ~/cli-auth/codex:/root/.codex:rw ← auth на хосте
|
||||
│ Node 24 + codex + claude + git
|
||||
│ CLAUDE_CODE_OAUTH_TOKEN из .env
|
||||
▼
|
||||
возврат: diff + вывод тестов + коммит → Балда → MM
|
||||
diff + тесты + коммит → Балда → Mattermost
|
||||
```
|
||||
|
||||
### Данные CLI — на хосте, не в образе
|
||||
### Auth CLI на хосте
|
||||
|
||||
🔴 Требование Alex: **данные codex/claude живут в хостовой папке**, чтобы не ушли
|
||||
при пересборке образа, обновлении CLI или пересоздании контейнера.
|
||||
|
||||
**Расположение — `~/cli-auth`, ВНЕ `~/agents`** (решение принято 2026-09-26):
|
||||
агент-исполнитель монтирует `/agents` на запись, и если auth лежит внутри, агент
|
||||
получает доступ к своим же токенам. Вынос наружу закрывает это.
|
||||
|
||||
| Данные | Хост-путь | В контейнере | Права |
|
||||
| Данные | Хост | В контейнере | Права |
|
||||
|---|---|---|---|
|
||||
| Claude auth / сессии / конфиг | `~/cli-auth/claude` | `/root/.claude` | 0700 |
|
||||
| Codex auth / конфиг | `~/cli-auth/codex` | `/root/.codex` | 0700 |
|
||||
| OAuth-токен Claude | `~/Docker/balda-executor/.env` | env `CLAUDE_CODE_OAUTH_TOKEN` | 0600 |
|
||||
| Ключ провайдера codex (если нужен) | `~/Docker/balda-executor/.env` | env | 0600 |
|
||||
| Claude auth | `~/cli-auth/claude` | `/root/.claude` | 0700 |
|
||||
| Codex auth | `~/cli-auth/codex` | `/root/.codex` | 0700 |
|
||||
| OAuth-токен Claude | `~/Docker/balda-executor/.env` | env | 0600 |
|
||||
|
||||
**Почему так:**
|
||||
- пересборка образа `docker build` **не трогает** bind-mounted каталоги → auth переживает
|
||||
всё; новый образ поднимается уже залогиненным;
|
||||
- обновление версии CLI внутри образа не сбрасывает сессию;
|
||||
- откат образа (`--force-recreate` на старый тег) не требует повторного логина;
|
||||
- токен не попадает в слои образа → не утечёт при `docker save`/шаринге;
|
||||
- агент **не видит** свои токены (они вне смонтированного `/agents`).
|
||||
|
||||
⚠️ `~/cli-auth` обязан быть на **том же томе**, что и корень Docker-маппинга, иначе
|
||||
проект упрётся в virtiofs-ограничения (см. §1a).
|
||||
Пересборка образа и обновление CLI auth не сбрасывают. Токены вне `/agents` → агент их не видит.
|
||||
|
||||
---
|
||||
|
||||
## 4. Секция «Agents» в Eagle Dashboard
|
||||
|
||||
### Где живёт код (проверено 2026-09-26)
|
||||
Правки: `main.py` (роуты) · `templates/index.html` (вкладка `?tab=agents`) ·
|
||||
`services.yaml` (запись `agents-bridge`, `health.type: http`) · `health.py`.
|
||||
|
||||
| Файл | Строк | Что править |
|
||||
|---|---|---|
|
||||
| `~/Developer/eagle-dash/main.py` | 889 | новые `@app.get/post/delete` роуты |
|
||||
| `~/Developer/eagle-dash/templates/index.html` | 1248 | новая вкладка + Alpine.js стейт |
|
||||
| `~/Developer/eagle-dash/health.py` | 181 | health-чек моста (`type: http`) |
|
||||
| `~/Developer/eagle-dash/services.yaml` | 6.5 КБ | регистрация сервиса `agents-bridge` |
|
||||
|
||||
**Существующие вкладки:** `services` · `crons` · `pages` · `files`
|
||||
(роутинг через `?tab=`, `setUrlTab()`, `activeTab` в Alpine).
|
||||
|
||||
### Что делает новая секция
|
||||
|
||||
1. **Список содержимого `~/agents`** — по каждому проекту: имя, тип (реальная папка /
|
||||
mount / битый symlink), размер, дата изменения, **статус**.
|
||||
2. **Создать проект** — `mkdir ~/agents/<name>`.
|
||||
3. **Подключить внешний проект** — запись в `~/Developer/agents-mounts.yaml`
|
||||
(`name` + `host_path`) → `docker compose up -d` (**2-3 сек, без privileged**).
|
||||
⚠️ **Только bind-mount.** Ни symlink (битый), ни hardlink (каталоги не линкуются).
|
||||
⚠️ Live `mount --bind` **недоступен на macOS** (см. §1a) — recreate обязателен.
|
||||
4. **Hardlink-операции** — `ln -F <file> ~/agents/<proj>/<name>` (только **файлы**,
|
||||
только внутри одного тома; в UI показывать `st_dev` и блокировать при разных ФС).
|
||||
5. **Удалить из контейнера** — убрать из `agents-mounts.yaml` + `docker compose up -d`
|
||||
⚠️ **Файлы на диске не трогать** — только отвязка от контейнера (см. Core Rules).
|
||||
6. **Открыть терминал** — ссылка на существующую вкладку `files`.
|
||||
|
||||
### API-контракт (черновик)
|
||||
|
||||
```
|
||||
GET /api/agents → [{name, kind, size, mtime, link_valid, target}]
|
||||
POST /api/agents {name} → mkdir ~/agents/<name>
|
||||
POST /api/agents/{name}/mount {host_path} → добавить bind-mount + recreate
|
||||
DELETE /api/agents/{name}/mount → убрать bind-mount + recreate (файлы целы)
|
||||
POST /api/agents/{name}/hardlink {file, name} → ln -F (с проверкой st_dev)
|
||||
GET /api/agents/{name}/usage → размер, место на томе
|
||||
```
|
||||
|
||||
### Чего секция НЕ делает
|
||||
|
||||
- **Не удаляет** содержимое `~/agents` — только отвязывает от контейнера.
|
||||
- **Не убивает** процессы — lifecycle контейнера через существующий supervisor.
|
||||
- **Не создаёт symlink** на внешние цели — вместо них bind-mount.
|
||||
|
||||
---
|
||||
|
||||
## 5. Система подтверждений — итог разведки
|
||||
|
||||
### Как устроено в Balda (апстрим, `docs/architecture/interactive-questions.md`)
|
||||
|
||||
Три режима (`permissioncmd.Mode`):
|
||||
|
||||
| Режим | Поведение |
|
||||
| Функция | Что делает |
|
||||
|---|---|
|
||||
| `allow_all` | никого не спрашивает — **выбран Alex** |
|
||||
| `ask` | ADK-callback **блокирует турн**, вопрос уходит в чат, ждёт ответа, таймаут fail-closed, продолжает через `ResumeTarget` |
|
||||
| `deny_all` | всё запрещено |
|
||||
| Список | содержимое `~/agents`: имя, тип, размер, mtime, статус линка |
|
||||
| Создать | `mkdir ~/agents/<name>` |
|
||||
| Подключить внешний | запись в `agents-mounts.yaml` → `compose up -d` |
|
||||
| Hardlink | `ln -F` для файлов, проверка `st_dev` |
|
||||
| Отключить | убрать из манифеста + `up -d`; **файлы на диске целы** |
|
||||
| Терминал | ссылка на вкладку `files` |
|
||||
|
||||
🔴 **`ask` сейчас работает только для Telegram и Slackagent** — прямая цитата
|
||||
`interactive-questions.md`: *«`ask` is supported for Telegram and Slackagent and
|
||||
fails closed elsewhere»*. Для Mattermost `ask` **фейлится закрыто**.
|
||||
**API:**
|
||||
```
|
||||
GET /api/agents → список
|
||||
POST /api/agents {name} → mkdir
|
||||
POST /api/agents/{name}/mount {host_path} → bind + recreate
|
||||
DELETE /api/agents/{name}/mount → отвязать (файлы целы)
|
||||
POST /api/agents/{name}/hardlink {file, name} → ln -F со st_dev
|
||||
GET /api/agents/{name}/usage → размер
|
||||
```
|
||||
|
||||
### Что это значит для сценария «MCP просит линк → Alex подтверждает из чата»
|
||||
|
||||
Прямой путь **сейчас невозможен** поверх MM. Варианты:
|
||||
|
||||
| Вариант | Объём | Ложится куда |
|
||||
|---|---|---|
|
||||
| A. Дописать `ask` в MM | ~1-2 дня | ветка `feat/mattermost-channel` → **расширение PR #8** |
|
||||
| B. Апрув в мосте (свой мини-эндпоинт + очередь) | ~0.5 дня | FastAPI-мост Фазы 4 |
|
||||
|
||||
**С Alex выбран `allow_all`** — этим заходом подтверждения не нужны. Пункт
|
||||
оставлен как задел: когда `ask` понадобится, начинать с варианта **B** (дешевле,
|
||||
не зависит от ревью апстрима).
|
||||
**Не делает:** не удаляет файлы, не убивает процессы, не создаёт symlink на внешние цели.
|
||||
|
||||
---
|
||||
|
||||
## 6. План работ (по шагам)
|
||||
## 5. Подтверждения
|
||||
|
||||
### Шаг 1 — `~/agents` + `~/cli-auth` + контейнер-исполнитель (~2 ч)
|
||||
1. `mkdir ~/agents` (права `admin:staff`); `~/cli-auth/{claude,codex}` (0700) — **вне** `~/agents`.
|
||||
2. `~/Docker/balda-executor/Dockerfile` — Node 24 + `codex` + `claude` + `git`.
|
||||
3. `~/Docker/balda-executor/docker-compose.yml`:
|
||||
```yaml
|
||||
volumes:
|
||||
- /Users/admin/agents:/agents:rw
|
||||
- /Users/admin/cli-auth/claude:/root/.claude:rw # auth на хосте, вне /agents
|
||||
- /Users/admin/cli-auth/codex:/root/.codex:rw # auth на хосте, вне /agents
|
||||
```
|
||||
+ `agents-mounts.yaml` подключается генератором override-файла
|
||||
(`compose.override.yaml`), чтобы composefile не правился скриптом вручную.
|
||||
🔴 только `-v` + `docker compose up -d` — live-bind на macOS невозможен (§1a).
|
||||
4. Авторизация один раз: `claude setup-token` на Mac → токен в
|
||||
`~/Docker/balda-executor/.env` (0600); `codex` — свой flow в `~/cli-auth/codex`.
|
||||
5. Проверка: смонтированные auth видны (`docker exec ... ls /root/.claude`),
|
||||
`claude --version` + `codex --version` работают, повторный `up -d` остаётся залогинен.
|
||||
Режимы Balda: `allow_all` (выбран) · `ask` · `deny_all`.
|
||||
`ask` поддержан **только для Telegram и Slackagent**; для Mattermost фейлится закрыто.
|
||||
Сценарий «подтвердить из чата» требует: **A** — дописать `ask` в MM (~1-2 дня, расширение PR #8),
|
||||
либо **B** — апрув в мосте (~0.5 дня). При `allow_all` не нужно; начинать с B, когда понадобится.
|
||||
|
||||
### Шаг 2 — Мост на Mac (~3-4 ч)
|
||||
1. `~/Developer/agents-bridge/` — FastAPI, порт **8790**.
|
||||
2. Эндпоинты: `POST /task` (idempotency key), `GET /task/{id}`, `GET /healthz`.
|
||||
3. Запуск CLI headless в контейнере, возврат diff + тестов.
|
||||
4. Лимит параллельных задач: **1-2** (риск «unusual traffic» — см. §Риски плана Фаз).
|
||||
5. Shared secret в заголовке + проверка source IP (WireGuard-подсеть).
|
||||
---
|
||||
|
||||
### Шаг 3 — Сеть Kraken → Mac (~1 ч)
|
||||
1. WireGuard-туннель (хаб на VPS уже есть — см. скилл `wireguard-vps-hub`).
|
||||
2. Проверка: `curl` с Kraken на Mac `:8790/healthz`.
|
||||
## 6. Шаги
|
||||
|
||||
### Шаг 4 — Секция «Agents» в Eagle Dashboard (~3-4 ч)
|
||||
1. `main.py` — 6 роутов из §4.
|
||||
2. `templates/index.html` — вкладка `agents` + `?tab=agents`.
|
||||
3. `services.yaml` — запись `agents-bridge` (`health.type: http`, url `:8790/healthz`).
|
||||
4. `__pycache__` + `launchctl kickstart -k gui/$(id -u)/com.eagle.dashboard`.
|
||||
### Артефакты, уже созданные на диске (2026-09-26)
|
||||
|
||||
### Шаг 5 — Связка Балда → мост (~2 ч)
|
||||
1. MCP-инструмент в Балде (или webhook) → `POST /task`.
|
||||
2. Проверка на задаче-пустышке: `echo ok > /agents/test/probe.txt`.
|
||||
Созданы **до** подтверждения плана — статус «черновик», при старте сверить и доделать:
|
||||
|
||||
### Шаг 6 — Приёмка Фазы 5 (~1 день)
|
||||
1. Сквозной тест **от аккаунта Alex**: `@balda почини X` → diff в MM.
|
||||
2. Расходы: DeepSeek-токены + подписочные лимиты.
|
||||
3. Задокументировать: как работает, где логи, как откатить.
|
||||
| Путь | Что |
|
||||
|---|---|
|
||||
| `~/agents` | рабочая область (drwxr-xr-x, `admin:staff`) |
|
||||
| `~/cli-auth/{claude,codex}` | auth CLI, 0700, **вне** `~/agents` |
|
||||
| `~/Docker/balda-executor/Dockerfile` | Node 24 + claude + codex + git, HEALTHCHECK |
|
||||
| `~/Docker/balda-executor/docker-compose.yml` | 4 тома, сеть `executor-net`, лимит 8G |
|
||||
| `~/Docker/balda-executor/.env` | ⏳ не создан (токен) |
|
||||
|
||||
`docker-compose.yml` ссылается на `compose.override.yaml`, который генерирует
|
||||
`tools/render-mounts.py` из `agents-mounts.yaml` — **override вручную не править**.
|
||||
Ни образ не собран, ни контейнер не запущен.
|
||||
|
||||
**1. `~/agents` + `~/cli-auth` + контейнер (~2 ч)**
|
||||
`mkdir ~/agents`, `~/cli-auth/{claude,codex}` 0700 → `~/Docker/balda-executor/Dockerfile`
|
||||
(Node 24 + claude + codex + git) → `docker-compose.yml` с 4 томами → `claude setup-token` в `.env` 0600 →
|
||||
проверка `claude --version`, `codex --version`, повторный `up -d` остаётся залогинен.
|
||||
|
||||
**2. Мост (~3-4 ч)**
|
||||
`~/Developer/agents-bridge/`, FastAPI :8790, `POST /task` (idempotency), `GET /task/{id}`, `GET /healthz`;
|
||||
запуск CLI headless в контейнере; лимит 1-2 задач; shared secret.
|
||||
|
||||
**3. Сеть (~1 ч)**
|
||||
Cloudflare Tunnel: добавить published route `agents-bridge.qentra.top` → HTTP `localhost:8790` +
|
||||
Access Application. Токен и процесс cloudflared не трогаются. Проверка: `curl` извне на `/healthz`.
|
||||
⚠️ hostname — **одноуровневый** (двухуровневые дают TLS handshake failure).
|
||||
|
||||
**4. Секция «Agents» (~3-4 ч)**
|
||||
6 роутов + вкладка + запись в `services.yaml` → `__pycache__` + `launchctl kickstart -k`.
|
||||
|
||||
**5. Связка Балда → мост (~2 ч)**
|
||||
MCP-инструмент в Балде → `POST /task`; проба `echo ok > /agents/test/probe.txt`.
|
||||
|
||||
**6. Приёмка (~1 день)**
|
||||
Сквозной тест **от аккаунта Alex** в MM: `@balda` → задача → diff в ответе.
|
||||
Замер расходов (DeepSeek + подписка), документация отката.
|
||||
|
||||
---
|
||||
|
||||
@@ -301,43 +157,25 @@ fails closed elsewhere»*. Для Mattermost `ask` **фейлится закры
|
||||
|
||||
| Риск | Оценка | Митигация |
|
||||
|---|---|---|
|
||||
| Симлинк в `~/agents` на внешний путь → битый | **подтверждён** | bind-mount вместо линка; UI помечает битые линки |
|
||||
| Live `mount --bind` на macOS → контейнер не видит | **подтверждён** | только `-v` + `compose up -d`; live-bind вычеркнут из плана |
|
||||
| Сетевую шару (SMB/NFS) в контейнер протянуть нельзя | **подтверждён** | не тянуть: `rsync` в `~/agents` по требованию |
|
||||
| Hardlink через границу ФС → `EXDEV` | высокий | проверять `st_dev` перед `ln`; UI блокирует |
|
||||
| Бан подписочного аккаунта за «unusual traffic» | средний | 1-2 параллельных задачи, официальный бинарь, без прокси токена |
|
||||
| Полный доступ (`allow_all`) + MCP → агент может снести `~/agents` | **средний** | бэкапы + `git rm`-семантика вместо `rm`; правки только через bind-mount-манифест |
|
||||
| Агент читает/пишет свои же auth-данные | средний | `~/cli-auth` **вне** `~/agents` → агент их не видит |
|
||||
| Мост открыт наружу | высокий | только WireGuard-подсеть + shared secret; не публиковать порт в LAN |
|
||||
| Мост опубликован в интернет | высокий | Access Application + shared secret |
|
||||
| Полный доступ агента к `~/agents` | средний | бэкапы; `git rm`-семантика вместо `rm` |
|
||||
| Бан подписки за «unusual traffic» | средний | 1-2 параллельных задачи, официальный бинарь, без прокси токена |
|
||||
| Hardlink через ФС → `EXDEV` | высокий | проверка `st_dev` в UI |
|
||||
| Mac спит → туннель и мост недоступны | средний | задачи в durable-очереди Балды, идемпотентность |
|
||||
|
||||
---
|
||||
|
||||
## 8. Открыто (требует ответа Alex при старте)
|
||||
## 8. Открыто
|
||||
|
||||
1. Порт моста — **8790** (взят свободный; проверить `lsof -i :8790` перед стартом).
|
||||
2. Провайдер исполнителя — **claude** (подписка уже залогинена) или **codex**?
|
||||
3. Нужен ли `ask`-апрув в будущем, или `allow_all` достаточно постоянно?
|
||||
|
||||
📌 Решено 2026-09-26: auth в **`~/cli-auth`** (вне `~/agents`), внешние проекты —
|
||||
**только bind-mount**, live-bind вычеркнут.
|
||||
|
||||
## История
|
||||
- **2026-09-26**: создан. Решения Alex: папка `~/agents`, full access, управление
|
||||
линками — секция в Eagle Dashboard. Живым тестом доказано, что симлинк в Docker
|
||||
не читается → внешние проекты подключать bind-mount'ом, не линком.
|
||||
- **2026-09-26 (продолжение)**: два уточнения доказаны живыми тестами.
|
||||
**Live `mount --bind` на macOS Docker НЕ работает** (virtiofs видит VM-mount'ы только
|
||||
на старте контейнера) → recreate обязателен, live-bind вычеркнут (3 прогона, все
|
||||
отрицательные). **Сетевые шары** — тот же корень, в контейнер не протянуть.
|
||||
Добавлен раздел про **данные CLI на хосте** (`~/cli-auth` вне `~/agents`) по
|
||||
требованию Alex: auth переживает пересборку образа и обновление CLI.
|
||||
1. Провайдер исполнителя — claude или codex
|
||||
2. Нужен ли `ask`-апрув в будущем
|
||||
3. Hostname для моста (`agents-bridge.qentra.top`?)
|
||||
|
||||
## Связанные заметки
|
||||
- [[personal/plans/balda-mm-phase4-agents]] — Фаза 4 (план)
|
||||
- [[personal/tech/macos-docker-virtiofs-mount-limits]] — 🔴 virtiofs: симлинки/live-bind/шары в контейнер
|
||||
- [[personal/projects/balda/balda-mm-delegation-plan]] — общий план (Фазы 4-5)
|
||||
- [[personal/projects/balda/balda-kraken-mm-deploy]] — Фаза 3 (✅ бот отвечает)
|
||||
- [[personal/plans/balda-mm-pr-plan]] — Фаза 2 (PR #8, 8/8 CI, ждёт ревью)
|
||||
- [[personal/projects/balda/balda-kraken-mm-deploy]] — Фаза 3 (✅)
|
||||
- [[personal/plans/balda-mm-pr-plan]] — Фаза 2 (PR #8)
|
||||
- [[personal/tech/macos-docker-virtiofs-mount-limits]] — ограничения macOS Docker
|
||||
- [[personal/tech/balda-go-fx-pitfalls]] — fx-питфоллы, дельта-апплаер
|
||||
- скилл `eagle-dashboard` — архитектура дашборда, health-типы, launchd-нюансы
|
||||
- скилл `wireguard-vps-hub` — туннель Kraken → Mac
|
||||
- [[family/how-to/kraken-portainer-access]] — cloudflared, routes
|
||||
- скилл `eagle-dashboard` — архитектура дашборда
|
||||
@@ -155,6 +155,13 @@ Scheduler/webhook-таргеты (`balda.scheduler.jobs` → `target: locator`)
|
||||
- Вариант C: **вторая Балда в VM** и связка через webhooks Балды (в апстриме есть `balda.webhooks.*`).
|
||||
- **Рекомендация: начать с B**, потому что он не требует ни форка Балды, ни второго канала. Позже можно перейти на C.
|
||||
|
||||
✅ **Решено 2026-09-26: вариант B (мост на Mac).** Сетевой путь Kraken → Mac —
|
||||
**Cloudflare Tunnel** через уже работающий cloudflared на Mac (`agents-bridge.qentra.top`
|
||||
→ HTTP `localhost:8790` + Access Application), **не WireGuard**: VPS-хаб `91.207.28.205`
|
||||
удалён 2026-09-01, туннель мёртв. cloudflared-маршруты применяются на стороне Cloudflare
|
||||
без рестарта процесса. Детали транспорта и монтажей — [[personal/plans/balda-mm-phase4-agents]].
|
||||
⚠️ hostname обязательно одноуровневый (см. [[personal/tech/cloudflare-tunnel-ssh-published-route]]).
|
||||
|
||||
---
|
||||
|
||||
## 2. Сценарий делегирования (как это работает для Alex)
|
||||
|
||||
@@ -52,6 +52,36 @@ Host ssh-kraken.qentra.top
|
||||
- Сам ssh к hostname:22 напрямую (без cloudflared в ProxyCommand) к проксированному DNS не работает — hostname за Cloudflare-Anycast, в туннель трафик попадает только через cloudflared-клиент.
|
||||
- `network_mode: host` (container cloudflared) — сервис напрямую видит локальные порты (sshd на `0.0.0.0:22`).
|
||||
|
||||
## HTTP-сервис через существующий туннель (без рестарта cloudflared)
|
||||
|
||||
Туннель на origin — **один процесс на все маршруты**. Добавление нового HTTP-маршрута
|
||||
**не требует** ни правки токена, ни рестарта cloudflared: маршруты живут на стороне
|
||||
Cloudflare и подхватываются работающим туннелем.
|
||||
|
||||
**Схема (Zero Trust):**
|
||||
- Networks → Tunnels → туннель → **Routes → Add route → Published application**
|
||||
- Service: **HTTP** → `localhost:<порт>` (например `localhost:8790`)
|
||||
- hostname — **одиночный** subdomain (`agents-bridge.qentra.top`), см. правило выше
|
||||
|
||||
**Обязательно: Access Application** на тот же hostname (Access → Applications →
|
||||
Add self-hosted, тип HTTP). Без него сервис получается публичным без авторизации —
|
||||
для служебных мостов это недопустимо.
|
||||
|
||||
**Живой пример на Mac (проверено 2026-09-26):**
|
||||
|
||||
| Факт | Значение |
|
||||
|---|---|
|
||||
| Бинарь | `/opt/homebrew/bin/cloudflared` (2026.3.0) |
|
||||
| Процесс | `cloudflared tunnel --protocol quic run --token …` |
|
||||
| Автозапуск | LaunchAgent `com.cloudflare.cloudflared.plist` |
|
||||
| Токены | `~/.cloudflared/*-token` (0600), отдельный файл на каждый hostname |
|
||||
| Маршрут на Mac | `ssh-kraken.qentra.top` → `ssh://localhost:22` |
|
||||
|
||||
Проверка, что туннель поднят: наличие процесса + запись в `~/.cloudflared/`.
|
||||
Проверка нового маршрута: `curl` извне на `<hostname>/healthz`.
|
||||
|
||||
---
|
||||
|
||||
## Общий паттерн «жив, но шумит» cloudflared на origin без IPv6
|
||||
|
||||
Если у origin нет глобального IPv6 (роутер не выдаёт), cloudflared пытается поднять QUIC и по IPv6 → в логах постоянные `ERR ... write udp [::]->198.41.x.x:7844: sendmsg: network is unreachable`, соединения падают/retry. Туннель продолжает работать по IPv4-QUIC. Возможный фикс — заставить cloudflared использовать только IPv4.
|
||||
|
||||
Reference in new issue
Block a user