[2026-09-20] eagle: family/how-to/kraken-access.md family/plans/kraken-backup-to-truenas.md personal/tech/docker-data-root-mount-race.md

This commit is contained in:
Alexey Martemyanov committed 2026-09-20 11:44:53 +06:00
1 parent d295d69a4c
commit 49e1c1a11f
3 files changed
+96 -2

No files matched your search

+7 -1
View File
@@ -57,7 +57,13 @@
> 4. ✅ `/etc/fstab`: убран дубль строки для `6194539b` + дубль bind-mount `/var/log/journal`. Осталось 1 вхождение (OMV-блок). Проверено грепом.
> 5. ✅ Sanity `systemctl restart docker` после правок → `containers=25`, 11-12 Up. Критичные поднялись сами.
>
> ⚠️ `hermes-kraken` — `Restarting (1)` (crash-loop, **не связан** с этой задачей, был до правок).
> ✅ **`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`.
>
+58 -1
View File
@@ -1,9 +1,66 @@
# Kraken → TrueNAS — автобэкап стека
> **Создано:** 2026-09-20
> **Статус:** 📋 ПЛАН СОГЛАСОВАН (вариант A + исключение jellyfin-метаданных), к реализации
> **Обновлено:** 2026-09-20 (реализация: шаги 1-3 ✅, шаг 6 упёрся в права root — см. «СТАТУС РЕАЛИЗАЦИИ»)
> **Статус:** 🔄 В РАБОТЕ — шаги 1,2,3 сделаны; прогон падает на `Permission denied` (нужен sudo). Шаги 4,5,7 — не сделаны.
> **Задача:** бэкап стека Kraken складывать на TrueNAS в `/mnt/RED_2TB/backup/kraken/`, откуда он автоматически уезжает в Mail.ru Cloud существующим rclone-скриптом TrueNAS (**скрипт не трогаем**).
## 🔄 СТАТУС РЕАЛИЗАЦИИ (2026-09-20)
| # | Шаг | Статус | Факт |
|---|---|---|---|
| 1 | SSH-ключ `kraken-backup-push` + pub на TrueNAS | ✅ | Ключ на Kraken `~/.ssh/kraken-backup-push` (ed25519, fp `SHA256:hpatuPO7Yyf+/OryR1dokDmmUXAY7Ei4dWvL1pfsvLQ`). Pub записан в **`sshpubkey` юзера `nas` (id=72)** через `midclt call user.update 72`. **Проверено живьём:** `ssh -i kraken-backup-push nas@mallexxx.duckdns.org` → `CONNECTED_AS=nas`. |
| 2 | Датасет `/mnt/RED_2TB/backup/kraken` | ✅ | Создан через `midclt call pool.dataset.create` (NFSV4/PASSTHROUGH). Права `1000:1000` (nas) `770` — выставлены через **`midclt call filesystem.setperm`** (обычный `chown` от truenas_admin → `Operation not permitted`). Проверено: Kraken пишет туда (WRITE_OK/CLEANUP_OK). |
| 3 | Скрипт на Kraken | ✅ | `/home/kraken/kraken-backup-to-truenas.sh` (755, `bash -n` OK). Лог `~/kraken-backup.log`, err `~/kraken-backup.err`. |
| 4 | Ротация 14 архивов | ⏸ | В скрипте реализована (rm на стороне TrueNAS), но живьём не проверена — прогон не дошёл. |
| 5 | Крон `0 4 * * *` | ⏸ | Не поставлен (ждём рабочий прогон). |
| 6 | Живой прогон | ❌ | **БЛОКЕР: права.** См. раздел ниже. |
| 7 | Выключить старый Kraken→Mail.ru | ⏸ | Не сделано. |
## ❌ БЛОКЕР ШАГА 6: tar под юзером `kraken` не читает root-каталоги
**Симптом:** `tar: docker/portainer/certs: Cannot open: Permission denied` (и по другим каталогам) → `Exiting with failure status` → скрипт ветка `exit 2`, архив удалён, на TrueNAS ничего не ушло.
**Факт (владельцы подпапок `/srv/...6194539b.../docker/`):**
| Владелец | Каталоги |
|---|---|
| **root (0:0)** | `cloudflared`, `hermes-build`, `hermes`, `homeassistant`, `jellyfin`, `portainer`, `rclone`, `scripts`, `watchtower` |
| **1000:1000 (kraken)** | `media-pipeline`, `prowlarr`, `radarr`, `sonarr`, `transmission` |
**Следствие:** `tar` от `kraken` покрывает только половину стека. Критичные для бэкапа `hermes` (sessions/.env/auth.json), `portainer`, `rclone/config` — **root-owned**, недоступны.
**Обходной путь (проверен частично):** на Kraken **`sudo -n` работает без пароля** (`SUDO_NOPASSWD_OK` — NOPASSWD-правило есть). Значит tar можно запускать через `sudo -n`.
**Развилка решения (не закрыто):**
- **A.** `sudo -n tar` → затем `sudo -n chown kraken <архив>` перед `rsync`.
- **B.** `sudo -n tar`, архив в `/tmp` с `chmod 644` (читаем всем) → `rsync` от `kraken` его читает.
- **C. (рекомендовано)** Весь скрипт под `sudo -n` из cron: `sudo -n /home/kraken/kraken-backup-to-truenas.sh`; тогда `rsync` тоже от root, а владельца на TrueNAS выставить `rsync --chown=nas:nas` (в текущей версии скрипта стоит `--no-o --no-g`, при root-rsync надо `--chown=nas:nas`, иначе файл ляжет `root:root` и `nas` его перезапишет через `770` группы — но владелец будет root).
> ⚠️ **Питфолл (новый):** при root-сборке архива ОБЯЗАТЕЛЬНО `rsync --chown=nas:nas`, иначе архив на TrueNAS окажется `root:root`. Ротация (`rm` старых) от `nas` их не удалит (файлы root), поэтому ротацию тоже придётся делать под sudo либо чинить владельца при заливке.
**Команда проверки (была заблокирована по таймауту апрува, безопасна — только чтение):**
```bash
ssh kraken 'sudo -n tar czf /tmp/_t.tar.gz -C /srv/dev-disk-by-uuid-6194539b-03e0-4f33-986b-4da09834e82f/docker portainer; ls -la /tmp/_t.tar.gz; sudo -n rm -f /tmp/_t.tar.gz'
```
## 📦 Факты для реализации (собраны 2026-09-20)
- **Размер стека:** `/srv/.../docker` = **2.2G**, без jellyfin и `*.log` = **603M** (сжимается).
- **`/tmp` на Kraken** = tmpfs 2.0G → места под архив хватает.
- **rsync/tar на Kraken** есть: `/usr/bin/rsync`, `/usr/bin/tar`.
- **Внешний IP Kraken** = `92.62.70.9` (⚠️ **динамический** — в доке фигурировал `.41`).
**Следствие:** `from=` в `authorized_keys` для push-ключа **не ставить** — IP сменится и бэкап умрёт. Ограничить только `no-pty,no-port-forwarding,no-agent-forwarding,no-X11-forwarding`. (Через API `user.update` ограничения всё равно не выставить — ключ пишется «голым»; если нужны ограничения, править `~nas/.ssh/authorized_keys` под root.)
- **Бэкап-скрипт на Mac (рабочая копия):** `/Users/admin/kraken-recovery/kraken-backup-to-truenas.sh`.
- **Дампы перед изменениями (TrueNAS):** `/home/truenas_admin/kraken-backup-setup-20260919-223211/` — `nas-user-before.json`, `cronjobs-before.json`, `smb-before.json`.
⚠️ `/mnt/RED_2TB/backup/` — `root:root`, `truenas_admin` **не может** создавать там папки (бэкапы держать в `~`).
## Питфоллы TrueNAS, подтверждённые в этой сессии
- `chown`/`chmod` от `truenas_admin` на root-owned датасете → `Operation not permitted`. **Рабочий путь:** `midclt call filesystem.setperm '{"path":...,"mode":"770","uid":1000,"gid":1000}'` (возвращает job id, применяется асинхронно — подождать 3-5 с и проверить `ls -ldn`). Это **не требует root-шелла**, в отличие от `chown`.
- `midclt call pool.dataset.create` с `aclmode=PASSTHROUGH` + `acltype=NFSV4` — сработало (обход питфолла №3 из [[family/plans/t610-backup-to-truenas]] про DISCARD).
- Ключ юзера `nas` класть через **`midclt call user.update 72 '{"sshpubkey":"..."}'`** — TrueNAS сам создаёт `~nas/.ssh/authorized_keys` с правильными правами, обходя питфолл №9 (непроходимые родители `~nas`).
## Почему появилась задача
2026-09-20 при разборе падения `vless reverse proxy` и `cloudflared ssh tunnel` выяснилось:
@@ -133,3 +133,34 @@ docker info --format "containers={{.Containers}} running={{.ContainersRunning}}"
Кратко: boot 17:29:17, dockerd 17:31:01, диск 17:31:27 (на 26 с позже) → 0
контейнеров. Фикс: drop-in `RequiresMountsFor=/srv/.../6194539b.../docker-data`
+ чистка fstab. Бэкап: `/home/kraken/backups/docker-recovery-20260920-105228/`.
## Смежная ловушка: смешанные владельцы под data-root
Бэкап (или tar) дерева сервисов на том же диске часто упирается в **разных владельцев** —
часть каталогов `root:root` (созданы root-контейнерами), часть `1000:1000`.
Бэкап от обычного юзера получит `Permission denied` на root-каталогах и **молча
сделает неполный архив**.
```bash
sudo ls -ldn /srv/dev-disk-by-uuid-<DISK>/docker/*/ | awk '{print $3, $4, $NF}'
```
Если `sudo -n true` проходит (NOPASSWD есть) — архивировать под `sudo -n`, а на приёмной
стороне выставить владельца бэкап-юзера: `rsync --chown=<user>:<group>` (не голое копирование,
иначе файл ляжет `root:root`).
**ПИТФОЛЛ — длинный `chown -R`/архив умирает вместе с SSH-сессией.** Рекурсивный `chown`
(или большой `tar`) по медленному USB-HDD идёт минутами и убивается при обрыве SSH.
Запускать отвязанно:
```bash
sudo systemd-run --unit=<name> --collect --property=Type=oneshot chown -R 1000:1000 <path>
systemctl is-active <name> # 'activating' = ещё идёт, доживёт до конца независимо от SSH
```
Тем же приёмом — любую долгую удалённую задачу через флаки-линк.
**ПИТФОЛЛ — контейнер в `Restarting (1)` с `mkdir: Permission denied` на своём каталоге данных** —
тот же класс багов, но наоборот: UID владельца на хосте не совпадает с UID, под которым
работает контейнер (образ `USER` или compose `user:`). Лечится `chown <container-uid>:<gid> <host-dir>`.
Ожидаемый UID: `docker inspect <name> --format '{{.Config.User}}'`.
Первые несколько `mkdir`-ошибок могут быть **остатком буфера** от прежнего падения — прежде
чем объявлять провал, убедиться, что контейнер реально держится `Up`.