Files
obsidian-vault/family/how-to/kraken-access.md
T

252 lines
25 KiB
Markdown
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.
# Kraken — Внешний доступ
> # ✅ 2026-09-20 — НАЙДЕНА ИСТИННАЯ ПРИЧИНА (2026-09-15 поломки): ГОНКА Docker ↔ монтирование USB-диска
>
> **Симптом 2026-09-15:** одновременно упали (1) reverse-туннель Kraken→TrueNAS (`kraken-user`) и (2) cloudflared SSH-туннель `ssh-kraken.qentra.top`. Обе поломки — **одна причина**, не две.
>
> ### Таймлайн последнего boot (Kraken)
> | Событие | Время |
> |---|---|
> | Boot | Sep 15 **17:29:17** |
> | Ядро видит `sda1` (USB HDD) | +130 с → **17:31:27** |
> | **`dockerd` стартовал** (PID 1324) | **17:31:01** ← на **26 с раньше** диска |
> | Диск смонтирован | **17:31:27** |
>
> **Механизм:** `/etc/docker/daemon.json` → `data-root` = `/srv/dev-disk-by-uuid-49e8f586-.../docker-data` (симлинк на `6194539b-...`). `docker.service` **не имеет `RequiresMountsFor=`** → не ждёт монтирования. dockerd на 17:31:01 открыл путь до появления диска → увидел пусто → загрузил **0 контейнеров**; через 26 с диск смонтировался поверх точки, но daemon уже держал пустое состояние в памяти. Перечитывания нет.
>
> ### Что НЕ потеряно (проверено живьём)
> - `docker-data/containers/` = **4.7G, 35 контейнеров целы** (cloudflared, xray-reverse-bridge, jellyfin, sonarr, radarr, prowlarr, transmission, homeassistant, hermes-kraken, portainer, rclone, watchtower, flaresolverr, timemachine).
> - Образы (21), 32 volume — на месте. Диск `sda1` 1.8T смонтирован, 242G свободно.
> - `docker info` при этом рапортует `Containers=0` — **не верь ему, metadata на диске цела**.
>
> ### Побочные дефекты, найденные при разборе
> 1. **`/etc/fstab` содержит ДУБЛЬ** строки для `6194539b`: OMV-блок + ручная строка `UUID=6194539b-...` → systemd: `Duplicate entry in '/etc/fstab'?`.
> 2. **`docker.service`: `RequiresMountsFor=` пусто** — корень гонки. Drop-in нужен, чтобы dockerd ждал диск.
> 3. ~~RestartPolicy=null → нужен ручной `docker start`~~ ❌ **ЭТО БЫЛО ОШИБОЧНОЕ ДОПУЩЕНИЕ.** Я прочитал `RestartPolicy.Name` из `hostconfig.json` (это reflection исходного `docker run`, там `null`), но в daemon state policy **есть**. Факт: после `systemctl restart docker` все 12 контейнеров с `unless-stopped` поднялись **сами**, ручной `docker start` не потребовался. Проверять политику надо `docker inspect <name> --format '{{.HostConfig.RestartPolicy.Name}}'` (живой daemon), а не `hostconfig.json` на диске.
>
> ### Команды диагностики (воспроизводимые)
> ```bash
> ssh kraken 'docker ps -a --format "{{.Names}}\t{{.Status}}"' # пусто → daemon state пуст
> ssh kraken 'ps -o pid,lstart,cmd -p $(pgrep -x dockerd)' # реальное время старта dockerd
> ssh kraken 'systemctl show docker -p RequiresMountsFor' # пусто = нет зависимости от mount
> ssh kraken 'uptime -s; findmnt /srv/dev-disk-by-uuid-6194539b-...' # когда boot / когда диск
> ssh kraken 'sudo ls /srv/dev-disk-by-uuid-6194539b-.../docker-data/containers | wc -l' # 35 = целы
> ```
> Питфолл: `systemctl show docker -p ExecMainStartTimestamp` дал stale `15:18:36`; **истинное время старта — `ps -o lstart`** = 17:31:01.
>
> ### План восстановления (согласован 2026-09-20)
> 1. Backup: `/etc/fstab`, `/etc/docker/daemon.json`, `/home/kraken/xray-reverse/` → `/home/kraken/backups/docker-recovery-<date>/`.
> 2. **`sudo systemctl restart docker`** — диск уже смонтирован → daemon прочитает все 35 контейнеров.
> 3. `docker start` по списку — ⚠️ **НЕ ПОТРЕБОВАЛСЯ**: оказалось, RestartPolicy в daemon state есть (`unless-stopped`), все 12 контейнеров поднялись автоматически после рестарта daemon. `hostconfig.json` на диске показывает `null` — это НЕ отражает реальную политику. Проверять через живой daemon: `docker inspect --format '{{.HostConfig.RestartPolicy.Name}}'`.
> 4. Правка боевого: убрать дубль fstab + drop-in `RequiresMountsFor=/srv/dev-disk-by-uuid-6194539b-...` — **только с апрувом Alex**. Порядок: коммит ДО → правка → заливка → проверка Alex → коммит ПОСЛЕ.
> 5. Проверка: `docker ps` все Up; reverse-канал `udp:reverse:0`; cloudflared tunnel.
> 6. Версии Xray на бридже: оставить `26.4.25` + `network_mode: host` (проверенный фикс 2026-09-02) — открытый вопрос к Alex.
>
> **Статус (обновлено 2026-09-20): ✅ ФИКС ПРИМЕНЁН, ПРОВЕРЕН РЕАЛЬНЫМ REBOOT'ом, REBOOT-БЕЗОПАСЕН.**
>
> **Подтверждение живым reboot'ом (2026-09-20):** Alex выключил Kraken (`sudo shutdown -h now`), включил — **все 12 контейнеров поднялись сами** (`docker ps` не пуст). Ручных `docker start` не потребовалось. Гонка закрыта.
>
> **Полный разбор механики и фикса:** [[personal/tech/docker-data-root-mount-race]].
>
> ⚠️ **После reboot `jellyfin` поднялся, но отдавал 503** — это **отдельный** дефект (образ 10.11.8 старше схемы БД 12.1.0.0, `no such column: p.Permission_Permissions_Guid`), НЕ связанный с гонкой. См. [[personal/tech/jellyfin-db-schema-mismatch]]. **Различать при диагностике:** контейнер не поднялся (гонка) vs поднялся, но падает внутри (своя ошибка).
>
> **Что сделано:**
> 1. ✅ Backup: `/home/kraken/backups/docker-recovery-20260920-105228/` (fstab, daemon.json, xray-reverse).
> 2. ✅ `sudo systemctl restart docker` → все 25 контейнеров загрузились, 12 поднялись автоматом (`cloudflared`, `xray-reverse-bridge` в т.ч.).
> 3. ✅ Drop-in `/etc/systemd/system/docker.service.d/mounts.conf`:
> ```ini
> [Unit]
> RequiresMountsFor=/srv/dev-disk-by-uuid-6194539b-03e0-4f33-986b-4da09834e82f/docker-data
> After=srv-dev\x2ddisk\x2dby\x2duuid\x2d6194539b\x2d03e0\x2d4f33\x2d986b\x2d4da09834e82f.mount
> ```
> Проверено: `systemctl show docker -p Requires -p After` → маунт-юнит в **Requires И After**. dockerd теперь физически не стартует раньше диска → **гонка закрыта**.
> 4. ✅ `/etc/fstab`: убран дубль строки для `6194539b` + дубль bind-mount `/var/log/journal`. Осталось 1 вхождение (OMV-блок). Проверено грепом.
> 5. ✅ Sanity `systemctl restart docker` после правок → `containers=25`, 11-12 Up. Критичные поднялись сами.
>
> ✅ **`hermes-kraken` — crash-loop РАЗОБРАН И ПОЧИНЕН (2026-09-20, отдельная от гонки причина).**
> **Симптом:** `Restarting (1)` циклично, в логах `mkdir: cannot create directory '/opt/data': Permission denied`.
> **Причина:** контейнер работает под **UID 1000:1000** (из образа, `user:` в compose не задан), `HOME=/opt/data` ← bind-mount из `/srv/.../docker/hermes/config`. На хосте этот каталог имел владельца **`10000:10000`** mode `700` (аномалия — UID 10000 больше нигде в `docker/` не встречается; похоже след от перерегистрации диска 15.09). 1000 ≠ 10000 + mode 700 → `Permission denied`.
> **Фикс:** `sudo chown 1000:1000 …/docker/hermes/config` (сначала на верхнем уровне, затем `-R`).
> ⚠️ **Питфолл:** `chown -R` по большому дереву на медленном USB-диске **не влезает в SSH-таймаут**. Запускать независимо: `sudo systemd-run --unit=hermes-chown --collect --property=Type=oneshot chown -R 1000:1000 <path>` — юнит переживёт обрыв SSH. Проверка: `systemctl is-active hermes-chown`.
> **Результат:** контейнер `Up`, логи — чистый старт Gateway, API отвечает `404` на `/` (норма, рабочий эндпоинт `/v1/chat/completions`).
> 📌 Три `mkdir: Permission denied` в начале лога после фикса — **остаток буфера от прошлого падения**, не текущие ошибки.
>
> **Питфолл (опровергнут):** `RestartPolicy` в `hostconfig.json` = `null`, но в daemon state policy есть → при рестарте daemon контейнеры **поднимаются сами**. Не пугаться `null`, не нужен ручной `docker start`.
>
> **Урок на будущее:** USB-диск на Kraken поднимается ~130 с. Любой сервис, зависящий от `docker-data` на этом диске, ОБЯЗАН иметь `RequiresMountsFor`. Теперь это обеспечено для docker.service.
> # 🔴 2026-09-15 — KRAKEN НЕДОСТУПЕН ПО ВСЕМ ПУТЯМ (проверено с Mac)
>
> ⚠️ **ВНИМАНИЕ (обновлено 2026-09-20):** вывод этой секции был **ЧАСТИЧНО НЕВЕРЕН**. «Kraken недоступен» относилось к внешним путям (cloudflared `ssh-kraken.qentra.top`, WG `10.99.1.2`) — оба были down не из-за выключенной машины Kraken, а из-за **гонки docker↔mount** (см. секцию выше): `cloudflared`-контейнер просто не загрузился daemon'ом. По **LAN Kraken был жив всю дорогу** (`192.168.1.15`). Урок: «недоступен снаружи» ≠ «машина мертва» — проверять LAN и состояние docker daemon, прежде чем звонить провайдеру.
>
> **Симптом:** перестал работать клиент `kraken-user` в 3x-ui на TrueNAS. При разборе выяснилось — причина **не в БД и не в конфиге**, а в том, что **сам хост Kraken недоступен**.
>
> | Путь | Команда | Результат |
> |---|---|---|
> | Cloudflare SSH | `ssh ssh-kraken.qentra.top` | ❌ `Connection timed out during banner exchange` |
> | WireGuard Direct | `ssh kraken@10.99.1.2:22` | ❌ `Operation timed out` |
> | DNS | `host kraken` | ❌ `NXDOMAIN` (в `/etc/hosts` записи нет) |
>
> **Состояние TrueNAS-стороны (жива):** `xray-reverse-portal` Up 13 дней, порты `12345` (SOCKS) и `12346` (interconn) в LISTEN, конфиг `/mnt/RED_2TB/docker/reverse-portal/config.json` цел.
>
> **Диагностика, доказывающая «бридж молчит»:**
> ```bash
> docker exec xray-reverse-portal netstat -an | grep 12346 # пусто = от Kraken никто не звонит
> curl -s --max-time 20 --socks5-hostname 127.0.0.1:12345 https://api.ipify.org # пусто (ждём 92.62.70.41)
> ```
> Логи портала при этом содержат `app/dispatcher: non existing outTag: reverse-out` — **это не битый конфиг**, а ожидаемое следствие: `reverse-out` создаётся Xray'ем автоматически только пока бридж подключён (объявлен в `inbounds[interconn].clients[0].reverse.tag`, не в `.outbounds`).
>
> **Вывод:** нужно проверять Kraken со стороны панели хостинга/консоли провайдера — машина выключена либо сетевая недоступность. Решение Alex: **«подождём, может оживет»**.
>
> ⚠️ **Урок:** перед тем как искать причину в конфиге TrueNAS, проверять доступность самого Kraken. Полдня ушло на разбор 3x-ui, который к этой поломке отношения не имел.
>
> **Обновлено: 2026-09-04**
> Updated: 2026-09-01 23:00 — диск/докер кракена восстановились после перезагрузки.
## Как зайти
Везде `ssh kraken`. Резолвится через `~/.ssh/config` на Eagle:
- **Дома** — напрямую по LAN (`192.168.1.15`)
- **Снаружи** — ⚠️ через WireGuard (`10.99.1.2`) **БОЛЬШЕ НЕ РАБОТАЕТ**: VPS-узло (10.99.0.1/10.99.1.1) qentra.top **удалили 2026-09-01**. Альтернатива внешнего доступа обсуждается через Xray reverse (см. [[tech/xray-reverse-tunnel-kraken-truenas]]).
> ⚠️ **Kraken-диск был нестабилен 2026-09-01**: USB-диск TOSHIBA в цикле enumeration (`device descriptor read/64 error -110`, `device not accepting address error -62`, `[sda] Read Capacity(10) failed` → `0 B`), Kraken перезагружался (`System is booting up / pam_nologin`). **После повторной перезагрузки диск зарегистрировался**: `sda` 1.8T, `sda1` смонтирован в `/srv/dev-disk-by-uuid-6194539b-...`. Docker стал `active`.
> ⚠️ **Kraken Docker на 2026-09-01:** Server 29.4.3, Docker Root Dir `/srv/dev-disk-by-uuid-6194539b-03e0-4f33-986b-4da09834e82f/docker-data`. **30 образов сохранены, но 0 контейнеров** (all прежние контейнеры — jellyfin/transmission/radarr/hermes-kraken и т.д. — потеряны и НЕ пересозданы после сбоя; data-root на HDD был в цикле enumeration). При необходимости — пересоздать из образов.
## Portainer (локально)
`http://kraken:9000`
API:
```bash
BASE=http://localhost:9000
KEY="ptr_AJY+Ba9A7f6pcAHZfDD5koU4stkKgJCdbTEXLDLxn0g="
EP=3 # endpoint ID на Кракене = 3, не 1!
```
## Hermes API
`http://kraken:8642/v1/chat/completions` — OpenAI-compatible endpoint.
## Reverse Xray bridge (развёрнут 2026-09-01)
- Контейнер **`xray-reverse-bridge`** в `/home/kraken/xray-reverse/` (compose + `config.json`), образ **`teddysun/xray:26.4.25`** + `network_mode: host` (проверенный фикс 2026-09-02).
- Outbound VLESS+REALITY TCP → `mallexxx.duckdns.org:12346` (TrueNAS через OpenWrt DNAT), reverse tag `reverse-in`, egress freedom с `finalRules:[{action:allow}]`.
- ✅ **End-to-end payload работает** (верифицировано 2026-09-02: SOCKS `12345` → egress IP Kraken `92.62.70.41`). Полная история диагностики (регрессии #6612/#6242, docker bridge/NAT) — в [[tech/xray-reverse-tunnel-kraken-truenas]].
- ⚠️ 2026-09-15…20 контейнер был **не запущен** (гонка docker↔mount, см. шапку) — это единственная причина «туннель down». После рестарта daemon — снова Up.
## Docker контейнеры (обновлено 2026-09-20, после фикса)
✅ **daemon видит все контейнеры. `containers=25`, 12 Up** (после восстановления гонки docker↔mount, см. шапку). Все 35 metadata-записей в `docker-data/containers/` целы.
| Имя | Статус 2026-09-20 | Заметки |
|-----|---------|---------|
| xray-reverse-bridge | ✅ Up | образ `teddysun/xray:26.4.25`, `network_mode: host` (фикс 2026-09-02) |
| cloudflared | ✅ Up | `cloudflare/cloudflared:latest`, `network_mode: host`, remote-managed по TUNNEL_TOKEN |
| jellyfin / sonarr / radarr / prowlarr | ✅ Up | |
| portainer / transmission / rclone / watchtower / flaresolverr | ✅ Up | |
| hermes-kraken | ✅ Up | crash-loop вылечен 2026-09-20 (см. ниже) |
| homeassistant | ⏹ `Exited (128)` 2 месяца назад | не поднимался, отдельная задача |
| timemachine + alpine-* (6 шт.) | ⏹ Exited (0) | одноразовые/диагностические, норма |
> **Питфолл про RestartPolicy (проверено):** `hostconfig.json` на диске показывает `RestartPolicy.Name = null`, но в daemon state политика `unless-stopped` есть → контейнеры поднимаются сами. Не пугаться `null`, не нужен ручной `docker start`. Проверять живой daemon: `docker inspect --format '{{.HostConfig.RestartPolicy.Name}}' <name>`.
## ✅ hermes-kraken — crash-loop вылечен 2026-09-20 (права каталога)
> **Симптом:** `hermes-kraken` в вечном `Restarting (1)`. Логи циклично:
> `mkdir: cannot create directory '/opt/data': Permission denied`.
>
> **НЕ связано** с гонкой docker↔mount (контейнер был запущен, но падал сам).
| Что | Значение |
|---|---|
| Контейнер работает под | **UID 1000:1000** (`kraken`, задан в образе, не в compose) |
| `/opt/data` примонтирован из | `…/docker/hermes/config` |
| Владелец на хосте (было) | **10000:10000**, `drwx------` (700), mtime **Sep 15 15:37** |
| Итог | 1000 ≠ 10000 + mode 700 → `Permission denied` на `mkdir /opt/data` |
**Корень:** каталог `docker/hermes/config` после перерегистрации диска (15 сен) получил **чужой UID 10000** — след старого OMV-маппинга. `10000` больше нигде в `…/docker/` не встречается: все прочие каталоги либо `root:root`, либо `1000:1000`. Данные (`.env`, `auth.json`, `sessions/`, `cron/`, `response_store.db`) были целы — только владелец не тот.
**Фикс:**
```bash
# top-level — мгновенно
sudo chown 1000:1000 /srv/dev-disk-by-uuid-6194539b-.../docker/hermes/config
# рекурсивно — долго (медленный USB-HDD), запускать в фоне, НЕ блокируясь на SSH:
sudo systemd-run --unit=hermes-chown --collect --property=Type=oneshot \
chown -R 1000:1000 /srv/dev-disk-by-uuid-6194539b-.../docker/hermes/config
```
Проверка: `sudo find …/docker/hermes -uid 10000` → пусто; контейнер `Up`; API `http://localhost:8642/` отвечает (`404` на `/` — норма, эндпоинт `/v1/chat/completions`).
**Питфоллы:**
- `chown -R` по большому дереву на USB-HDD идёт **минуты** → **обычный ssh-вызов рвётся по таймауту и правка НЕ применяется**. Запускать через `systemd-run … --collect` в фоне, потом проверять `systemctl is-active hermes-chown` и `ls -ldn` каталога.
- Логи после старта содержат «хвост» прежних `mkdir` ошибок из буфера — не пугаться, если дальше идёт `Hermes Gateway Starting...` и скиллы синхронизированы.
## Cloudflared туннель — публичные маршруты (состояние на 2026-09-04)
Туннель `kraken` в Zero Trust, remote-managed по `TUNNEL_TOKEN`, `network_mode: host`, образ `cloudflare/cloudflared:latest`. Актуальный ingress конфиг (виден в логах cloudflared на Kraken, `INF Updated to new configuration`):
```
{"ingress":[
{"hostname":"kraken.qentra.top", "originRequest":{"httpHostHeader":"kraken.qentra.top"},"service":"http://localhost:8642"}, ← Hermes API
{"hostname":"ssh-kraken.qentra.top", "service":"ssh://localhost:22"}, ← SSH (published application route)
{"service":"http_status:404"} ← default catch-all
]}
```
⚠️ **Важно (путаница была!):** `kraken.qentra.top` — это **HTTP-маршрут на `localhost:8642` (Hermes API)**, НЕ SSH. Поэтому SSH к `kraken.qentra.top:22` и не шёл. Под SSH создан отдельный hostname **`ssh-kraken.qentra.top`**.
### ✅ SSH-доступ работает через `ssh-kraken.qentra.top` (РАБОЧИЙ, 2026-09-04)
**Вход (проверено, работает):**
```bash
ssh kraken@ssh-kraken.qentra.top
```
`~/.ssh/config` блок:
```
Host ssh-kraken.qentra.top
HostName ssh-kraken.qentra.top
User kraken
ProxyCommand cloudflared access ssh --hostname %h
```
Первый раз — авторизация cloudflared в Access-приложение:
```bash
cloudflared access login ssh-kraken.qentra.top
```
Маршрут на Kraken (UP, контейнер) подхвачен ingress `ssh-kraken.qentra.top → ssh://localhost:22`; sshd на Kraken — `0.0.0.0:22`. DNS проксировано CF (104.21.x / 172.67.x).
### ⚠️ Питфол: почему `ssh.kraken.qentra.top` (двухуровневый) НЕ сработал
Изначально под SSH добавлялся hostname `ssh.kraken.qentra.top` (`ssh.kraken` + `qentra.top`) → `ssh://localhost:22`, + Access Application (type SSH) + Service Token. Вход НЕ заработал. Решающий факт — на edge Cloudflare у этого hostname **НЕТ SSL/TLS-терминации**, в отличие от одиночных subdomain:
```
openssl s_client -connect ssh.kraken.qentra.top:443 → SSL alert 40 (handshake failure), no peer certificate
curl https://ssh.kraken.qentra.top → ssl=1 handshake failure
```
Любой `cloudflared access login/ssh` и Service Token к чистому двухуровневому SSH-hostname не помогают — клиент не доходит даже до авторизации из-за отсутствия edge-TLS.
**Решение, которое сработало:** использовать **одиночный subdomain через дефис** — `ssh-kraken.qentra.top` (покрывается Universal SSL `*.qentra.top`). На нём размещены тот же `published application route` SSH → `ssh://localhost:22` + Access Application. Вошло сразу.
> Почему так: Universal SSL зоны покрывает `*.qentra.top` (1 уровень) и `qentra.top`, но НЕ `*.kraken.qentra.top` (2 уровня). Для 2-уровневого нужен Advanced Certificate — проще взять одиночный subdomain.
### Диагноз IPv4/IPv6 «жив, но шумит» (актуально для обоих hostname)
- В логах cloudflared — часть `Registered tunnel connection ... protocol=quic`, но постоянные `ERR ... write udp [::]->198.41.x.x:7844: sendmsg: network is unreachable`.
- **Причина: на Kraken НЕТ глобального IPv6** (только `::1` и link-local `fe80::`; роутер `192.168.1.1` IPv6 не выдаёт → `ping6 2606:4700::1111` = `Network is unreachable`). cloudflared пробует QUIC и по IPv6 → каналы падают; туннель выживает по IPv4-QUIC.
- **Возможный фикс:** заставить cloudflared QUIC только по IPv4, чтобы убрать IPv6-шум.
### SSH через VPS reverse (независимо от cloudflared)
Остаётся рабочим внешним путём: reverse SSH туннель через VPS `91.207.28.205:2223` (см. ниже), НЕ cloudflared.
## Связанные заметки
- [[kraken-network]] — SSH, WG топология
- [[wireguard-vpn]] — полное описание WG (⚠️ VPS-звено мёртво на 2026-09-01)
- [[kraken-portainer-access]]
- [[openmediavault-rpi5]]
- [[tech/cloudflare-tunnel-ssh-published-route]] — генерализуемое руководство: SSH через Cloudflare Tunnel (1-уровневый subdomain + Universal SSL), питфолы