diff --git a/family/projects/media-toolbox-kraken.md b/family/projects/media-toolbox-kraken.md new file mode 100644 index 00000000..801623f1 --- /dev/null +++ b/family/projects/media-toolbox-kraken.md @@ -0,0 +1,679 @@ +--- +created: '2026-05-13' +updated: '2026-05-13' +status: planning +tags: + - kraken + - media + - jellyfin + - transmission + - swift + - project +--- +# Media Toolbox на Кракене + +> Создано: 2026-05-13. Цель: полноценный медиасервер на Кракене (RPi 5) — перенос библиотеки с HTPC, автосортировка через kodi-media-sync, Jellyfin, торренты. + +--- + +## Архитектура (целевая) + +``` +HTPC (Bazzite 192.168.1.86) + └── /run/media/bazzite/Data/{Мультфильмы,Сериалы,movies,...} + └─── rsync ──→ Kraken HDD /srv/.../media/ + +Kraken (RPi 5, 192.168.1.14) + ├── /srv/dev-disk-by-uuid-.../media/ + │ ├── movies/ ← фильмы + │ ├── cartoons/ ← мультфильмы + │ ├── series/ ← сериалы + │ ├── documentaries/ ← документальные + │ └── downloads/ ← папка Transmission (входящие) + ├── Docker: Transmission (скачка торрентов) + ├── Docker: Jellyfin (стриминг на TV/телефоны) + └── Docker: kodi-media-sync (сортировщик + метаданные) + +kodi-media-sync (Swift CLI, cross-compile Mac → Linux ARM64) + ├── Читает downloads/ + необработанные папки + ├── Идентифицирует через Transmission RPC (если торрент) или поиск по имени + ├── Создаёт NFO-файлы (Kodi/Jellyfin-совместимые) + ├── Раскладывает по категориям (movies/cartoons/series/docs) + ├── Обновляет пути скачки в Transmission через RPC API + └── Запускается как download-complete скрипт Transmission + +Hermes cron (Кракен, 1-2x/day) + └── kodi-media-sync --dry-run → лог → manual confirm / auto-apply + +TrueNAS (Taiga) + └── Transmission → миграция на Крaken (или оставить для seed, скопировать БД торрентов) +``` + +--- + +## Статус по шагам + +| Шаг | Статус | Заметки | +|-----|--------|---------| +| 1 — Исследование (Kodi lib на HTPC) | 🔲 | | +| 2 — Wake HTPC + prevent sleep | 🔲 | | +| 3 — Копирование медиа HTPC → Kraken | 🔲 | | +| 4 — Transmission на Кракене | 🔲 | уже есть контейнер в /home/kraken/media/ | +| 5 — Перенос torrent-базы с HTPC | 🔲 | Windows раздел или Bazzite Transmission | +| 6 — kodi-media-sync: изучить + расширить | 🔲 | Swift, scaffold готов | +| 7 — Cross-compile Mac → Linux ARM64 | 🔲 | Swift toolchain для aarch64 | +| 8 — Docker-образ kodi-media-sync на Кракене | 🔲 | | +| 9 — Config: пути, Transmission, категории | 🔲 | | +| 10 — Dry run: проверка сортировки | 🔲 | | +| 11 — Jellyfin: настройка библиотек | 🔲 | уже есть контейнер | +| 12 — Transmission download-complete hook | 🔲 | | +| 13 — Hermes cron на Кракене | 🔲 | | +| 14 — Руtracker-скилл (поиск/закачка) | 🔲 | | +| 15 — Музыка: скачка + sync с TrueNAS | 🔲 | | + +--- + +## Шаг 1 — Исследование: Kodi lib на HTPC + +**Цель:** найти реальную структуру медиабиблиотеки, где симлинки, куда ведут. + +```bash +ssh -i ~/.ssh/id_rsa bazzite@192.168.1.86 + +# Kodi userdata (может быть в двух местах) +ls ~/.kodi/userdata/ +ls ~/.kodi/userdata/sources.xml # список медиаисточников + +# Если Kodi с Windows раздела мигрировал +ls /run/media/bazzite/Data/AppData/Roaming/Kodi/ 2>/dev/null + +# Найти симлинки в home +find ~ -type l 2>/dev/null | head -30 + +# Медиа на Data разделе +ls /run/media/bazzite/Data/ | grep -iE "film|movie|кино|мульт|serial|сери|doc" +``` + +**Ожидаемые пути на Data:** +- `/run/media/bazzite/Data/Мультфильмы/` +- `/run/media/bazzite/Data/Сериалы/` +- `/run/media/bazzite/Data/movies/` +- `/run/media/bazzite/Data/документальные/` (или аналог) + +--- + +## Шаг 2 — Wake HTPC + предотвращение сна + +### Wake on LAN +```bash +# С Mac или Кракена: +wakeonlan B0:6E:BF:60:6D:64 +# или +python3 ~/Automation/wake-on-lan.py +``` + +### Предотвратить сон во время SSH / копирования + +**Вариант A — systemd-inhibit (рекомендуется):** +```bash +# Запустить на HTPC в отдельной SSH-сессии, оставить висеть пока идёт копирование: +systemd-inhibit --what=sleep:idle --who="media-copy" \ + --why="Copying media to Kraken" --mode=block \ + sleep infinity +``` +Закрыть эту сессию = сон разрешён снова. + +**Вариант B — через systemd сервис-lockfile (долгосрочно):** +Если SSH-сессия падает, нужен более надёжный способ. Создать временный `inhibit.service` который поднимается на время rsync. + +**Вариант C — xdg-screensaver (только экран):** +```bash +# Только блокирует screensaver, не suspend +while true; do xdg-screensaver reset; sleep 60; done & +``` + +**Рекомендация:** вариант A (systemd-inhibit). Запускать в tmux, чтобы сессия жила независимо от SSH: +```bash +ssh bazzite@192.168.1.86 "tmux new-session -d -s inhibit 'systemd-inhibit --what=sleep:idle --who=media-copy --why=Copying --mode=block sleep 86400'" +``` + +--- + +## Шаг 3 — Копирование медиа HTPC → Kraken + +### Целевая структура на Кракене +``` +/srv/dev-disk-by-uuid-49e8f586-3839-4c5d-a1e1-58bfc3579ade/media/ +├── movies/ +├── cartoons/ +├── series/ +├── documentaries/ +└── downloads/ ← Transmission download dir + └── complete/ ← сюда Transmission кладёт готовые +``` + +### Создать директории +```bash +ssh kraken@192.168.1.14 "mkdir -p /srv/dev-disk-by-uuid-49e8f586-3839-4c5d-a1e1-58bfc3579ade/media/{movies,cartoons,series,documentaries,downloads/complete,downloads/incomplete}" +``` + +### rsync HTPC → Kraken (прямо с Кракена) +```bash +# Запустить на Кракене (pull с HTPC): +HDD=/srv/dev-disk-by-uuid-49e8f586-3839-4c5d-a1e1-58bfc3579ade + +# Мультфильмы +rsync -avh --progress --no-links \ + bazzite@192.168.1.86:"/run/media/bazzite/Data/Мультфильмы/" \ + "$HDD/media/cartoons/" + +# Сериалы +rsync -avh --progress --no-links \ + bazzite@192.168.1.86:"/run/media/bazzite/Data/Сериалы/" \ + "$HDD/media/series/" + +# Фильмы (уточнить точное имя папки) +rsync -avh --progress --no-links \ + bazzite@192.168.1.86:"/run/media/bazzite/Data/movies/" \ + "$HDD/media/movies/" + +# Документальные (если есть) +rsync -avh --progress --no-links \ + bazzite@192.168.1.86:"/run/media/bazzite/Data/документальные/" \ + "$HDD/media/documentaries/" 2>/dev/null || true +``` + +> ⚠️ `--no-links` — не копировать симлинки как симлинки, следовать им. +> ⚠️ Для SSH без пароля с Кракена нужен ключ: `ssh-copy-id -i ~/.ssh/id_rsa bazzite@192.168.1.86` + +### Альтернатива — пушить с HTPC через SSH: +```bash +# На HTPC: +rsync -avh --progress /run/media/bazzite/Data/Мультфильмы/ \ + kraken@192.168.1.14:/srv/.../media/cartoons/ +``` + +--- + +## Шаг 4 — Transmission на Кракене + +Transmission уже задеплоен вместе с Jellyfin в `/home/kraken/media/docker-compose.yml`. + +**Проверить конфиг:** +```bash +cat /home/kraken/media/docker-compose.yml +``` + +**Download dir** должен быть смонтирован как HDD/media/downloads. + +**Порты:** 9091 (RPC), 51413 (peers) + +**Если Transmission ещё не настроен** — проверить и обновить docker-compose, добавить volume: +```yaml +volumes: + - /srv/dev-disk-by-uuid-.../media/downloads:/downloads + - /srv/dev-disk-by-uuid-.../docker/transmission:/config +``` + +--- + +## Шаг 5 — Перенос torrent-базы с HTPC + +### Найти торрент-клиент на HTPC + +```bash +ssh bazzite@192.168.1.86 + +# Что стоит? +which transmission-daemon qbittorrent-nox rtorrent 2>/dev/null +systemctl list-units | grep -iE "torrent|transmission|qbit" + +# Transmission (если есть): +ls ~/.config/transmission/torrents/ | head -10 +ls ~/.config/transmission/resume/ | head -10 + +# qBittorrent: +ls ~/.local/share/data/qBittorrent/BT_backup/ | head -10 +``` + +### Если Transmission на Bazzite: +```bash +# Скопировать на Кракен: +rsync -avh bazzite@192.168.1.86:~/.config/transmission/ \ + kraken@192.168.1.14:/tmp/htpc-transmission/ +``` + +### Если торрент-клиент был на Windows (TrueNAS Transmission): +Торрентная БД TrueNAS уже работает. Нужно решить: +- **Вариант A:** Перенести TrueNAS Transmission на Крaken (один клиент) +- **Вариант B:** Оставить TrueNAS для seed, Крaken для новых загрузок + +**Миграция `.torrent` + `.resume` файлов:** +```bash +# Скопировать с TrueNAS (через duckdns): +rsync -avh -e "ssh -i ~/.ssh/id_rsa" \ + truenas_admin@mallexxx.duckdns.org:/mnt/RED_2TB/docker/transmission/ \ + /tmp/taiga-transmission/ + +# Поправить пути в .resume файлах: +# Старый путь TrueNAS: /mnt/storage/Downloads/... +# Новый путь Кракена: /downloads/complete/... +for f in /tmp/taiga-transmission/resume/*.resume; do + # .resume это bencoded файл — нужен python с bencode или transmission-edit + echo "$f" +done +``` + +> ⚠️ Файлы `.resume` — бинарный bencode-формат. Для правки путей использовать: +> `pip install bencoder.pyx` или `transmission-remote --torrent N --find /new/path` +> Проще: после импорта торрентов запустить media-sync с --fix-paths флагом через RPC API. + +### Исправление путей через Transmission RPC: +```bash +# Transmission RPC: изменить путь для конкретного торрента +curl -s -u user:pass http://kraken:9091/transmission/rpc \ + -H "X-Transmission-Session-Id: TOKEN" \ + -d '{"method":"torrent-set-location","arguments":{"ids":[N],"location":"/downloads/complete/FolderName","move":false}}' +``` + +**kodi-media-sync должен уметь это автоматически** (см. шаг 6). + +--- + +## Шаг 6 — kodi-media-sync: функциональность + +### Текущее состояние проекта +Проект `~/Developer/kodi-media-sync` (Swift): +- `Config.swift` — читает config.json: `transmission` URL, `directories[]`, `output.{movies,series}` +- `MediaSync.swift` — scaffold: сканирует директории, вызывает `updateMetadata` (пустая) +- `Path.swift` — утилиты для файловой системы + +**Нужно реализовать:** + +### 6.1 Transmission RPC клиент +```swift +// Sources/TransmissionClient.swift +struct TransmissionClient { + let url: URL + + // Получить все торренты + func getTorrents() async throws -> [Torrent] + + // Найти торрент по имени файла / папке + func findTorrent(forPath path: Path) async throws -> Torrent? + + // Обновить путь торрента + func setLocation(torrentId: Int, location: Path) async throws +} +``` + +### 6.2 Идентификация медиа +```swift +// Sources/MediaIdentifier.swift +enum MediaType { + case movie(title: String, year: Int?) + case series(title: String, season: Int?, episode: Int?) + case cartoon(title: String, year: Int?) + case documentary(title: String, year: Int?) + case unknown +} + +struct MediaIdentifier { + // Из имени папки/файла → тип медиа + нормализованное название + func identify(path: Path, torrent: Torrent?) -> MediaType + + // Поиск метаданных (TMDB/OMDB/локальный поиск) + func fetchMetadata(for media: MediaType) async throws -> MediaMetadata +} +``` + +**Алгоритм идентификации:** +1. Если есть торрент в Transmission — взять имя торрента, категорию +2. Парсить имя файла/папки: регексы для `S01E01`, год `(2024)`, качество `1080p` +3. Для документалок типа "знакомимся с кабанами" — поиск по названию (TMDB API) +4. Маппинг исходной папки → тип: `Мультфильмы/` → cartoon, `Сериалы/` → series + +### 6.3 NFO-файлы (Kodi/Jellyfin) +```swift +// Sources/NFOWriter.swift +// Формат Kodi NFO: https://kodi.wiki/view/NFO_files/Movies +struct NFOWriter { + func writeMovieNFO(_ metadata: MediaMetadata, to path: Path) throws + func writeSeriesNFO(_ metadata: MediaMetadata, to path: Path) throws + func writeTVShowNFO(_ metadata: MediaMetadata, to path: Path) throws +} +``` + +### 6.4 Config расширение +```json +{ + "transmission": "http://user:pass@192.168.1.14:9091/transmission/rpc", + "tmdb_api_key": "...", + "directories": [ + "/media/downloads/complete", + "/media/movies", + "/media/cartoons" + ], + "output": { + "movies": "/media/movies", + "series": "/media/series", + "cartoons": "/media/cartoons", + "documentaries": "/media/documentaries" + }, + "dry_run": false, + "category_hints": { + "/media/cartoons": "cartoon", + "/media/series": "series", + "/media/documentaries": "documentary" + } +} +``` + +### 6.5 CLI флаги +``` +kodi-media-sync [--config path] [--dry-run] [--path ] [--fix-paths] + --dry-run Показать что будет сделано, не двигать файлы + --path PATH Обработать только конкретную папку/файл + --fix-paths Обновить пути в Transmission для уже существующих файлов +``` + +--- + +## Шаг 7 — Cross-compilation Mac → Linux ARM64 + +**Swift на Mac имеет встроенный cross-compiler для Linux.** + +### Установить Swift toolchain для Linux (если не стоит): +```bash +# На Mac: +swiftly install latest +# или скачать Linux ARM toolchain: +# https://www.swift.org/install/linux/ +``` + +### Build для aarch64-linux: +```bash +cd ~/Developer/kodi-media-sync + +# Вариант A — через Docker buildx (рекомендуется): +docker buildx build --platform linux/arm64 \ + -f Dockerfile.linux \ + -t kodi-media-sync:arm64 \ + --output type=local,dest=./build-arm64 . + +# Вариант B — swift cross-compile (нужен SDK): +swift build --triple aarch64-unknown-linux-gnu \ + --sdk /path/to/linux-arm64-sdk +``` + +### Dockerfile.linux для Крaken: +```dockerfile +FROM swift:5.9-jammy AS builder +WORKDIR /build +COPY . . +RUN swift build -c release + +FROM ubuntu:22.04 +RUN apt-get update && apt-get install -y libstdc++6 libgcc-s1 && rm -rf /var/lib/apt/lists/* +COPY --from=builder /build/.build/release/kodi-media-sync /usr/local/bin/ +COPY config.json /etc/kodi-media-sync/config.json +ENTRYPOINT ["kodi-media-sync"] +CMD ["--config", "/etc/kodi-media-sync/config.json"] +``` + +> ⚠️ Собирать образ на Mac через buildx с эмуляцией QEMU — не на RPi5 (undervoltage под нагрузкой)! + +### Deploy на Кракен: +```bash +# Сохранить образ и скопировать: +docker save kodi-media-sync:arm64 | gzip | ssh kraken@192.168.1.14 "docker load" +``` + +--- + +## Шаг 8 — Docker-образ на Кракене + +### `/home/kraken/media-sync/docker-compose.yml`: +```yaml +version: '3.8' +services: + kodi-media-sync: + image: kodi-media-sync:arm64 + container_name: kodi-media-sync + restart: "no" # запускается по требованию, не автостарт + volumes: + - /srv/dev-disk-by-uuid-.../media:/media + - /srv/dev-disk-by-uuid-.../docker/media-sync:/config + environment: + - CONFIG=/config/config.json + network_mode: host # доступ к Transmission на localhost +``` + +### Запуск (разовый): +```bash +cd /home/kraken/media-sync +sudo docker compose run --rm kodi-media-sync --dry-run +sudo docker compose run --rm kodi-media-sync +``` + +--- + +## Шаг 9 — Config для Кракена + +```json +{ + "transmission": "http://admin:pass@192.168.1.14:9091/transmission/rpc", + "tmdb_api_key": "КЛЮЧ_ИЗ_TMDB", + "directories": [ + "/media/downloads/complete" + ], + "category_hints": { + "/media/cartoons": "cartoon", + "/media/series": "series", + "/media/documentaries": "documentary", + "/media/movies": "movie" + }, + "output": { + "movies": "/media/movies", + "series": "/media/series", + "cartoons": "/media/cartoons", + "documentaries": "/media/documentaries" + }, + "dry_run": false +} +``` + +> TMDB API key: получить на https://www.themoviedb.org/settings/api (бесплатно) + +--- + +## Шаг 10 — Dry run и ручная проверка + +```bash +# Dry run — показывает что будет сделано +sudo docker run --rm \ + -v /srv/.../media:/media \ + -v /srv/.../docker/media-sync:/config \ + kodi-media-sync:arm64 --dry-run + +# Проверить проблемные документалки +sudo docker run --rm ... kodi-media-sync:arm64 --dry-run \ + --path "/media/downloads/complete/знакомимся с кабанами" + +# Проверить NFO файлы +cat /srv/.../media/movies/SomeMovie/SomeMovie.nfo +``` + +**Что проверить:** +- [ ] Фильмы попали в movies/, мультфильмы в cartoons/ и т.д. +- [ ] NFO-файлы созданы и содержат правильные title/year/plot +- [ ] Документалки с нестандартными названиями найдены через TMDB +- [ ] Торренты в Transmission показывают правильные пути + +--- + +## Шаг 11 — Jellyfin: настройка библиотек + +Jellyfin уже работает на Кракене: http://192.168.1.14:8096 + +### Категории библиотек: +| Библиотека | Тип | Путь | Язык | +|------------|-----|------|------| +| Детские мультфильмы | Animated Movies | /media/cartoons | ru | +| Детские сериалы | Series | /media/cartoons-series | ru | +| Документальные | Movies | /media/documentaries | ru | +| Сериалы | TV Shows | /media/series | ru | +| Фильмы | Movies | /media/movies | ru | + +### Настройки библиотеки: +- **Metadata Language:** Russian (или auto-detect) +- **NFO reading:** включить (Kodi-compatible NFO) +- **Sort by:** Date Added (поле из NFO), Rating, Title + +### Сортировка и фильтрация в UI (Jellyfin): +- Детские: отдельная коллекция "Детям" → мультфильмы + детские сериалы +- Можно использовать **Collections** для кастомных подборок +- **Recently Added** — показывает по дате скачки (metadata: dateadded в NFO) +- Рейтинг: IMDB/TMDB — парсится из NFO `` тег + +--- + +## Шаг 12 — Transmission download-complete hook + +Transmission вызывает скрипт при завершении скачки: + +```bash +# settings.json Transmission (в /config/): +# "script-torrent-done-enabled": true, +# "script-torrent-done-filename": "/usr/local/bin/on-download-complete.sh" +``` + +### `/usr/local/bin/on-download-complete.sh` (внутри контейнера): +```bash +#!/bin/bash +# Transmission передаёт переменные: +# TR_TORRENT_NAME, TR_TORRENT_DIR, TR_TORRENT_ID, TR_APP_VERSION + +logger "Transmission: download complete: $TR_TORRENT_NAME" + +# Запустить media-sync для этой конкретной папки +docker run --rm \ + -v /srv/.../media:/media \ + -v /srv/.../docker/media-sync:/config \ + kodi-media-sync:arm64 \ + --path "$TR_TORRENT_DIR/$TR_TORRENT_NAME" +``` + +> ⚠️ Скрипт должен быть доступен изнутри контейнера Transmission, или монтируется туда. +> Альтернатива: Transmission вызывает скрипт на хосте → хост запускает docker run. + +--- + +## Шаг 13 — Hermes cron на Кракене + +**Добавить через Hermes на Кракене:** +``` +Крон: каждые 12 часов +Задача: запустить kodi-media-sync --dry-run на папке downloads, прислать отчёт в Telegram +``` + +### Cron job (добавить в Hermes Кракена): +``` +Schedule: 0 */12 * * * +Prompt: Запусти kodi-media-sync на Кракене: + sudo docker run --rm \ + -v /srv/.../media:/media \ + -v /srv/.../docker/media-sync:/config \ + kodi-media-sync:arm64 --dry-run + Пришли отчёт: что было распознано, что нет. +``` + +--- + +## Шаг 14 — Rutracker / торрент-поиск скилл + +**Добавить скилл Кракену:** +- Поиск по Rutracker (может требовать авторизацию) +- Альтернативы: Jackett (агрегатор) + Prowlarr +- Добавление торрента в Transmission через RPC API + +**Jackett** (рекомендуется как агрегатор): +```yaml +# docker-compose для Jackett: +services: + jackett: + image: linuxserver/jackett + ports: + - "9117:9117" + volumes: + - /srv/.../docker/jackett:/config +``` +Jackett поддерживает Rutracker + сотни других tracker-ов, отдаёт unified API. + +--- + +## Шаг 15 — Музыка: скачка + sync с TrueNAS + +### Структура: +``` +Kraken: /media/music/ + ├── [Artist]/ + │ └── [Album]/ + │ └── *.mp3 + +TrueNAS: /mnt/RED_2TB/storage/Music/ (master copy) +``` + +### kodi-media-sync — расширение для музыки: +- Идентификация: MusicBrainz / Beets +- Тегирование через `beets` (python tool) +- Sync Kraken → TrueNAS через rsync + +### Jellyfin музыкальная библиотека: +- Тип: Music +- Путь: /media/music +- Плеер: Jellyfin + Finamp на Android + +--- + +## Технические детали + +### HTPC +- IP: 192.168.1.86 +- SSH: `ssh -i ~/.ssh/id_rsa bazzite@192.168.1.86` +- Data mount: `/run/media/bazzite/Data/` +- WoL MAC: `B0:6E:BF:60:6D:64` + +### Крaken +- IP: 192.168.1.14 +- SSH: `ssh -i ~/.ssh/id_rsa kraken@192.168.1.14` +- HDD: `/srv/dev-disk-by-uuid-49e8f586-3839-4c5d-a1e1-58bfc3579ade/` +- Jellyfin: http://192.168.1.14:8096 +- Transmission: http://192.168.1.14:9091 + +### kodi-media-sync +- Исходники: `~/Developer/kodi-media-sync/` (Mac) +- Язык: Swift 5.9+ +- Зависимости: swift-argument-parser +- Target: Linux ARM64 (aarch64) + +### TrueNAS (Taiga) +- SSH: `truenas_admin@mallexxx.duckdns.org` +- Transmission config: `/mnt/RED_2TB/docker/transmission/` +- Downloads: `/mnt/RED_2TB/storage/Downloads/` + +--- + +## Журнал выполнения + +### 2026-05-13 — Планирование +- Изучена структура: HTPC (Bazzite), Кракен (OMV/Docker), TrueNAS (Taiga) +- Найден проект kodi-media-sync: scaffold (Config + Path + пустой MediaSync) +- Jellyfin + Transmission уже есть на Кракене в `/home/kraken/media/` +- Transmission на TrueNAS: конфиг в `/mnt/RED_2TB/docker/transmission/`, скачки в `storage/Downloads/` +- Plan создан в Obsidian + +### TODO (следующий сеанс) +- [ ] Разбудить HTPC, исследовать структуру медиа (шаг 1) +- [ ] Решить вопрос сна HTPC (шаг 2) +- [ ] Запустить rsync HTPC → Крaken (шаг 3)