[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 personal/tech/jellyfin-db-schema-mismatch.md personal/tech/skill-docker-data-root-mount-race.md
This commit is contained in:
1 parent
9a29d0ae0c
commit
92a637ccf9
5 files changed
+278
-86
No files matched your search
@@ -81,3 +81,27 @@ After=srv-dev\x2ddisk\x2dby\x2duuid\x2d<UUID>\x2d....mount
|
||||
## Урок
|
||||
|
||||
Любой сервис, зависящий от данных на **медленно монтирующемся диске** (USB-HDD), ОБЯЗАН иметь `RequiresMountsFor`. Без него — гонка при каждом reboot, лотерея.
|
||||
|
||||
## ✅ Фикс подтверждён живым reboot'ом (2026-09-20)
|
||||
|
||||
Alex выключил Kraken (`sudo shutdown -h now`), затем включил. **Все контейнеры поднялись сами** — `docker ps` не пуст (12 Up: `cloudflared`, `xray-reverse-bridge`, `rclone`, `jellyfin`, sonarr/radarr/prowlarr, transmission, portainer, hermes-kraken, flaresolverr, watchtower). Ручных `docker start` не потребовалось.
|
||||
|
||||
**Команда проверки после каждого reboot:**
|
||||
```bash
|
||||
ssh kraken 'docker ps -q | wc -l' # ожидаем ~12, НЕ 0
|
||||
```
|
||||
|
||||
Если 0 — гонка вернулась: `systemctl show docker -p RequiresMountsFor` должен быть непуст.
|
||||
|
||||
> ⚠️ **Не путать с другим дефектом:** после этого reboot `jellyfin` поднялся, но отдавал **503** — это **отдельная** проблема (сервер старше БД, `no such column: p.Permission_Permissions_Guid`), НЕ связанная с гонкой монтирования. См. [[personal/tech/jellyfin-db-schema-mismatch]]. При диагностике «сервис не работает после reboot» различать: контейнер не поднялся (гонка) vs поднялся, но падает внутри (своя ошибка приложения).
|
||||
|
||||
## Крон не догоняет пропущенное окно
|
||||
|
||||
Общий питфолл для сервисов на этом узле: если Kraken лежал, cron-задачи (бэкап `0 4 * * *`, t610-подобные) **пропускаются без компенсации**. Для критичных — добавлять `@reboot`-строку с проверкой «артефакт за сегодня уже есть?».
|
||||
|
||||
## Связанные заметки
|
||||
|
||||
- [[personal/tech/jellyfin-db-schema-mismatch]] — jellyfin после reboot: сервер старше БД (503)
|
||||
- [[family/how-to/kraken-access]] — доступ к Kraken, список контейнеров
|
||||
- [[family/plans/kraken-backup-to-truenas]] — бэкап стека Kraken на TrueNAS
|
||||
- [[personal/tech/xray-reverse-tunnel-kraken-truenas]] — падение reverse-туннеля от той же гонки
|
||||
@@ -0,0 +1,118 @@
|
||||
# Jellyfin: сервер старше БД — `no such column: p.Permission_Permissions_Guid`
|
||||
|
||||
> **Обнаружено:** 2026-09-20 на Kraken (RPi5/OMV, arm64, Docker).
|
||||
> **Статус:** 🔴 **НЕ ЗАФИКСИРОВАНО** — диагноз подтверждён, фикс согласован, ждёт апрува Alex.
|
||||
> **Симптом:** `jellyfin` не подключается (`http://kraken:8096` → **503**), контейнер `Up` но `unhealthy`, health-check бесконечно пишет `Server is could not complete startup. Check logs.`
|
||||
> **Связанные:** [[personal/tech/docker-data-root-mount-race]], [[family/how-to/kraken-access]], [[family/plans/kraken-backup-to-truenas]]
|
||||
|
||||
## Суть (коротко)
|
||||
|
||||
**БД `jellyfin.db` мигрирована под Jellyfin 12.1.0.0, а запущенный контейнер работает на образе 10.11.8.**
|
||||
Сервер 10.11.8 ожидает старую схему таблицы `Permissions` (с колонкой `Permission_Permissions_Guid`), а в 12.x-схеме её больше нет → падение на конструкторе `UserManager` → сервер не стартует.
|
||||
|
||||
Это **не** «миграция не прошла». Наоборот: миграции применены **вперёд**, а сервер откатился **назад**.
|
||||
|
||||
## Диагностика (воспроизводимая)
|
||||
|
||||
```bash
|
||||
# 1) внешний симптом
|
||||
ssh kraken 'docker exec jellyfin sh -c "curl -s -o /dev/null -w \"%{http_code}\n\" http://localhost:8096/"'
|
||||
# → 503
|
||||
|
||||
# 2) статус
|
||||
ssh kraken 'docker ps --format "{{.Names}} {{.Status}}" | grep jelly'
|
||||
# → jellyfin Up ... (unhealthy)
|
||||
|
||||
# 3) ГДЕ ЛОГ: jellyfin пишет в ФАЙЛ, не в stdout!
|
||||
# docker logs пуст по свежему запуску — не путаться.
|
||||
ssh kraken 'sudo docker exec jellyfin sh -c "ls -lat /config/log/"'
|
||||
ssh kraken 'sudo docker exec jellyfin sh -c "grep -nE \"SqliteException|could not complete startup\" /config/log/log_20260920.log | head"'
|
||||
# → SQLite Error 1: 'no such column: p.Permission_Permissions_Guid'
|
||||
|
||||
# 4) какая версия сервера РЕАЛЬНО запущена
|
||||
ssh kraken 'docker exec jellyfin sh -c "/jellyfin/jellyfin --version"'
|
||||
# → Jellyfin.Server 10.11.8.0
|
||||
|
||||
# 5) какой образ у контейнера vs локальный latest
|
||||
ssh kraken 'docker inspect jellyfin --format "{{.Image}}"'
|
||||
ssh kraken 'docker inspect jellyfin/jellyfin:latest --format "{{.Id}}"'
|
||||
# → РАЗНЫЕ ID
|
||||
|
||||
# 6) до какой версии мигрирована БД (на Mac, копия БД)
|
||||
# см. скрипт ниже: миграции по ProductVersion
|
||||
```
|
||||
|
||||
### Разбор БД (скопировать на Mac, не трогая оригинал)
|
||||
|
||||
```bash
|
||||
ssh kraken 'sudo docker cp jellyfin:/config/data/jellyfin.db /tmp/jf.db && sudo chmod 644 /tmp/jf.db'
|
||||
scp kraken:/tmp/jf.db /Users/admin/kraken-recovery/jellyfin-db/jellyfin.db
|
||||
# затем python3: SELECT ProductVersion, COUNT(*) FROM __EFMigrationsHistory GROUP BY ProductVersion
|
||||
# скрипт-разбор: /Users/admin/kraken-recovery/inspect_db.py, inspect_db2.py
|
||||
```
|
||||
|
||||
## Факты инцидента (2026-09-20)
|
||||
|
||||
| Что | Значение |
|
||||
|---|---|
|
||||
| Образ **контейнера** | `sha256:1694ff06...` = **10.11.8** |
|
||||
| Образ **локальный `latest`** | `sha256:78d3ea12...` = **12.1.0.0** (обновлён 15.09) |
|
||||
| БД `jellyfin.db` мигрирована до | **12.1.0.0** (последняя миграция `20260911120000_StripEmbeddedLinkedChildren`) |
|
||||
| Миграций в `__EFMigrationsHistory` | 105 (наборы: 9.0.11 ×36, 10.0.11 ×19, 10.11.8 ×32, 12.0.0 ×15, 12.1.0 ×3) |
|
||||
| Схема `Permissions` в БД | **без** `Permission_Permissions_Guid` (12.x-форма) |
|
||||
| Контейнер создан | 26.05.2026, **не пересоздавался** |
|
||||
| Первое падение в логе | **09:57 (до shutdown Kraken)** — дефект не от перезагрузки |
|
||||
| `SQLiteBackups/` (авто-бэкапы) | **пуст** |
|
||||
| Единственный бэкап БД | `jellyfin.db.backup-20260617_034429` (17.06) |
|
||||
|
||||
## Как это получилось
|
||||
|
||||
`latest` указывал на 10.11.8 в мае → контейнер создан на этом образе. 15.09 `latest` на Hub обновился до **12.1**. Контейнер в какой-то момент запустился на новом образе (или compose пересоздал его), миграции прошли до 12.1.0.0. Затем контейнер вернулся на **старый** образ 10.11.8 (снапшот от 26.05) — и уже не может стартовать с новой БД.
|
||||
|
||||
**Питфолл тега `latest`:** `docker inspect` показывает digest, откуда образ БЫЛ СКАЧАН, а не текущий `latest`. Локальный «latest» может быть сильно старше, чем `latest` в реестре. Проверять версию **внутри образа**: `docker run --rm --entrypoint sh jellyfin/jellyfin:latest -c '/jellyfin/jellyfin --version'`.
|
||||
|
||||
## Фикс (согласован с Alex, ЖДЁТ ВЫПОЛНЕНИЯ)
|
||||
|
||||
**Миграцию вручную запустить НЕЛЬЗЯ и не нужно** — миграции уже применены (до 12.1.0.0), запускать нечего; даунгрейд схемы EF Core не поддерживает. Проблема не в БД, а в **образе контейнера**.
|
||||
|
||||
```bash
|
||||
# 1) БЭКАП БД (авто-бэкапов нет!)
|
||||
ssh kraken 'D=/home/kraken/backups/jellyfin-db-$(date +%Y%m%d-%H%M%S); mkdir -p $D; \
|
||||
sudo docker cp jellyfin:/config/data/jellyfin.db $D/ && \
|
||||
sudo docker cp jellyfin:/config/data/jellyfin.db-wal $D/ 2>/dev/null; \
|
||||
sudo chown -R kraken:kraken $D; ls -la $D'
|
||||
|
||||
# 2) пересоздать контейнер на актуальном образе
|
||||
ssh kraken 'cd /srv/dev-disk-by-uuid-6194539b-03e0-4f33-986b-4da09834e82f/docker/jellyfin && \
|
||||
sudo docker compose up -d --force-recreate'
|
||||
|
||||
# 3) проверка
|
||||
ssh kraken 'docker exec jellyfin sh -c "/jellyfin/jellyfin --version"' # ожидаем 12.1.0.0
|
||||
ssh kraken 'docker ps --format "{{.Names}} {{.Status}}" | grep jelly' # health: healthy
|
||||
ssh kraken 'docker exec jellyfin sh -c "curl -s -o /dev/null -w \"%{http_code}\n\" http://localhost:8096/"'
|
||||
```
|
||||
|
||||
**Риск:** низкий. Данные (`/config`) не трогаются, пересоздаётся только контейнер. Бэкап БД обязателен — откатываться больше некуда.
|
||||
|
||||
**Compose jellyfin** (`/srv/.../docker/jellyfin/docker-compose.yml`):
|
||||
```yaml
|
||||
image: jellyfin/jellyfin # без тега = latest
|
||||
restart: always
|
||||
ports: ["8096:8096"]
|
||||
volumes:
|
||||
- /srv/.../docker/jellyfin:/config
|
||||
- /srv/.../media:/media:ro
|
||||
environment: [TZ=Asia/Almaty]
|
||||
```
|
||||
> Рекомендация на будущее: **пинить тег** (`jellyfin/jellyfin:12.1`) вместо `latest`, иначе `watchtower` может снова переключить мажорную версию и сломать БД.
|
||||
|
||||
## Питфоллы диагностики
|
||||
|
||||
- **`docker logs jellyfin` не показывает свежий старт** — jellyfin пишет в `/config/log/log_YYYYMMDD.log`, а не в stdout. Читать файл внутри контейнера.
|
||||
- **В контейнере нет `sqlite3`, `python3`, `strings`** — БД разбирать только копией на Mac.
|
||||
- **Старые ошибки в логе сбивают**: `System.UriFormatException: Invalid URI` и `WS ... closed` — это НЕ причина падения, а фон. Искать `SqliteException` и `could not complete startup`.
|
||||
- Проверять **`--since`** фильтр осторожно: если контейнер старше, `--since 15m` даст пусто.
|
||||
|
||||
## Урок
|
||||
|
||||
При `no such column` в jellyfin (или любом EF Core сервисе) **первым делом сравнивать версию бинарника внутри запущенного контейнера с максимальной версией в `__EFMigrationsHistory`**. Версия БД выше версии сервера = откатился образ. Лечится пересозданием контейнера на правильном образе, НЕ правкой БД.
|
||||
@@ -0,0 +1,103 @@
|
||||
# Skill mirror: `docker-data-root-mount-race`
|
||||
|
||||
> Зеркало скилла `docker-data-root-mount-race` (категория `docker-service-administration`) для поиска по vault.
|
||||
> **Создано:** 2026-09-20. **Источник:** Kraken mount-race инцидент.
|
||||
> **Связанные:** [[personal/tech/docker-data-root-mount-race]] (полный разбор), [[personal/tech/jellyfin-db-schema-mismatch]], [[family/plans/kraken-backup-to-truenas]]
|
||||
|
||||
## Когда применять
|
||||
|
||||
Хост, у которого Docker `data-root` лежит на **поздно монтирующемся диске** (USB HDD, OMV `/srv/dev-disk-by-uuid-*`, RPi + внешний диск):
|
||||
|
||||
- `docker ps -a` **пусто**, хотя контейнеры были.
|
||||
- `docker info` → `Containers=0` при смонтированном диске.
|
||||
- После reboot сервисы в контейнерах «молча down» (туннели, reverse-proxy, jellyfin) — но не удалены.
|
||||
- Journal: `Duplicate entry in '/etc/fstab'?`
|
||||
|
||||
## Механизм
|
||||
|
||||
`dockerd` стартует **раньше**, чем монтируется диск с `data-root` → видит пустой каталог → загружает пустое состояние (0 контейнеров). Диск монтируется позже, но daemon **не перечитывает**. Данные целы.
|
||||
|
||||
Причина: `docker.service` без `RequiresMountsFor=`.
|
||||
|
||||
## Диагностика
|
||||
|
||||
```bash
|
||||
docker ps -a --format "{{.Names}}\t{{.Status}}" # пусто
|
||||
docker info --format "{{.DockerRootDir}} | containers={{.Containers}}"
|
||||
sudo ls <data-root>/containers/ | wc -l # >0 = целы
|
||||
ps -o pid,lstart,cmd -p "$(pgrep -x dockerd)" # РЕАЛЬНОЕ время старта
|
||||
uptime -s; sudo dmesg | grep -iE "EXT4-fs \(sd[a-z]" | tail -5
|
||||
systemctl show docker -p RequiresMountsFor # пусто = гонка возможна
|
||||
grep -c "<DISK-UUID>" /etc/fstab # 2 = дубль
|
||||
```
|
||||
|
||||
**Питфоллы диагностики:**
|
||||
- `systemctl show docker -p ExecMainStartTimestamp` даёт **stale** время → только `ps -o lstart`.
|
||||
- `RestartPolicy.Name` в `hostconfig.json` = `null`, но в live-состоянии политика есть. Проверять `docker inspect <name> --format '{{.HostConfig.RestartPolicy.Name}}'`. При `unless-stopped` контейнеры вернутся сами, ручной `docker start` не нужен.
|
||||
|
||||
## Фикс
|
||||
|
||||
**Немедленно:** `sudo systemctl restart docker` (диск уже смонтирован).
|
||||
|
||||
**Постоянно:** drop-in `/etc/systemd/system/docker.service.d/mounts.conf`:
|
||||
```ini
|
||||
[Unit]
|
||||
RequiresMountsFor=/srv/dev-disk-by-uuid-<DISK>/docker-data
|
||||
After=srv-dev\x2ddisk\x2dby\x2duuid\x2d<DISK>.mount
|
||||
```
|
||||
+ `systemctl daemon-reload`, + убрать дубль строки из `/etc/fstab`.
|
||||
|
||||
Проверка: `systemctl show docker -p Requires -p After` → маунт-юнит в **обоих**.
|
||||
|
||||
## Подтверждение реальным reboot
|
||||
|
||||
`systemctl restart docker` — **НЕ** тот же тест, что reboot (диск уже смонтирован). Проверка после реального выключения/включения 2026-09-20: все 12 контейнеров поднялись сами.
|
||||
|
||||
```bash
|
||||
ssh kraken 'docker ps -q | wc -l' # ожидаем ~12, НЕ 0
|
||||
```
|
||||
|
||||
## 🔴 «Сервис down после reboot» — ДВЕ разные причины, не путать
|
||||
|
||||
| Наблюдение | Причина | Где смотреть |
|
||||
|---|---|---|
|
||||
| `docker ps` **пусто** (0) | mount race | этот скилл |
|
||||
| Контейнер **Up**, но 5xx / `unhealthy` | своя ошибка приложения | лог внутри контейнера |
|
||||
|
||||
Реальный пример путаницы: после reboot `jellyfin` был **Up, но 503** — из-за **несовпадения версий** (образ старше схемы БД), НЕ из-за монтирования. См. [[personal/tech/jellyfin-db-schema-mismatch]].
|
||||
|
||||
**Правило:** контейнер Up → mount race ни при чём, читать свой лог приложения.
|
||||
|
||||
**Питфолл — логи не в stdout.** `docker logs` может быть пуст, если приложение пишет в файл внутри volume (jellyfin: `/config/log/log_YYYYMMDD.log`). Читать файл; фильтровать старые ошибки по `StartedAt`.
|
||||
|
||||
**Питфолл — дрейф версий у `latest`-тегов.** `docker inspect image:latest` показывает digest, **откуда скачали**, а не текущий `latest`. Локальный `latest` может быть на месяцы старше. Проверять версию внутри образа: `docker run --rm --entrypoint sh <image>:latest -c '<bin> --version'`. Если версия БД выше версии сервера → **пересоздать контейнер на верном образе**, НЕ даунгрейдить данные.
|
||||
|
||||
## Смежная ловушка: смешанные владельцы под data-root
|
||||
|
||||
Бэкап/tar дерева сервисов на том же диске: часть каталогов `root:root`, часть `1000:1000` → обычный юзер получает `Permission denied` и **молча частичный архив**.
|
||||
|
||||
```bash
|
||||
sudo ls -ldn <data-root>/../docker/*/ | awk '{print $3, $4, $NF}' # владельцы
|
||||
```
|
||||
Если `sudo -n true` проходит — архивировать под `sudo -n`, а на приёмнике ставить владельца: `rsync --chown=<user>:<group>`.
|
||||
|
||||
**Питфолл — долгий `chown -R`/tar умирает с SSH-сессией.** Запускать отсоединённо:
|
||||
```bash
|
||||
sudo systemd-run --unit=<name> --collect --property=Type=oneshot chown -R 1000:1000 <path>
|
||||
```
|
||||
|
||||
**Питфолл — `Restarting (1)` + `mkdir: Permission denied`** на data-каталоге: владелец на хосте ≠ UID контейнера. `docker inspect <name> --format '{{.Config.User}}'` → `chown <uid>:<gid>`. Первые `mkdir`-ошибки могут быть **буферизованным остатком** от прошлого запуска — убедиться, что контейнер реально держится `Up`.
|
||||
|
||||
## Никогда
|
||||
|
||||
- Не считать `docker ps` пустым признаком потери данных — сначала проверить `containers/` count.
|
||||
- Не `rm -rf` «устаревший» docker-data — это реальные данные.
|
||||
- Не править `/etc/fstab` через sed — писать файл локально, `findmnt --verify --fstab`, затем копировать.
|
||||
|
||||
## Чеклист после фикса
|
||||
|
||||
- [ ] `docker info` count == число каталогов в `containers/`
|
||||
- [ ] `RequiresMountsFor` непусто и указывает на `docker-data`
|
||||
- [ ] `grep -c "<UUID>" /etc/fstab` == 1
|
||||
- [ ] После `systemctl restart docker` критичные контейнеры `Up`
|
||||
- [ ] После реального reboot: `docker ps -q | wc -l` == ожидаемое, не 0
|
||||
Reference in new issue
Block a user