Files
obsidian-vault/personal/projects/balda/mattermost-kraken-deploy.md
T

424 lines
28 KiB
Markdown
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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).
---
## 🔴 Главная находка: официального 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`».
---
## Бот `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 для Балды