312 lines
32 KiB
Markdown
312 lines
32 KiB
Markdown
# Kraken — Внешний доступ
|
||
|
||
> # ✅ 2026-09-23 — КАНАЛ REVERSE ЖИВ + ТОПОЛОГИЯ УТОЧНЕНА
|
||
>
|
||
> **1. Reverse-туннель работает** (первая проверка после фикса гонки 2026-09-20): SOCKS `xray-reverse-portal:12345` → `92.62.77.233` ≠ TrueNAS `90.189.160.148`. Детали — [[personal/tech/xray-reverse-tunnel-kraken-truenas]].
|
||
>
|
||
> 🔴 **2. IP Кра́кена ИЗМЕНИЛСЯ: `92.62.70.41` → `92.62.77.233`.** Прежний зафиксирован в этом документе и в `truenas-infrastructure.md` — при проверках брать актуальный из живого запроса.
|
||
>
|
||
> **3. Топология Кра́кена (проверено `ip -4 addr` + `ip route`, 2026-09-23):**
|
||
>
|
||
> ```
|
||
> eth0 192.168.1.14 metric 100 ← активный default route
|
||
> wlan0 192.168.1.15 metric 600
|
||
> default via 192.168.1.1 dev eth0 src 192.168.1.14
|
||
> wg0 10.99.1.2/24 (VPS qentra удалён — звено мёртво)
|
||
> ```
|
||
>
|
||
> 🔴 **Кра́кен и Mac (`192.168.1.57`) — СОСЕДИ в одной подсети `192.168.1.0/24`**, шлюз `192.168.1.1` (ASUS RT-AC68U). **`192.168.2.2` (OpenWrt) — это шлюз сети TrueNAS, к сети Mac/Кра́кена отношения не имеет.** Не приписывать OpenWrt сети `192.168.1.x`.
|
||
>
|
||
> ⚠️ Следствие: с Mac до `192.168.2.197` маршрут идёт `gateway: 192.168.1.1` (проверено `route -n get`), а не через OpenWrt. Схема «статический маршрут `192.168.2.0/24 → 192.168.1.15` на ASUS» **работоспособна**, потому что Кра́кен — сосед в той же подсети, и обратный NAT делает он сам. Нюанс интерфейса (`.14` eth0 vs `.15` wlan0) — в [[personal/plans/lan-via-reverse-tunnel]].
|
||
>
|
||
> ⚠️ **ASUS `192.168.1.1` по SSH недоступен** (`Permission denied` → `Connection closed`, порт 22 `closed`). Раньше в доке `home-network-config` вход был описан как `ssh -i ~/.ssh/id_rsa admin@192.168.1.1` — **сейчас не работает**. Правки маршрутов — только через веб-UI.
|
||
>
|
||
> **4. `xray-reverse-bridge` не имеет `inbounds`** — ключи конфига только `log`/`outbounds`/`routing`. Слушающих портов нет. Канал работает на отправку, принять трафик от роутера бриджу нечем. Разбор — [[personal/plans/lan-via-reverse-tunnel]].
|
||
|
||
> # ✅ 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 — диск/докер кракена поднялись после перезагрузки (тогда причина ещё не была известна; истинная — гонка docker↔mount, найдена 2026-09-20).
|
||
|
||
## Как зайти
|
||
|
||
Везде `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]]).
|
||
|
||
> 🔴 **2026-09-15 — ПРИЧИНА НАЙДЕНА 2026-09-20: гонка docker ↔ монтирование диска.** Диск не «нестабилен» — он **монтируется медленно (~130 с)**, dockerd обгоняет. См. секцию выше.
|
||
|
||
> ⚠️ **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 контейнеров** (все прежние контейнеры — jellyfin/transmission/radarr/hermes-kraken и т.д. — не были видны daemon'ом после старта без диска). Причина — та же mount-race, НЕ дефект диска; после фикса 2026-09-20 такого не повторяется.
|
||
>
|
||
> 🔴 **Диск НЕ нестабилен.** Формулировка «USB-диск нестабилен» — ошибочное обобщение разового инцидента 2026-09-01 (циклы USB enumerate, `error -110/-62`, `Read Capacity(10) failed`). Истинная причина проблем с контейнерами — **mount race**: диск монтируется ~130 с, dockerd стартует раньше. Фикс (`RequiresMountsFor`) применён 2026-09-20, проверен живым reboot'ом — все контейнеры поднялись сами. **Не возвращать формулировку «диск нестабилен»** (Alex требовал её убрать, 2026-09-25).
|
||
> Реальные ограничения HDD: 5400rpm, ~95 МБ/с на чтение — лимит механики, не USB и не дефект.
|
||
|
||
## 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). Полная история диагностики (регрессии #6612/#6242, docker bridge/NAT) — в [[tech/xray-reverse-tunnel-kraken-truenas]].
|
||
- ⚠️ 2026-09-15…20 контейнер был **не запущен** (гонка docker↔mount, см. шапку) — это единственная причина «туннель down». После рестарта daemon — снова Up.
|
||
- ✅ **2026-09-23 — канал перепроверен end-to-end после починки гонки. Egress Кра́кена = `92.62.77.233`.**
|
||
> 🔴 **IP Кра́кена ИЗМЕНИЛСЯ:** `92.62.70.41` (все старые записи) → **`92.62.77.233`** (2026-09-23, проверено через SOCKS портала). **Не доверять `92.62.70.41`**, брать актуальный живым запросом.
|
||
- 🔧 **2026-09-23 — в конфиг бриджа добавлен inbound `lan-in`** (`dokodemo-door`, **`listen 0.0.0.0:12345`**, `followRedirect:true`, `sniffing:false`) + routing `lan-in → conn`. Прежде `inbounds` было **`null`** — бридж мог только отправлять в туннель, принимать не мог.
|
||
- ⚠️ **`listen` обязан быть `0.0.0.0`, НЕ `127.0.0.1`.** С `127.0.0.1` REDIRECT-трафик с внешнего адреса не принимается — соединение отбивается мгновенно (`HTTP 000` за ~0.005 с). Ошибка проверена и исправлена.
|
||
- Плюс на хосте Кра́кена: `iptables -t nat PREROUTING -s 192.168.1.0/24 -d 192.168.2.0/24 -p tcp -j REDIRECT --to-ports 12345`.
|
||
- 🏁 **Задача, ради которой это ставилось, — ТУПИК.** Xray reverse намеренно не пускает forward-proxy в приватные сети: портал отдаёт `not allowed to use forward proxy`. Это штатная защита, не баг. Теория: [[personal/tech/xray-reverse-limits-private-network]]. Полное состояние: [[personal/plans/lan-access-via-reverse-tunnel]].
|
||
- ⚠️ **`iptables-persistent` НЕ установлен** — правило REDIRECT слетит при reboot.
|
||
- Бэкапы: `config.json.bak-lan-in-20260923-130541`, `config.json.bak-listen0000-20260923-131655`, `config.json.bak-v3-<date>`, `/home/kraken/backups/iptables.bak-20260923-130541`.
|
||
- ⚠️ **`sudo` на Кра́кене через SSH требует `< /dev/null`** — иначе `Connection closed by remote host`. На Кра́кене **нет `ifconfig`** (использовать `ip addr`).
|
||
|
||
## 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...` и скиллы синхронизированы.
|
||
|
||
> ### 🔁 2026-09-23 — ПОВТОРНЫЙ СИМПТОМ, НАЙДЕН ДРУГОЙ КОРЕНЬ
|
||
>
|
||
> Kraken-агент сообщил: **«нет SSH доступа к хосту»**. Проверка:
|
||
> ```
|
||
> sudo docker exec hermes-kraken ssh kraken-host "echo HELLO"
|
||
> → No user exists for uid 1000
|
||
> ```
|
||
> Это **не про ключ** и **не связано** с crash-loop 2026-09-20. `ssh` падает на `getpwuid(1000)` —
|
||
> **в `/etc/passwd` образа нет записи для uid 1000**, там только `hermes:x:10000:10000`.
|
||
> Контейнер запущен под uid 1000 (из `USER` в образе), т.е. анонимный uid.
|
||
>
|
||
> **Не путать три разных вещи** (все они про uid и все ломались отдельно):
|
||
> | Дефект | Что не так | Фикс | Дата |
|
||
> |---|---|---|---|
|
||
> | crash-loop `mkdir /opt/data: Permission denied` | владелец **файлов** на хосте = `10000:10000` | `chown 1000:1000` на хосте | 2026-09-20 ✅ |
|
||
> | гонка docker↔mount | data-root на USB раньше dockerd | `RequiresMountsFor` drop-in | 2026-09-20 ✅ |
|
||
> | `No user exists for uid 1000` | нет записи uid 1000 в **passwd образа** | завести `kraken` uid 1000 в Dockerfile | 2026-09-23 ⏳ план согласован |
|
||
>
|
||
> **Полный разбор + план фикса (вариант A):** [[family/how-to/hermes-docker-kraken]] → раздел
|
||
> «ПЛАН: фикс uid 1000». Статус: **согласован, НЕ применён**.
|
||
>
|
||
> Дополнительно найдено: 3-й bind-mount `config/.ssh → /root/.ssh` при `HOME=/opt/data` — SSH его не
|
||
> читает; в самом образе есть `entrypoint.sh` с логикой `gosu`/`usermod`, но вся она обёрнута в
|
||
> `if [ "$(id -u)" = "0" ]` и при старте под 1000 **пропускается целиком**.
|
||
|
||
## 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), питфолы
|
||
|