From e02a9a8f1b24540c42aefdbb22e50688375b8625 Mon Sep 17 00:00:00 2001 From: gilfanovr Date: Sun, 11 Oct 2026 16:10:52 +0300 Subject: [PATCH] =?UTF-8?q?rpi-media:=20=D1=81=D1=82=D0=B0=D1=80=D1=82?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D1=8F=20=D1=81=D1=82=D1=80=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D1=86=D0=B0=20Homepage,=20Radarr/Sonarr=20=E2=86=92=20Jellyfin?= =?UTF-8?q?=20connect?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - homepage/: сервисы с описанием и живыми виджетами; ключи — в homepage.env на малине - Jellyfin-виджет через customapi (встроенный не совместим с Jellyfin 12) - provision.py: шаги homepage и connect Co-Authored-By: Claude Opus 5.5 --- AGENTS.md | 2 +- servers/rpi.md | 6 +- .../__pycache__/provision.cpython-314.pyc | Bin 26591 -> 31527 bytes stacks/rpi-media/docker-compose.yml | 18 ++++ stacks/rpi-media/homepage/bookmarks.yaml | 1 + stacks/rpi-media/homepage/docker.yaml | 1 + stacks/rpi-media/homepage/services.yaml | 99 ++++++++++++++++++ stacks/rpi-media/homepage/settings.yaml | 23 ++++ stacks/rpi-media/homepage/widgets.yaml | 18 ++++ stacks/rpi-media/provision.py | 56 +++++++++- 10 files changed, 220 insertions(+), 4 deletions(-) create mode 100644 stacks/rpi-media/homepage/bookmarks.yaml create mode 100644 stacks/rpi-media/homepage/docker.yaml create mode 100644 stacks/rpi-media/homepage/services.yaml create mode 100644 stacks/rpi-media/homepage/settings.yaml create mode 100644 stacks/rpi-media/homepage/widgets.yaml diff --git a/AGENTS.md b/AGENTS.md index 199f998..769c4cb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -6,7 +6,7 @@ - [`servers/de1.md`](servers/de1.md) — 213.136.74.247. **Выведен** (все сервисы остановлены, метрики удалены): был прокси/VPN-нодой (telemt, AmneziaWG, OpenVPN) плюс gitea, openclaw, боты — всё уехало на fr1 или в бэкапы. ⚠️ Сам файл описания под вывод не переписан. - [`servers/fr1.md`](servers/fr1.md) — `fr1.ruzzy.dev` (161.97.93.252). Основная нода (Ubuntu 24.04): мониторинг, gitea, AmneziaWG exit для ru1, боты (claude-channels, openclaw), статика `www`/`share`. Держит апекс `ruzzy.dev`. - [`servers/ru1.md`](servers/ru1.md) — `85.198.109.86` (VDSina, РФ). VPN entry-нода (OpenVPN ×2, AmneziaWG **до fr1** после переключения, WireGuard клиентов), умный роутинг RU/зарубеж; WEB-прокси Telegram (tproxy-server + MTProxy) на `ru.ruzzy.dev:443`, telemt удалён 2026-10-09. ⚠️ Файл описания сверялся до переключения. -- [`servers/rpi.md`](servers/rpi.md) — `rpi.local` (192.168.10.25, дом, за NAT). Raspberry Pi 4: бэкап личного OneDrive на HDD 4 ТБ (rclone, вечный архив версий, еженедельная сверка хэшей); SMART и статус бэкапов пушит в pushgateway fr1 по SSH. Код — `stacks/rpi-backup/`. Агент Astra (Codex) с управлением из Telegram — репо `ruzzy/astra-bot`. Медиацентр в Docker (Jellyfin, Seerr, qBittorrent, Radarr/Sonarr/Prowlarr/Bazarr) — `stacks/rpi-media/`. +- [`servers/rpi.md`](servers/rpi.md) — `rpi.local` (192.168.10.25, дом, за NAT). Raspberry Pi 4: бэкап личного OneDrive на HDD 4 ТБ (rclone, вечный архив версий, еженедельная сверка хэшей); SMART и статус бэкапов пушит в pushgateway fr1 по SSH. Код — `stacks/rpi-backup/`. Агент Astra (Codex) с управлением из Telegram — репо `ruzzy/astra-bot`. Медиацентр в Docker (Jellyfin, Seerr, qBittorrent, Radarr/Sonarr/Prowlarr/Bazarr, стартовая страница Homepage :3000) — `stacks/rpi-media/`. ## Поддержание описаний в актуальном состоянии Тронул сервер — обнови `servers/<хост>.md` в том же прогоне. Контракт и diff --git a/servers/rpi.md b/servers/rpi.md index 2ffc3cf..7ffae3a 100644 --- a/servers/rpi.md +++ b/servers/rpi.md @@ -88,6 +88,7 @@ Docker CE 29.9 + Compose v5.6 из репо download.docker.com (в Debian ст | Prowlarr | 9696 | индексаторы, раздаёт их в Radarr/Sonarr | 2.6.5 | | FlareSolverr | 8191 | обход Cloudflare для индексаторов с тегом `flaresolverr` | v3.5.2 | | Bazarr | 6767 | субтитры, профиль `ru+en` | v1.6.2 | +| Homepage | 3000 | **стартовая страница**: все сервисы с описанием по-русски, живой статус, CPU/температура/диск | v2.5.0 | - **Данные — `/mnt/backup/media`** (4-ТБ диск, решение пользователя): `torrents/{movies,tv,other}`, `library/{movies,tv}`, `.cache/jellyfin` (кэш и транскоды Jellyfin — не на SD). qBittorrent и *arr монтируют **один и тот же** корень как `/data` — иначе импорт из `torrents/` в `library/` превращается из хардлинка в копию и раздача занимает место дважды. `copyUsingHardlinks` включён. В бэкапы (`router-backup`, `onedrive-*`) `media/` не входит. - ⚠️ **Медиа делят диск с бэкапами.** `onedrive-backup` падает с кодом 2 при заполнении >90 % (~370 ГБ свободно) → `BackupFailed`. В Radarr/Sonarr стоит `minimumFreeSpaceWhenImporting = 500 ГБ`: ниже этого импорт останавливается. **qBittorrent своего порога не имеет** — ручная заливка торрентов мимо *arr может забить диск. @@ -97,7 +98,10 @@ Docker CE 29.9 + Compose v5.6 из репо download.docker.com (в Debian ст - ⚠️ RuTracker закрывает `login.php`/`tracker.php` челленджем Cloudflare (по IPv4; IPv6 у малины нет маршрута). FlareSolverr на Pi решает его **80–180 с** — у прокси в Prowlarr `requestTimeout=180` (было 60, с ним добавление падало по таймауту). После решения Prowlarr держит cookie и поиск идёт ~4 с; когда cookie протухнет, первый поиск снова будет минутами, и *arr могут на время пометить индексатор недоступным. - Bazarr: провайдеры без аккаунтов (`embeddedsubtitles`, `podnapisi`, `yifysubtitles`); для русских субтитров лучший — OpenSubtitles.com, нужен аккаунт. - **Авторизация:** везде пользователь `rus`, один пароль — `/opt/media/.admin-password` (rus 600). *arr — формы, пароль **всегда** (режим «без пароля из LAN» в новых *arr требует `AllowedHosts`). Между собой сервисы ходят по API-ключам из `config//config.xml`. -- Первичная связка — `stacks/rpi-media/provision.py` (идемпотентный, пароль и ключи читает на месте): `python3 /opt/media/provision.py [qbit jellyfin arr prowlarr bazarr seerr]`. +- **Точка входа для пользователя — http://192.168.10.25:3000** (Homepage, конфиги `stacks/rpi-media/homepage/` → `/opt/media/homepage/`). Ключи виджетов — `/opt/media/homepage.env` (rus 600, `HOMEPAGE_VAR_*`, генерит `provision.py homepage`). ⚠️ Там же пароль qBittorrent: после смены общего пароля перегенерировать env и `docker compose up -d homepage`, иначе виджет qBittorrent отвалится. `HOMEPAGE_ALLOWED_HOSTS` обязателен — без него 400 на любой Host, кроме localhost. +- ⚠️ Встроенный виджет `jellyfin` Homepage 2.5 с Jellyfin 12 не работает: ходит на `/emby/…?api_key=` — пути нет (404), ключ в query отключён (`EnableLegacyAuthorization=false`, включать не стали). Вместо него `customapi` на `/Items/Counts` с заголовком `Authorization: MediaBrowser Token=…`. +- Radarr/Sonarr → Jellyfin (Connect «Jellyfin», тип MediaBrowser, ключ Jellyfin `homepage`; `provision.py connect` на чистой установке заводит ключ `arr-connect`): библиотека обновляется сразу после импорта. Без этого новый фильм висел невидимым до скана по расписанию — realtime-монитор хардлинк не заметил (проверено 2026-10-11 на первом фильме). +- Первичная связка — `stacks/rpi-media/provision.py` (идемпотентный, пароль и ключи читает на месте): `python3 /opt/media/provision.py [qbit jellyfin arr prowlarr bazarr seerr connect homepage]`. - ⚠️ Каталог `config/<сервис>`, которого нет до `up`, docker создаёт **от root** — Jellyfin/Seerr (uid 1000) падают с `Permission denied`. Новые каталоги создавать заранее или `chown -R rus:rus /opt/media/config`. - ⚠️ `fallenbagel/jellyseerr` заброшен (последний образ 2025-08) и не логинится в Jellyfin 12 — заменён на `ghcr.io/seerr-team/seerr`. - Торренты идут **напрямую** с домашнего IP (решение пользователя 2026-10-11; вариант через AWG fr1 отложен). Порт 6881 на роутере **не проброшен** — входящих соединений нет, раздача и скорость хуже. diff --git a/stacks/rpi-media/__pycache__/provision.cpython-314.pyc b/stacks/rpi-media/__pycache__/provision.cpython-314.pyc index 9ea63d6a5700b5c3705b80dfc463136cf5d2bda3..d5e6e789453a846a04e1952b6a0fef9f4ec454d6 100644 GIT binary patch delta 6034 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 delta 1583 zcmYk6e{2**6vub&S9{Px3zSrBX(_$-4!Cab+TsCip}(%yA~sk40P1=!y}fqJ_3k>e zSK3wMj~F%8h~$AWF(mwffQdrPA{bEdCkY~B(E#+V>V(JDdI5Ov-j1UGk|ym{}< zo0+%Yoqd0h@6HhK(pmX41pYkx%&DP+U4rlf4sJiQZyDF7$ab$mYPVl}dYLpo;*}Oe zd{RkdiBuX{DlLpGlgdPwRxT}y_(K~6Ar=;d5rzP43^qM4DFp z!%9q*MMHD%FDANCTG41HhOxSGx1@9;C7_W1i?1PhaUhew6F6P>YIs4(k zdJnk-Pt}JC-@uxVt#VSM_h9TYoT^_;t{cDB@105BhnLq>*BnRx^N0@+A0j?Ne2nNu ztVMi+_!O?J*+$O7BP|aXp68Br^geX|h4>q$S_XbfD*+1FJoim*f5HC@B5f66EKM&Uni1b{=ywfjT6(1@#<4WZ zmR=bOtO$toB9EK1s4|jKn6_RWR#F*_{wu)Iw#o&cV}a9HAfI~uBee$8Pz)Zui3*|}L&{sdiu{uabd*wY>(N5S2(Yu>k*n8$1O&{SMa zC|xo7JsjZ`dk|L@d?G?$Ji^BZ z;)_dm@F_l_EnM_Yd&;43P$UFe1}mLSJnk@t23P0$_58q!U`=3os3uTb6KtR+pQY|D<2`U? zn9|Dxf`lS`sO-$ty_w*Wfij$c8=7MIygD z4e&4PG5Uhb(zgJ!S&_4oPig@l^Hf>Vwi-VJH|^#yXUqMXI;z~!krOoLB}ZTvJ6wip z(hJ1feG#dpEA_-eOR~p+YODAia6l`rUV*h7Ln$>$gV+nylsTiH-!Yn;P<6$U7QY^* zv^tMsdYwqRj)CgIczI1u= 300 else "") + + # ---------------------------------------------------------------- Bazarr def bazarr(): import yaml # python3-yaml есть в Raspberry Pi OS @@ -333,7 +385,7 @@ def jellyseerr(): log("seerr ok") -STEPS = {"qbit": qbit, "jellyfin": jellyfin, "arr": arr, "prowlarr": prowlarr, "bazarr": bazarr, "seerr": jellyseerr} +STEPS = {"qbit": qbit, "jellyfin": jellyfin, "arr": arr, "prowlarr": prowlarr, "bazarr": bazarr, "connect": connect, "homepage": homepage, "seerr": jellyseerr} if __name__ == "__main__": for s in sys.argv[1:] or list(STEPS):