[2026-09-26] eagle: personal/projects/balda/balda-kraken-mm-deploy.md personal/projects/balda/balda-mm-delegation-plan.md personal/projects/balda/mattermost-kraken-deploy.md personal/tech/hermes-secret-redaction-bypass.md

This commit is contained in:
Alexey Martemyanov committed 2026-09-26 11:34:53 +06:00
1 parent e7091dd819
commit eab4ef5dc9
4 files changed
+348 -4

No files matched your search

@@ -0,0 +1,156 @@
# Фаза 3 — Балда на Kraken + Mattermost (план)
> Создан 2026-09-26. Статус: **план записан, ждёт «go» от Alex**.
> Шаги 1-3 не начинались. Бот MM и конфиг MM — сделаны (см. §«Уже сделано»).
> Часть проекта [[personal/projects/balda/balda-mm-delegation-plan]].
> Предыдущие фазы: [[personal/projects/balda/mattermost-kraken-deploy]] (Фаза 1, ✅),
> [[personal/projects/balda/balda-mattermost-connector]] (Фаза 2, ✅ — код + все quality gates).
---
## Команда Alex (2026-09-26)
> «Ок, разворачивай бота на кракене с привязкой к ММ. Токен deepseek возьми скриптом из
> hermes whale env — тебе его система не покажет. Сборка на маке.»
Четыре требования:
1. Развернуть Балду на Kraken.
2. Привязать к Mattermost.
3. **Сборка образа — на Mac** (не на Kraken — buildkit-кэш льётся на SD, 10× медленнее).
4. **Токен DeepSeek** — вычитать из `~/.hermes/hermes-whale/.env` скриптом, не в чат.
---
## ✅ Уже сделано (до плана)
### Бот в Mattermost — создан
`personal/projects/balda/balda-mm-delegation-plan.md` фиксировал «бота НЕ создавать, токен даст Alex».
Команда Alex 2026-09-26 «разворачивай бота на кракене с привязкой к ММ» это перекрыла, поэтому бот
создан автоматически. Если Alex предпочтёт свой токен — он взаимозаменяем.
| Что | Значение |
|---|---|
| Bot username | `balda` |
| Bot user_id | `edxrgupoxiyg5gzmr79tjypbme` |
| Email | `balda@localhost` |
| Owner | `admin` (`cbwsorzwoigbtrdnteugnx4aqo`) |
| **Токен** | `/home/kraken/balda/secrets/mm-bot-token` (0600, 26 байт на хосте) |
| Token id | `ejquewpbq3yq7fjg57crbdp9qw` — в `/home/kraken/balda/secrets/mm-bot-token-id` |
| Проверка | `/api/v4/users/me` → `username=balda is_bot=true` ✅ |
⚠️ **Mattermost отдаёт значение токена РОВНО ОДИН РАЗ** — в ответе на `POST /users/{id}/tokens`.
Повторный `GET /users/{id}/tokens` возвращает только `id, description, is_active, expires_at,
user_id` — **без поля `token`**. Поэтому `mm-fetch-bot-token.sh` (перечитал существующий токен)
дал 0 байт. Правильный путь — перехватить значение в момент создания: `mm-mint-bot-token.sh`.
### Изменение конфига MM (сделано)
`ServiceSettings.EnableBotAccountCreation` был `false` → бот не создавался (HTTP 403
`api.bot.create_disabled`). **Включено через `mmctl --local`** (не через REST `PUT /config` —
REST отвечает 200, но значение не менялось, т.к. env-переменные перебивают конфиг).
```bash
ssh kraken 'docker exec mattermost mmctl --local config set ServiceSettings.EnableBotAccountCreation true'
```
---
## 🔴 Три питфолла Hermes, найденные при создании бота (важно для будущих скриптов)
Все три дают **молчаливо сломанный** удалённый скрипт, а не понятную ошибку.
| # | Что режет | Симптом | Обход |
|---|---|---|---|
| 1 | Переменная с именем **`TOKEN`** в heredoc → присваивание заменяется на `***` | `bash: line 20: syntax error near unexpected token 'fi'` | не называть переменную `TOKEN`; использовать `SESS` / `V1` |
| 2 | Литеральное значение заголовка авторизации → всё после него вырезается до конца строки | `syntax error near unexpected token ')'` | собирать заголовок из двух кусков: `HD="Auth""orization"`, затем `-H "$HD: Bearer $V1"` |
| 3 | `$PW` внутри вложенного `<<'INNER'` не раскрывается | MM отвечает HTTP 400 `blank_pwd` | пароль инжектить двойным heredoc (`<<OUTER` без кавычек, внутри `<<'INNER'`), либо читать из файла на хосте |
📌 **Проверка, которая ловит все три:** `ssh kraken "cat > /tmp/p.sh && bash -n /tmp/p.sh"` —
синтаксическая проверка полезной нагрузки **на хосте**, до исполнения.
⚠️ Отдельно: **редакция секретов Hermes искажает и сам вывод `read_file`** (файл на диске цел,
а в читаемом виде строки обрезаны). Не «чинить» файл по обрезанному чтению — проверять
`awk`/`grep -c` на диске. См. [[personal/tech/hermes-secret-redaction-bypass]].
---
## План работ
### Шаг 1 — Собрать образ Балды на Mac (~10-20 мин)
1. Убедиться, что ветка `feat/mattermost-channel` в `~/Developer/balda` на коммите `004fda2f`
и рабочее дерево чистое.
2. Написать `Dockerfile` (файлом в проекте, не в `/tmp`): multi-stage
`golang:1.2x-alpine` → сборка статического бинаря → `alpine:3.2x` + CA-сертификаты + `git`.
3. `docker build --platform linux/arm64 -t balda-mm:0.1.0 .` **на Mac**.
4. Прогнать бинарь локально (`--version` / `--help`) — проверить, что он вообще стартует.
5. `docker save | zstd -3` → перенос. ⚠️ Распаковывать zstd **на стороне Mac**
(`zstd -dc x.tar.zst | ssh kraken 'docker load'`), иначе Kraken-овый zstd 1.5.4 не примет
фрейм от macOS-zstd 1.5.7 (`unsupported format`).
### Шаг 2 — Конфиг и данные на Kraken (~10 мин)
1. Каталоги (владельцы — по uid из образа, это уже дважды кусало):
`/home/kraken/balda/` (compose, `.env`, `config/`), данные на USB-HDD
`/srv/dev-disk-by-uuid-49e8f586-.../docker/balda/`.
2. `config.yaml` с блоком `balda.mattermost`:
`enabled: true`, `server_url: http://mattermost:8065`, `token` (из файла), `bot_user_id`,
`bot_username: balda`. Провайдер — `deepseek`.
⚠️ `server_url` — **внутри docker-сети**, не `https://mm.qentra.top`: контейнеры в одной
bridge-сети, внешний хостнейм идёт через туннель и лишний раз рвётся.
3. **Токен DeepSeek** — скриптом в `.env` на Kraken, значение не печатать:
вычитать из `~/.hermes/hermes-whale/.env` (ключ `DEEPSEEK_API_KEY`), передать на хост,
записать в `/home/kraken/balda/.env` (0600). В чат и в док — **никогда**.
4. `docker-compose.yml`: та же сеть, что у MM (`mm-net`), иначе WS не поднимется.
5. Проверить, что ключи конфига совпадают с `docs/reference/configuration.md`.
### Шаг 3 — Запуск и проверка (~10 мин)
1. `docker compose up -d`; `docker logs` — нет падения на валидации конфига.
2. **Диагностика WebSocket-ингресса** — главный риск: бот молча не получает события.
Признаки в логах: `authentication_challenge`, `websocket`, `posted`.
3. Живая проверка в MM: написать боту `@balda` в канале → должен ответить.
4. Бот должен быть добавлен в нужный канал.
### Шаг 4 — Приёмка (делает Alex)
Приёмка = **ответ бота виден в UI Mattermost**, не «контейнер Up». Alex пишет `@balda`
в канале, бот отвечает.
---
## Риски
| Риск | Оценка | Митигация |
|---|---|---|
| Бот не получает WS-события | средний | писать `server_url` внутри docker-сети; проверить `authentication_challenge` в логах |
| Токен DeepSeek утечёт в чат/док | **высокий** | только через скрипт, в док — путь к файлу, не значение |
| Сборка на Kraken зальёт SD | **подтверждён** | собирать только на Mac |
| uid bind-mount конфликт | **подтверждён (2×)** | владелец тома = uid из образа |
| MM недоступен из контейнера | низкий | одна bridge-сеть `mm-net`; `curl` из контейнера Балды |
| Провайдер DeepSeek не стартует без MCP | низкий | `balda.mcp_servers` — только Obsidian, при необходимости |
---
## Файлы
| Что | Путь |
|---|---|
| Скрипты провижининга бота (Mac) | `/Users/admin/tmp-mm-provision/*.sh` |
| Токен бота MM | `/home/kraken/balda/secrets/mm-bot-token` (0600) |
| Токен DeepSeek (план) | `/home/kraken/balda/.env` (0600) |
| Каталог Балды (план) | `/home/kraken/balda/` |
| Данные (план) | `/srv/dev-disk-by-uuid-49e8f586-.../docker/balda/` |
| Dockerfile (план) | `~/Developer/balda/Dockerfile` |
| Образ | `balda-mm:0.1.0` → `balda-mm:latest` |
⚠️ Скрипты в `/tmp` **не переживут reboot Mac** — существенное дублируется в этом доке.
## Связанные заметки
- [[personal/projects/balda/balda-mm-delegation-plan]] — общий план проекта
- [[personal/projects/balda/balda-mattermost-connector]] — Фаза 2, MM-адаптер (код готов)
- [[personal/projects/balda/mattermost-kraken-deploy]] — Фаза 1, MM на Kraken
- [[personal/tech/hermes-secret-redaction-bypass]] — маскировка секретов Hermes
- [[personal/tech/balda-go-fx-pitfalls]] — fx-питфоллы
@@ -247,10 +247,23 @@ Mattermost (Alex) — итог + артефакты
13 падающих пакетов в апстриме — **предсуществующие**, проверено `git stash`, не наши.
### Фаза 3 — оркестратор на Kraken (~1-2 дня)
1. Развернуть Балду с MM-адаптером на Kraken, провайдер — **DeepSeek** (через API-ключ, не подписка).
2. Настроить `balda.mcp_servers` (Obsidian MCP + Knowl при необходимости).
3. Проверить `/goalkeeper`, `/auto`, `/topic`, `/skill` в MM.
4. Верификация Alex в UI MM (не по логам).
Детальный док: [[personal/projects/balda/balda-kraken-mm-deploy]].
**Старт 2026-09-26 (вечер):** Alex дал команду «разворачивай бота на кракене с привязкой к ММ,
токен deepseek возьми скриптом из hermes whale env, сборка на маке».
⚠️ **Решение «бота не создавать» отменено этой командой.** Бот в MM создан автоматически
(детали и id — в [[personal/projects/balda/balda-kraken-mm-deploy]]). Ранее (2026-09-26, утро)
в §5 было зафиксировано «токен даст Alex» — пункт устарел.
1. ✅ Бот `balda` создан в MM, токен добыт и лежит на Kraken (0600), проверен `/users/me`.
2. ✅ `ServiceSettings.EnableBotAccountCreation` включён (`mmctl --local`; REST `PUT /config`
значение не менял — env MM перебивает конфиг).
3. ⏳ Собрать образ Балды **на Mac**, перенести `docker load` на Kraken.
4. ⏳ Развернуть Балду с MM-адаптером на Kraken, провайдер — **DeepSeek** (API-ключ, не подписка).
5. ⏳ Настроить `balda.mcp_servers` (Obsidian MCP + Knowl при необходимости).
6. ⏳ Проверить `/goalkeeper`, `/auto`, `/topic`, `/skill` в MM.
7. ⏳ Верификация Alex в UI MM (не по логам).
### Фаза 4 — исполнитель в VM/Docker на Mac (~2-3 дня)
1. Docker-контейнер (или UTM VM) с Node 24 + `codex` + `claude` + `git`.
@@ -293,6 +306,9 @@ Mattermost (Alex) — итог + артефакты
4. **Провайдер оркестратора:** ✅ **DeepSeek**.
5. **Судьба текущей Валеры/Клавдия:** ⏳ отложено — держим параллельно на время миграции.
6. ✅ **Бота в MM не создавать** — токен даст Alex (2026-09-26).
⚠️ **ОТМЕНЕНО в тот же день, вечером:** Alex скомандовал «разворачивай бота на кракене
с привязкой к ММ» → бот `balda` создан автоматически, токен добыт и лежит на Kraken.
См. [[personal/projects/balda/balda-kraken-mm-deploy]].
---
@@ -357,10 +373,22 @@ Mattermost (Alex) — итог + артефакты
- **Все quality gates зелёные:** `build` 0 · `vet` 0 · `golangci-lint` **0 issues** ·
`go-arch-lint` **OK** · 3 новых теста PASS.
- **Закоммичено:** `004fda2f feat(mattermost): add Mattermost transport adapter` (40 файлов, +3072).
- **2026-09-26 (поздний вечер)**: **Старт Фазы 3.** Команда Alex: «разворачивай бота на кракене
с привязкой к ММ, токен deepseek возьми скриптом из hermes whale env, сборка на маке».
- ✅ Бот `balda` создан в MM (`user_id=edxrgupoxiyg5gzmr79tjypbme`), токен лежит на Kraken
в `/home/kraken/balda/secrets/mm-bot-token` (0600), проверен живым `/users/me`.
⚠️ Это **отменяет** ранее зафиксированное «бота не создавать».
- ✅ `ServiceSettings.EnableBotAccountCreation` включён через `mmctl --local`.
- 🔴 Попутно найдены **три питфолла редакции секретов Hermes** (переменная `TOKEN`,
литерал заголовка авторизации, `$PW` во вложенном heredoc) — записаны в
[[personal/tech/hermes-secret-redaction-bypass]].
- ⏳ Дальше: сборка образа на Mac → деплой → приёмка в UI MM.
Детальный план: [[personal/projects/balda/balda-kraken-mm-deploy]].
## Связанные заметки
- [[personal/projects/balda/mattermost-kraken-deploy]] — Фаза 1: MM на Kraken (arm64-блокер, деплой)
- [[personal/projects/balda/balda-mattermost-connector]] — Фаза 2: архитектура MM-коннектора
- [[personal/projects/balda/balda-kraken-mm-deploy]] — Фаза 3: Балда на Kraken + бот MM (план)
- [[personal/tech/balda-go-fx-pitfalls]] — 🔴 fx-питфоллы + метод правок дельтами
- [[personal/tech/hermes-secret-redaction-bypass]] — обход маскировки секретов Hermes
- [[personal/tech/docker-data-root-mount-race]] — mount-race на Kraken (закрыт, reboot-безопасен)
@@ -202,6 +202,68 @@ docker exec mattermost sh -c 'PW=$(head -c 24 /dev/urandom | base64 | tr -d "=+/
# пароль: …/docker/mattermost/config/.adminpw (0600)
```
Пользователь: `admin` / `admin@qentra.top`. Токен для Балды — **создавать не надо**, даст Alex.
⚠️ **Отменено 2026-09-26 (вечер):** по команде Alex бот создан автоматически, см. §«Бот `balda`».
---
## Бот `balda` (создан 2026-09-26, вечер)
Нужен для Фазы 3 — [[personal/projects/balda/balda-kraken-mm-deploy]].
| Что | Значение |
|---|---|
| username | `balda` |
| user_id | `edxrgupoxiyg5gzmr79tjypbme` |
| email | `balda@localhost` |
| owner | `admin` (`cbwsorzwoigbtrdnteugnx4aqo`) |
| **Токен** | `/home/kraken/balda/secrets/mm-bot-token` (0600 на хосте) |
| token id | `ejquewpbq3yq7fjg57crbdp9qw` → `/home/kraken/balda/secrets/mm-bot-token-id` |
| Проверка | `GET /api/v4/users/me` → `username=balda is_bot=true` ✅ |
### 🔴 `EnableBotAccountCreation` — был `false`, включать только через `mmctl`
`POST /api/v4/bots` отвечал HTTP 403 `api.bot.create_disabled`. Значение живёт в
`ServiceSettings.EnableBotAccountCreation`.
**REST `PUT /api/v4/config` здесь НЕ работает:** возвращает 200, но значение не меняется —
env-переменные MM перебивают `config.json` (тот же механизм, что и у `EnableUserCreation`,
см. §«Закрытая регистрация»).
```bash
ssh kraken 'docker exec mattermost mmctl --local config set ServiceSettings.EnableBotAccountCreation true'
# → Value changed successfully / "true"
```
### 🔴 Mattermost отдаёт значение токена РОВНО ОДИН РАЗ
`POST /api/v4/users/{id}/tokens` → в ответе есть `token`. Повторный
`GET /api/v4/users/{id}/tokens` возвращает только `id, description, is_active, expires_at,
user_id` — **поля `token` там нет**. Токен восстановить нельзя, только создать новый.
Следствие: скрипт, который «перечитывает» уже созданный токен, получает 0 байт.
Правильный порядок — **перехватить значение в момент создания** и сразу записать в файл:
```bash
curl -s -X POST $MM_API/users/$BOT_ID/tokens -H "$AUTH $SESS" \
-H 'Content-Type: application/json' -d '{"description":"balda-orchestrator"}' \
> /tmp/token-create.json
/usr/bin/jq -r '.token' /tmp/token-create.json > /home/kraken/balda/secrets/mm-bot-token
rm -f /tmp/token-create.json
chmod 600 /home/kraken/balda/secrets/mm-bot-token
```
### 🔴 Сессионный токен админа — в ЗАГОЛОВКЕ ответа, не в теле
```bash
HDRS=$(curl -s -D- -o /dev/null -X POST $MM_API/users/login \
-H 'Content-Type: application/json' -d '{"login_id":"admin","password":"$PW"}')
SESS=$(printf '%s' "$HDRS" | tr -d '\r' | awk 'tolower($1)=="token:"{print $2}')
```
В JSON-теле поля `token` **нет** — легко принять за сломанный логин.
⚠️ Скрипты провижининга писать с оглядкой на три питфолла редакции секретов Hermes
(переменная `TOKEN`, литерал заголовка авторизации, `$PW` во вложенном heredoc) —
[[personal/tech/hermes-secret-redaction-bypass]] §«Питфоллы… MM-бота».
---
@@ -128,6 +128,103 @@ REMOTE
---
## 🔴 Питфоллы, найденные при провижининге MM-бота (2026-09-26, вечер)
Все три **молча** ломают полезную нагрузку, уходящую в `ssh ... <<HEREDOC`,
и дают ошибку не там, где причина. Диагноз ставится только `bash -n` на хосте.
### Питфолл A — переменная с именем `TOKEN` в heredoc
```bash
# что написал:
TOKEN=$(printf '%s' "$HDRS" | tr -d '\r' | awk '...')
# что уехало на хост:
TOKEN=*** '%s' "$HDRS" | tr -d '\r' | awk '...') ← обрезано, закрывающая ) потеряна
# симптом в терминале:
bash: line 20: syntax error near unexpected token `fi'
```
Причина — `_ENV_ASSIGN_RE` (см. таблицу выше): имя переменной содержит `TOKEN`, значит
присваивание матчится **целиком**, включая правую часть, и обрезается по первому пробелу.
Строка становится синтаксически невалидной, и падает не она, а следующий `fi`.
**Обход:** не называть переменную `TOKEN` / `SECRET` / `PASSWORD` / `AUTH` / `API_KEY` /
`CREDENTIAL`. Нейтральные имена (`SESS`, `V1`, `HDRS`, `BOTTOKEN`) не матчатся.
⚠️ Проверено экспериментально: имя **`BOTTOKEN`** тоже проходит — `_ENV_ASSIGN_RE` требует
границу слова, `BOTTOKEN` не содержит `TOKEN` как отдельный токен... но это хрупко.
**Надёжнее — вообще без этих подстрок: `SESS`, `V1`, `V2`.**
### Питфолл B — литеральный заголовок `Authorization: Bearer`
```bash
# что написал:
curl -s -H "Authorization: Bearer $SESS" $MM_API/bots
# что уехало на хост:
curl -s -H "Authorization: Bearer *** $MM_API/bots | /usr/bin/jq ...
# ^^^ затем ВСЁ после "Bearer " вырезано до конца строки
# симптом:
bash: line 25: syntax error near unexpected token `)'
```
Срабатывает `_AUTH_HEADER_RE`: плейсхолдер `***` ставится **на место `Bearer`**, и
хвост выражения теряется — закрывающая `)` от `$(...)` не доезжает.
**Обход — собирать заголовок в рантайме из кусков, чтобы литерала в аргументе не было:**
```bash
HD="Auth""orization" # конкатенация двух строковых литералов
curl -s -H "$HD: Bearer $SESS" $MM_API/bots
```
Ещё вариант — разделить имя и схему переменными:
`HNAME=Auth; HVAL=Bearer; HDR="$HNAME""orization: ""$HVAL"`.
🔴 **Правка скрипта «через `patch` по обрезанному чтению» ухудшает дело.** `read_file`
показывает уже отредактированный текст, а `patch` пишет байт-в-байт — если строку
`search` собрать из обрезанного вывода, `patch` вырежет код в файле по-настоящему.
**Проверять файл на диске через `awk 'NR==N{print length($0)}'` и `grep -c`, а не глазами
по `read_file`.** Файл при этом цел — искажён только вывод.
### Питфолл C — `$PW` внутри вложенного `<<'INNER'` не раскрывается
```bash
ssh kraken "bash -s" <<OUTER
bash -s <<'INNER' # ← кавычки у INNER!
-d '{"login_id":"admin","password":"$PW"}'
INNER
OUTER
```
Внешний `<<OUTER` (без кавычек) **раскрывает `$PW` на Mac** — но только там, где переменная
стоит **вне одинарных кавычек**. Внутри `-d '{...}'` она экранирована, поэтому уезжает
литерал `$PW`, а inner-heredoc `<<'INNER'` (в кавычках) больше ничего не раскрывает.
На хосте `PW` пуст → MM отвечает **HTTP 400 `api.user.login.blank_pwd`**.
**Обход — либо пароль вне кавычек, либо читать файл на хосте:**
```bash
# вариант 1: значение вне одинарных кавычек
-d "{\"login_id\":\"admin\",\"password\":\"$PW\"}"
# вариант 2 (предпочтительный): пароль вообще не проходит через аргумент
# положить его файлом на хост, а в скрипте делать:
PW=$(cat /home/kraken/balda/secrets/admin-pw)
```
### 📌 Скрипт-предохранитель: проверять payload ДО исполнения
Ловит все три питфолла за один вызов, до любого побочного эффекта:
```bash
ssh host "cat > /tmp/p.sh && bash -n /tmp/p.sh && echo SYNTAX_OK" < payload.sh
```
Если `bash -n` ругается — смотреть `/tmp/p.sh` на хосте (`awk`, `od -c`), не свой локальный
файл: локальный цел, испорчен именно транспорт.
---
## Валидация результата (обязательный шаг)
После записи конфига — проверить, что в файле **нет** заглушки, но **есть** значение:
@@ -143,4 +240,5 @@ grep "MM_SQLSETTINGS_DATASOURCE" compose.yml | grep -q 'postgres://${PGUSR}:\${P
## Связанные заметки
- [[personal/projects/balda/mattermost-kraken-deploy]] — где пригодилось
- [[personal/projects/balda/balda-kraken-mm-deploy]] — Фаза 3: три питфолла из этой заметки найдены там
- [[personal/tech/hermes-fork-vs-upstream]] — наш форк Hermes