rpi-media: стартовая страница Homepage, Radarr/Sonarr → Jellyfin connect

- homepage/: сервисы с описанием и живыми виджетами; ключи — в homepage.env на малине
- Jellyfin-виджет через customapi (встроенный не совместим с Jellyfin 12)
- provision.py: шаги homepage и connect

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
gilfanovr
2026-10-11 16:10:52 +03:00
co-authored by Claude Opus 5.5
parent b915a58ad9
commit e02a9a8f1b
10 changed files with 220 additions and 4 deletions
+54 -2
View File
@@ -3,7 +3,7 @@
ничего не ломает, уже сделанное пропускает.
Запуск на малине: python3 /opt/media/provision.py [шаг ...]
Шаги: qbit jellyfin arr prowlarr bazarr seerr (без аргументов — все).
Шаги: qbit jellyfin arr prowlarr bazarr seerr connect homepage (без аргументов — все).
Пароль админа берётся из /opt/media/.admin-password, API-ключи — из config.xml.
"""
from http.cookiejar import CookieJar
@@ -256,6 +256,58 @@ def prowlarr():
log("prowlarr ok")
# ---------------------------------------------------------------- Homepage + Jellyfin connect
def jf_apikey(name):
"""API-ключ Jellyfin для приложения name (создаётся при отсутствии)."""
tok = jf_token()
h = {"Authorization": f'{JF_AUTH}, Token="{tok}"'}
find = lambda: next((x["AccessToken"] for x in http("GET", JF + "/Auth/Keys", headers=h)[1]["Items"]
if x["AppName"] == name), None)
if not find():
http("POST", f"{JF}/Auth/Keys?app={name}", headers=h)
return find()
def homepage():
"""/opt/media/homepage.env (rus 600) — ключи для виджетов, в репо их нет."""
import os, yaml
env = {
"HOMEPAGE_VAR_JELLYFIN_KEY": jf_apikey("homepage"),
"HOMEPAGE_VAR_SEERR_KEY": json.load(open(f"{BASE}/config/seerr/settings.json"))["main"]["apiKey"],
"HOMEPAGE_VAR_RADARR_KEY": apikey("radarr"),
"HOMEPAGE_VAR_SONARR_KEY": apikey("sonarr"),
"HOMEPAGE_VAR_PROWLARR_KEY": apikey("prowlarr"),
"HOMEPAGE_VAR_BAZARR_KEY": yaml.safe_load(open(f"{BASE}/config/bazarr/config/config.yaml"))["auth"]["apikey"],
"HOMEPAGE_VAR_QBIT_USER": USER,
"HOMEPAGE_VAR_QBIT_PASS": PW,
}
old = os.umask(0o077)
open(f"{BASE}/homepage.env", "w").write("".join(f"{k}={v}\n" for k, v in env.items()))
os.umask(old)
log("homepage env written (перезапусти контейнер homepage, если ключи сменились)")
def connect():
"""Radarr/Sonarr → Jellyfin: обновить библиотеку сразу после импорта (иначе ждать скан по расписанию)."""
k = jf_apikey("arr-connect")
for name, a in ARR.items():
h = {"X-Api-Key": apikey(name)}
u = f"{a['url']}/api/{a['api']}"
st, ns = http("GET", u + "/notification", headers=h)
if any(n["implementation"] == "MediaBrowser" for n in ns):
continue
st, schema = http("GET", u + "/notification/schema", headers=h)
n = next(x for x in schema if x["implementation"] == "MediaBrowser")
vals = {"host": "192.168.10.25", "port": 8096, "useSsl": False, "apiKey": k, "notify": False, "updateLibrary": True}
for f in n["fields"]:
if f["name"] in vals:
f["value"] = vals[f["name"]]
n.update({"name": "Jellyfin", "onDownload": True, "onUpgrade": True, "onRename": True,
"onMovieDelete": True, "onMovieFileDelete": True, "onSeriesDelete": True, "onEpisodeFileDelete": True})
st, r = http("POST", u + "/notification", n, h)
log(name, "jellyfin connect", st, r if st >= 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):