diff --git a/family/how-to/kraken-access.md b/family/how-to/kraken-access.md index 23f0950b..073e7740 100644 --- a/family/how-to/kraken-access.md +++ b/family/how-to/kraken-access.md @@ -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 ` — юнит переживёт обрыв 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`. > diff --git a/family/plans/kraken-backup-to-truenas.md b/family/plans/kraken-backup-to-truenas.md index 18e39702..bd42db71 100644 --- a/family/plans/kraken-backup-to-truenas.md +++ b/family/plans/kraken-backup-to-truenas.md @@ -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` выяснилось: diff --git a/personal/tech/docker-data-root-mount-race.md b/personal/tech/docker-data-root-mount-race.md index 412cae20..662e910f 100644 --- a/personal/tech/docker-data-root-mount-race.md +++ b/personal/tech/docker-data-root-mount-race.md @@ -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-/docker/*/ | awk '{print $3, $4, $NF}' +``` + +Если `sudo -n true` проходит (NOPASSWD есть) — архивировать под `sudo -n`, а на приёмной +стороне выставить владельца бэкап-юзера: `rsync --chown=:` (не голое копирование, +иначе файл ляжет `root:root`). + +**ПИТФОЛЛ — длинный `chown -R`/архив умирает вместе с SSH-сессией.** Рекурсивный `chown` +(или большой `tar`) по медленному USB-HDD идёт минутами и убивается при обрыве SSH. +Запускать отвязанно: +```bash +sudo systemd-run --unit= --collect --property=Type=oneshot chown -R 1000:1000 +systemctl is-active # 'activating' = ещё идёт, доживёт до конца независимо от SSH +``` +Тем же приёмом — любую долгую удалённую задачу через флаки-линк. + +**ПИТФОЛЛ — контейнер в `Restarting (1)` с `mkdir: Permission denied` на своём каталоге данных** — +тот же класс багов, но наоборот: UID владельца на хосте не совпадает с UID, под которым +работает контейнер (образ `USER` или compose `user:`). Лечится `chown : `. +Ожидаемый UID: `docker inspect --format '{{.Config.User}}'`. +Первые несколько `mkdir`-ошибок могут быть **остатком буфера** от прежнего падения — прежде +чем объявлять провал, убедиться, что контейнер реально держится `Up`.