469 lines
30 KiB
Markdown
Executable File
469 lines
30 KiB
Markdown
Executable File
> [!WARNING] Историческая схема Balda
|
||
> Не использовать пути, контейнеры, provider или MCP transport из этой заметки для текущего запуска.
|
||
> Канонический текущий runbook: [[personal/projects/balda/setup|Balda — актуальный локальный запуск]].
|
||
> Актуально: `balda-mm-mac`, `/Users/admin/Docker/balda-mm-mac/config/config.yaml`, Mattermost, Codex ACP, Obsidian `type: http` → `http://host.docker.internal:3101/mcp`.
|
||
|
||
# Mattermost на Kraken — деплой (arm64)
|
||
|
||
> Создано 2026-09-26. Обновлено 2026-09-26. Статус: **✅ СЕРВЕР РАБОТАЕТ**.
|
||
> `health=healthy restarts=0`, API `{"status":"OK"}` с Kraken и с Mac по LAN,
|
||
> админ создан, **регистрация закрыта** (проверено живым запросом → HTTP 501).
|
||
> Часть проекта [[personal/projects/balda/balda-mm-delegation-plan]].
|
||
>
|
||
> **Сделано (2026-09-26, позже):** `SITEURL` → `https://mm.qentra.top`, MM перезапущен → `health=healthy restarts=0`.
|
||
> Ping через туннель с Mac: `{"status":"OK"}`. `mmctl config get ServiceSettings.SiteURL` → `https://mm.qentra.top`.
|
||
>
|
||
> **Осталось:** приёмка входом в браузере (делает Alex).
|
||
>
|
||
> **Обновлено 2026-09-29:** добавлен третий бот — **`whale`** (Кит) для транспорта
|
||
> hermes-whale. Канал `whale`. Разбор — [[personal/tech/hermes-whale-mattermost-transport.md]].
|
||
>
|
||
> | Объект | ID |
|
||
> |---|---|
|
||
> | Бот `whale` | `k4i3uihzp3gdxj5gx3en3tbeso` |
|
||
> | Канал `whale` | `fnm6auyc8trz5njxrfofebkbfr` |
|
||
> | PAT бота | `len=26`, проверен `users/me` → `username: whale` |
|
||
>
|
||
> **Всего ботов на инстансе — 3:** `system-bot`, `balda`, `whale`.
|
||
> Каналов в команде `qentra` — 4: `town-square`, `off-topic`, `balda-test`, `whale`.
|
||
|
||
---
|
||
|
||
## 🔴 Главная находка: официального arm64-образа Mattermost НЕТ
|
||
|
||
`mattermost/mattermost-team-edition` — **только `linux/amd64`**. Проверено `docker manifest inspect`
|
||
по тегам `latest`, `release-12`, `release-10`, `11.11.1`, `10.11.24`, `9.11`, `release-8` —
|
||
везде `architecture: amd64`. То же у `mattermost-enterprise-edition` и `mattermost-preview`.
|
||
|
||
Официальная позиция Mattermost (KB `53131125053204`, на 2026-09-01):
|
||
**ARM64 «Not officially supported for production, no roadmap commitment».** Публикуются
|
||
некоторые arm64-сборки, но production не поддерживается; официально поддержан только amd64.
|
||
|
||
⚠️ **`linuxserver/mattermost` НЕ существует** — `pull access denied`. На Docker Hub его нет.
|
||
Не искать его снова (я потратил на это время).
|
||
|
||
### Решение: собираем образ сами из ОФИЦИАЛЬНОГО бинаря
|
||
|
||
Mattermost **публикует arm64-бинарники** на release-сервере — просто не пакует их в образы.
|
||
Поэтому образ собирается из официального tarball. Это production-путь:
|
||
свой Dockerfile + upstream-бинарь, **без сторонних community-образов**.
|
||
|
||
🔴 **СБОРКА ИДЁТ НА MAC, НЕ НА KRAKEN** (решение Alex, 2026-09-26 — исправление моей ошибки).
|
||
|
||
Я сначала собирал на Kraken. Alex указал на два дефекта, оба реальные:
|
||
1. **Mac — тот же `linux/arm64`** (Apple Silicon), Docker нативный, без эмуляции. Замер:
|
||
сборка на Mac — **33 секунды**. На Kraken тот же Dockerfile падал после **346 с** на одном
|
||
только apt-слое.
|
||
2. 🔴 **Buildkit-кэш на Kraken живёт на SD-карте, а не на HDD.** `data-root` перенесён на USB-HDD,
|
||
но **buildkit cache ему не подчиняется**. Это записано в `openmediavault-rpi5.md`
|
||
§«Build cache (buildkit) — живёт на SD!». Сборка льёт `apt-get`, curl tarball 157 МБ и слои
|
||
именно на 14-гигабайтную SD — ровно то, от чего разгружали Kraken в инциденте 2026-06-24
|
||
(SD под 99%, краш).
|
||
**Урок: тяжёлые сборки образов для Kraken — НИКОГДА на Kraken.**
|
||
|
||
Перенос образа на Kraken:
|
||
```bash
|
||
# на Mac
|
||
docker build --platform linux/arm64 -t mattermost-team-arm64:11.11.1 .
|
||
docker save ... | zstd -3 -T0 -o mm-arm64.tar.zst
|
||
# передача (zstd -dc на стороне Mac, распаковка на хосте)
|
||
zstd -dc mm-arm64.tar.zst | ssh kraken 'docker load'
|
||
```
|
||
⚠️ **Питфолл:** `zstd -dc file.zst | ssh kraken 'zstd -d | docker load'` **не работает** —
|
||
zstd на Kraken (v1.5.4) отказывается читать фрейм, записанный macOS-zstd (v1.5.7):
|
||
`unsupported format`. Распаковывать **на стороне Mac**, на хост отдавать уже `docker load`.
|
||
|
||
После переезда: `docker builder prune -af` на Kraken вернул SD к 36% (кэш 297 МБ удалён).
|
||
|
||
Проверка доступности tarball (HTTP 200, `content-length: 157689127`):
|
||
|
||
```
|
||
https://releases.mattermost.com/11.11.1/mattermost-team-11.11.1-linux-arm64.tar.gz
|
||
```
|
||
|
||
Работает и `mattermost-<ver>-linux-arm64.tar.gz` (без `team`). Префикс `mattermost-team-` —
|
||
Team Edition, это то, что нужно. Собранная версия подтверждена: `Version: 11.11.1`,
|
||
`Build Enterprise Ready: false`.
|
||
|
||
**Альтернативы, которые НЕ выбраны:**
|
||
| Вариант | Почему нет |
|
||
|---|---|
|
||
| `cyberman443/mattermost-arm64` | сторонний, 5 мес. давности, непроверенная сборка |
|
||
| `ghcr.io/antonsatt/mattermost-arm64` | сторонний; CI собирает weekly, но доверия меньше, чем своему Dockerfile |
|
||
| `mmtrt/…`, `ctlove0523/…`, `bitnami/mattermost` | не существуют / нет arm64 |
|
||
|
||
Референс структуры Dockerfile: `github.com/AntonSatt/mattermost-arm64` (там `ubuntu:noble` +
|
||
официальный tarball; **нюанс — в их ARG по умолчанию enterprise-путь без `-team`**, у нас явно team).
|
||
|
||
---
|
||
|
||
## Файлы и пути
|
||
|
||
| Что | Путь |
|
||
|---|---|
|
||
| Каталог compose | `/home/kraken/mattermost/` |
|
||
| `docker-compose.yml` | `/home/kraken/mattermost/docker-compose.yml` (0600) |
|
||
| `.env` (пароль postgres) | `/home/kraken/mattermost/.env` (0600, 3 строки) |
|
||
| **Пароль админа MM** | `…/docker/mattermost/config/.adminpw` (0600, uid 2000) |
|
||
| Бэкап compose | `/home/kraken/mattermost/backups/docker-compose.yml.bak-<stamp>` |
|
||
| Данные и тома | `/srv/dev-disk-by-uuid-49e8f586-3839-4c5d-a1e1-58bfc3579ade/docker/mattermost/{postgres,data,logs,plugins,config,client-plugins,bleve-indexes}` |
|
||
| Dockerfile сборки (Mac) | `/tmp/mm-build/Dockerfile` |
|
||
| Образ | `mattermost-team-arm64:11.11.1` (+ `:latest`) |
|
||
| Скрипты (Mac) | `/tmp/mm-deploy/{build-on-mac,deploy-mattermost,ship-to-kraken,reload-stack,fix-ownership,fix-ownership2,provision-admin,harden-registration,align-config,wait-health,final-check}.sh`, `patch_compose.py` |
|
||
|
||
> ⚠️ Скрипты в `/tmp` — **не переживут reboot Mac**. Всё существенное продублировано в этом доке;
|
||
> при необходимости воспроизводить по разделам ниже.
|
||
|
||
⚠️ Путь `49e8f586…` — **симлинк** на реальный `6194539b…`. Все сервисы используют старый путь.
|
||
|
||
### Владельцы каталогов — КРИТИЧНО (uid-конфликт, дважды укусил)
|
||
|
||
| Каталог | Владелец | Почему |
|
||
|---|---|---|
|
||
| `mattermost/postgres` | **`70:70`** | uid postgres-образа `postgres:16-alpine` |
|
||
| всё остальное в `mattermost/` | **`2000:2000`** | uid юзера в нашем образе (`useradd --uid 2000`) |
|
||
|
||
**Как сломалось дважды (один корень, два симптома):**
|
||
1. Каталоги создал `kraken` (uid **1000**), контейнер работает под **2000** →
|
||
`could not create config file: open /mattermost/config/config.json: permission denied`.
|
||
2. Я исправил **слишком широко**: `chown -R 2000:2000` задел и `postgres/` →
|
||
postgres (uid **70**) потерял доступ → `pq: could not open file "global/pg_filenode.map":
|
||
Permission denied (42501)`.
|
||
|
||
**Правильно — раздельно:**
|
||
```bash
|
||
ROOT=/srv/dev-disk-by-uuid-49e8f586-.../docker/mattermost
|
||
sudo -n chown -R 70:70 $ROOT/postgres
|
||
for d in config data logs plugins client-plugins bleve-indexes; do sudo -n chown -R 2000:2000 $ROOT/$d; done
|
||
sudo -n chown 2000:2000 $ROOT
|
||
```
|
||
📌 Это **тот же класс дефекта**, что crash-loop `hermes-kraken` (uid bind-mount). При любом
|
||
новом сервисе на Kraken — сразу приводить владельца тома к uid из образа.
|
||
|
||
**Питфолл:** `chown -R` на медленном USB-HDD может пережить разрыв SSH. Запускать через
|
||
`sudo -n systemd-run --unit=mm-chown --collect --property=Type=oneshot chown -R …`,
|
||
потом опрашивать `systemctl is-active mm-chown`.
|
||
|
||
### `.env` формат (3 строки, значения не в доке)
|
||
|
||
```
|
||
PGPW=<24 байта, openssl rand -hex 24>
|
||
PGUSR=mmuser
|
||
PGDBNAME=mattermost
|
||
```
|
||
|
||
Генерируется **на хосте** при первом запуске скрипта; повторный запуск не перезаписывает
|
||
(`if [ -s .env ]; then exists_kept`).
|
||
|
||
### `docker-compose.yml` — ключевые параметры
|
||
|
||
| Ключ | Значение | Почему |
|
||
|---|---|---|
|
||
| образ MM | `mattermost-team-arm64:11.11.1` | вместо официального amd64 |
|
||
| порт | `8065:8065` | был свободен (проверено `ss -tln`) |
|
||
| postgres | `postgres:16-alpine` | arm64 есть |
|
||
| `MM_SQLSETTINGS_DRIVERNAME` | `postgres` | |
|
||
| `MM_SQLSETTINGS_DATASOURCE` | `postgres://mmuser:***@postgres:5432/mattermost?sslmode=disable&connect_timeout=10` | пароль подставляется **в момент генерации compose** из `.env` |
|
||
| `MM_SERVICESETTINGS_SITEURL` | ✅ **`https://mm.qentra.top`** (обновлено 2026-09-26; было `http://192.168.1.14:8065`) | MM отдаёт ссылки и WS-URL по этому хосту; LAN-значение ломало бы WebSocket через туннель |
|
||
| `MM_SERVICESETTINGS_ENABLELOCALMODE` | `true` | нужен для healthcheck `mmctl --local` |
|
||
| `MM_TEAMSETTINGS_ENABLEOPENSERVER` | `"false"` | 🔴 **закрытая регистрация** |
|
||
| `MM_TEAMSETTINGS_ENABLEUSERCREATION` | `"false"` | 🔴 **закрытая регистрация** |
|
||
| `MM_FILESETTINGS_DIRECTORY` | `/mattermost/data` | |
|
||
| healthcheck MM | `curl -fsS http://localhost:8065/api/v4/system/ping` | curl есть в ubuntu:noble-базе |
|
||
| healthcheck PG | `pg_isready -U mmuser -d mattermost` | |
|
||
| `restart` | `unless-stopped` | |
|
||
| `depends_on` | postgres `service_healthy` | MM не стартует раньше БД |
|
||
| сеть | bridge `mm-net` (не `host`) | MM не требует host-сети, в отличие от cloudflared |
|
||
|
||
**Питфолл:** `MM_SQLSETTINGS_DATASOURCE` — это URL, Mattermost читает env **литерально**,
|
||
интерполяция `${PGPW}` внутри значения не работает. Поэтому DSN собирается на хосте
|
||
через `printf` при записи compose, а не через `env_file`.
|
||
|
||
### 🔴 Закрытая регистрация — состояние и как проверено
|
||
|
||
Требование Alex (2026-09-26): **регистрация в MM должна быть закрытая.**
|
||
|
||
Действующие ключи в **Mattermost 11.x** (проверено `mmctl config get TeamSettings`):
|
||
|
||
| Ключ | Статус |
|
||
|---|---|
|
||
| `TeamSettings.EnableOpenServer` | ✅ существует |
|
||
| `TeamSettings.EnableUserCreation` | ✅ существует |
|
||
| `TeamSettings.EnableOpenTeams` | ❌ **удалён в 11.x** (`selected path object is not valid`) |
|
||
| `TeamSettings.RestrictTeamInvitations` | ❌ **удалён в 11.x** (`invalid key`) |
|
||
| `TeamSettings.RestrictCreationToDomains` | ✅ существует (пусто = без доменных ограничений) |
|
||
|
||
⚠️ **Не использовать `EnableOpenTeams` / `RestrictTeamInvitations`** — их нет в 11.x.
|
||
Значения `MM_TEAMSETTINGS_ENABLEOPENTEAMS` / `_RESTRICTTEAMINVITATIONS` оставлены в compose
|
||
как безвредные (Mattermost их игнорирует), но реально работают только два `Enable*`.
|
||
|
||
🔴 **Env-переменные перебивают `config.json`.** `mmctl config set` пишет в `config.json`,
|
||
но env имеет приоритет. Поэтому `config.json` показывал `EnableUserCreation: true`, а рантайм —
|
||
`false`. Расхождение устранено: `config.json` приведён к `false` вручную, источник истины —
|
||
env в compose (переживает пересоздание контейнера).
|
||
|
||
**Живая проверка (не по логам):**
|
||
```bash
|
||
ssh kraken 'curl -s -X POST http://localhost:8065/api/v4/users -H "Content-Type: application/json" \
|
||
-d "{\"email\":\"probe@example.com\",\"username\":\"probeuser\",\"password\":\"ProbePass12345!\"}"'
|
||
# → {"id":"api.user.create_user.signup_email_disabled.app_error", ... "status_code":501}
|
||
```
|
||
HTTP 501 = самописная регистрация отбита. Это и есть доказательство, что закрыто.
|
||
|
||
### Админ
|
||
|
||
```bash
|
||
# создать (идемпотентно; пароль генерируется на хосте, не логируется)
|
||
docker exec mattermost sh -c 'PW=$(head -c 24 /dev/urandom | base64 | tr -d "=+/" | head -c 28); \
|
||
mmctl user create --local --email admin@qentra.top --username admin --password "$PW" --system-admin'
|
||
# пароль: …/docker/mattermost/config/.adminpw (0600)
|
||
```
|
||
Пользователь: `admin` / `admin@qentra.top`. Токен для Балды — **создавать не надо**, даст Alex.
|
||
⚠️ **Отменено 2026-09-26 (вечер):** по команде Alex бот создан автоматически, см. §«Бот `balda`».
|
||
|
||
---
|
||
|
||
## Боты на MM (реестр)
|
||
|
||
| Бот | user_id | Владелец | Назначение |
|
||
|---|---|---|---|
|
||
| `balda` | `edxrgupoxiyig5gzmr79tjypbme` | `admin` | Фаза 3 — [[personal/projects/balda/balda-kraken-mm-deploy]] |
|
||
| `system-bot` | `ackaowjyjbrutjgknr16d5mfry` | `admin` | системный, MM |
|
||
| **`whale`** (Кит) | **`k4i3uihzp3gdxj5gx3en3tbeso`** | `whale` | **транспорт hermes-whale, 2026-09-29** |
|
||
|
||
### Бот `whale` — создан 2026-09-29
|
||
|
||
Для транспорта hermes-whale. Полный разбор — [[personal/tech/hermes-whale-mattermost-transport.md]].
|
||
|
||
```
|
||
# создать юзера → сконвертировать в бота (ШЕСТЬ флагов обязательны, --owner НЕ существует)
|
||
mmctl --local user create --email whale@localhost --username whale --password '<rand>'
|
||
mmctl --local user convert whale --bot --email whale@localhost \
|
||
--firstname Whale --lastname Kit --locale en --system-admin
|
||
# → whale converted to bot.
|
||
```
|
||
|
||
🔴 **`mmctl user convert` НЕ выдаёт токен.** Токен выпускается отдельно и показывается
|
||
**ровно один раз**: `mmctl --local token generate whale whale-hermes`.
|
||
|
||
Канал `whale` = `fnm6auyc8trz5njxrfofebkbfr` (создан `channel create --team qentra`).
|
||
Бот в канале ✅, в команде — ⚠️ не подтверждено (`team users list` в mmctl не существует).
|
||
|
||
---
|
||
|
||
## Бот `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-бота».
|
||
|
||
---
|
||
|
||
## Команды
|
||
|
||
```bash
|
||
# Сборка образа (на Kraken, ~минуты; запускать в background)
|
||
bash /tmp/mm-deploy/build-mattermost-arm64.sh
|
||
|
||
# Деплой (идемпотентный)
|
||
bash /tmp/mm-deploy/deploy-mattermost.sh
|
||
|
||
# Проверка
|
||
ssh kraken "curl -s http://localhost:8065/api/v4/system/ping"
|
||
ssh kraken "docker ps --format '{{.Names}}\t{{.Status}}' | grep -E 'mattermost|mm-postgres'"
|
||
```
|
||
|
||
### Питфоллы, найденные на месте
|
||
|
||
1. 🔴 **Mattermost 11.x: `CMD ["mattermost"]` НЕ работает.** Бинарь печатает help и выходит
|
||
с exit 1. Нужна подкоманда: **`CMD ["mattermost", "server"]`**.
|
||
Симптом в контейнере: `bare_exit=1`, бесконечный цикл `starting`/`unhealthy`, логи — только
|
||
список подкоманд (`db`, `export`, `jobserver`, `server`, `version`, …).
|
||
Проверка: `docker inspect mattermost --format '{{.Config.Cmd}}'` → должно быть `[mattermost server]`.
|
||
2. 🔴 **`tar --strip-components=1 <member>` не водворит дерево под нужный корень.**
|
||
В Dockerfile `tar -xzf mm.tar.gz -C / --strip-components=1 mattermost` распаковывает
|
||
`bin/`, `templates/`, `NOTICE.txt` **в корень `/`**, а не в `/mattermost`. Следствие:
|
||
`test -x /mattermost/bin/mattermost` падает (exit 1), сборка рушится без внятной ошибки.
|
||
**Правильно:** распаковать в staging-каталог, потом `cp -a /staging/mattermost/. /mattermost/`.
|
||
3. **Права файлов в tarball — `rwxrwxrwx`.** После распаковки явно выставить
|
||
`chmod 0755 /mattermost/bin/{mattermost,mmctl}` и `chmod -R a+rX /mattermost`.
|
||
4. **`/srv/.../docker/` — `root:root 755`** → `kraken` НЕ может создать подкаталог.
|
||
Нужен `sudo -n mkdir -p`. `sudo -n true` на Kraken работает **без пароля** (проверено, exit 0).
|
||
5. **`sudo` через SSH требует `< /dev/null`** — иначе `Connection closed by remote host`.
|
||
(Подтверждено, питфолл из `kraken-access` актуален.)
|
||
6. **Нет `ifconfig`** на Kraken — использовать `ip addr` / `ss`.
|
||
7. **Один ssh-heredoc вместо десятка round-trip'ов** — `ssh kraken 'bash -s' <<'REMOTE'`.
|
||
Быстрее и надёжнее, чем отдельные вызовы.
|
||
8. 🔴 **Вложенные кавычки в heredoc через `ssh` ломаются молча.** Мой патч compose через
|
||
`ssh kraken bash -s <<REMOTE … python3 - <<'PY' … PY … REMOTE` **не применился**, не выдав ошибки
|
||
(шаг «== 2. patch ==» отработал, `PATCH_OK` не напечатался, файл не изменился).
|
||
**Правильно:** писать скрипт **файлом на Mac**, затем `ssh kraken 'cat > /path/x.py'
|
||
< x.py` и `ssh kraken 'python3 /path/x.py'`. Это же записано в доке Kraken как питфолл.
|
||
9. **`docker manifest inspect` на приватном/несуществующем образе** даёт
|
||
`unauthorized: authentication required`, а не `not found` — не путать с «образ существует».
|
||
10. **Долгие команды в `ssh` вешают вызов.** `docker run --rm … server --help` (запуск нового
|
||
контейнера) ушёл в таймаут 400 с. Проверять CLI **внутри уже запущенного контейнера**
|
||
через `docker exec`, а не через `docker run`.
|
||
|
||
---
|
||
|
||
## Cloudflare Tunnel — что настроить (делает Alex)
|
||
|
||
Туннель `kraken` уже существует, remote-managed по `TUNNEL_TOKEN`, `network_mode: host`.
|
||
|
||
**Zero Trust → Networks → Tunnels → `kraken` → Public Hostnames → Add:**
|
||
|
||
| Поле | Значение |
|
||
|---|---|
|
||
| Subdomain | `mm` |
|
||
| Domain | `qentra.top` |
|
||
| Path | *(пусто)* |
|
||
| Service Type | `HTTP` |
|
||
| URL | `localhost:8065` |
|
||
|
||
⚠️ **`httpHostHeader` НЕ ставить** — в отличие от `kraken.qentra.top`, где он обязателен.
|
||
|
||
⚠️ Без Access-Applications хостнейм `mm.qentra.top` **публично открыт в интернет**.
|
||
В Team Edition регистрация по умолчанию `open` — закрывать политикой Access или
|
||
выключать самостоятельную регистрацию в System Console.
|
||
|
||
**После добавления хостнейма** — поменять `MM_SERVICESETTINGS_SITEURL` на `https://mm.qentra.top`
|
||
и перезапустить MM. Иначе MM отдаёт ссылки на `192.168.1.14`, и WebSocket-клиент может не подключиться.
|
||
|
||
**Приёмка = вход в `https://mm.qentra.top` в браузере**, не «curl отвечает».
|
||
|
||
---
|
||
|
||
## Состояние (2026-09-26, обновлено после приёмки)
|
||
|
||
| Шаг | Статус |
|
||
|---|---|
|
||
| Каталоги на HDD | ✅ созданы, владельцы исправлены (см. §Владельцы) |
|
||
| `.env` с паролем | ✅ создан, 0600, значения не логировались |
|
||
| `docker-compose.yml` | ✅ записан + пропатчен (arm64-образ, закрытая регистрация) |
|
||
| Dockerfile + arm64-образ | ✅ **собран на Mac за 33 с**, перенесён `docker load` |
|
||
| `docker compose up -d` | ✅ оба контейнера Up |
|
||
| Health | ✅ **`health=healthy restarts=0 status=running`** |
|
||
| `/api/v4/system/ping` с Kraken | ✅ `{"status":"OK"}` |
|
||
| **Ping с Mac по LAN `192.168.1.14:8065`** | ✅ `{"status":"OK"}` |
|
||
| Порт | ✅ `0.0.0.0:8065` (cloudflared видит) |
|
||
| Версия | ✅ 11.11.1 Team Edition, arm64, `Enterprise Ready: false` |
|
||
| Миграции БД | ✅ завершены, websocket-хабы подняты |
|
||
| Админ `admin@qentra.top` | ✅ создан, пароль в `.adminpw` |
|
||
| **Регистрация закрыта** | ✅ **HTTP 501 на самописную регистрацию (живая проверка)** |
|
||
| `config.json` = рантайм | ✅ приведены к согласию (`EnableUserCreation/OpeNserver: false`) |
|
||
| Cloudflare `mm → 8065` | ✅ **добавил Alex 2026-09-26** («up») |
|
||
| `SITEURL` → `https://mm.qentra.top` | ✅ **сделано 2026-09-26** (бэкап → скрипт → `compose up -d` → проверка) |
|
||
| Ping через туннель `https://mm.qentra.top/api/v4/system/ping` | ✅ `{"status":"OK"}` |
|
||
| `mmctl config get ServiceSettings.SiteURL` | ✅ `"https://mm.qentra.top"` |
|
||
| Приёмка входом в браузере | ⏳ **делает Alex** |
|
||
|
||
### Как менялся SITEURL (2026-09-26, выполнено)
|
||
|
||
Порядок по правилу «коммит ДО → правка → заливка → проверка»:
|
||
|
||
1. **Бэкап compose** на хосте: `cp docker-compose.yml backups/docker-compose.yml.bak-$(date +%Y%m%d-%H%M%S)`
|
||
→ `/home/kraken/mattermost/backups/docker-compose.yml.bak-20260926-103937`.
|
||
2. **Правка скриптом, не sed.** `/tmp/mm-deploy/set-siteurl.py` (локально на Mac) →
|
||
`ssh kraken 'cat > /tmp/set-siteurl.py' < set-siteurl.py` → `ssh kraken 'python3 /tmp/set-siteurl.py'`.
|
||
Скрипт идемпотентен (NOOP при совпадении), пишет во временный файл и делает атомарный `replace`.
|
||
Вывод: `OK: MM_SERVICESETTINGS_SITEURL: "http://192.168.1.14:8065" -> MM_SERVICESETTINGS_SITEURL: "https://mm.qentra.top"`.
|
||
3. **Перезапуск:** `ssh kraken 'cd /home/kraken/mattermost && docker compose up -d mattermost'`
|
||
→ `mattermost Recreated → Started`, `mm-postgres Healthy`.
|
||
4. **Ожидание health** (`docker inspect mattermost --format '{{.State.Health.Status}} restarts={{.RestartCount}}'`):
|
||
`starting` ×8 итераций по 5 с → **`healthy restarts=0`** на 9-й (~45 с).
|
||
5. **Проверка с Mac:** `curl -s https://mm.qentra.top/api/v4/system/ping` → `{"status":"OK",...}`;
|
||
`docker exec mattermost mmctl --local config get ServiceSettings.SiteURL` → `"https://mm.qentra.top"`.
|
||
|
||
⚠️ `sed -i` из старой инструкции ниже **не использовать** — он оставлен как история. Правка
|
||
compose — только скриптом с бэкапом.
|
||
⚠️ При пересборке compose из генератора SITEURL перезапишется на LAN-значение. Долгосрочно —
|
||
менять значение в генераторе, а не в файле.
|
||
|
||
⚠️ **Приёмка = вход в `https://mm.qentra.top` в браузере**, не «curl отвечает».
|
||
Приёмка `SITEURL` и техническая проверка через туннель — сделаны (см. таблицу выше).
|
||
|
||
**Осталось одно:** Alex открывает `https://mm.qentra.top` в браузере и логинится `admin`.
|
||
|
||
**Пароль админа получен 2026-09-26** и передан Alex'ом в чат (значение в доке не хранится —
|
||
только путь к файлу). Если понадобится снова:
|
||
|
||
```bash
|
||
# Забрать пароль админа
|
||
ssh kraken 'sudo cat /srv/dev-disk-by-uuid-49e8f586-3839-4c5d-a1e1-58bfc3579ade/docker/mattermost/config/.adminpw'
|
||
# Вывод вида: PW=<28 символов> ← в контейнере файл лежит без префикса
|
||
```
|
||
|
||
⚠️ Файл принадлежит uid **2000** и лежит в `config/` — читать только через `sudo -n`,
|
||
и только при наличии `< /dev/null` в ssh-команде (иначе `Connection closed by remote host`).
|
||
|
||
### Проверка, что всё живо (одна команда)
|
||
|
||
```bash
|
||
ssh kraken "docker inspect mattermost --format '{{.State.Health.Status}} restarts={{.RestartCount}}' && curl -s http://localhost:8065/api/v4/system/ping" < /dev/null
|
||
curl -s -m 20 https://mm.qentra.top/api/v4/system/ping # с Mac, через туннель
|
||
```
|
||
Ожидаемо: `healthy restarts=0` + `{"status":"OK",...}` в обоих случаях.
|
||
|
||
|
||
## Связанные заметки
|
||
|
||
- [[personal/projects/balda/balda-mm-delegation-plan]] — общий план MM + делегирования
|
||
- [[family/how-to/kraken-access]] — доступ к Kraken, cloudflared, порты
|
||
- [[family/how-to/openmediavault-rpi5]] — структура Docker на Kraken
|
||
- [[personal/tech/docker-data-root-mount-race]] — почему том на HDD, а не на SD
|
||
- [[personal/projects/balda/balda-mattermost-connector]] — коннектор MM для Балды
|