Mise à jour automatique (Gitea + Watchtower) et conteneurisation Docker

- Détection de version via l'API Gitea, application via Watchtower
- Router système, UI admin (intégrations), bandeau de mise à jour
- Dockerfile multi-étapes, docker-compose, scripts/release.sh
- Version centralisée dans app/version.py (pyproject ou OHM_VERSION au build)
This commit is contained in:
Roman
2026-09-22 11:58:03 +00:00
parent 41566ab5fb
commit 9148b5fb6a
21 changed files with 1092 additions and 49 deletions
+65 -1
View File
@@ -1,7 +1,8 @@
"""Paramètres persistés en DB (activation des sources, réglages UI…)."""
"""Paramètres persistés en DB (activation des sources, santé, réglages UI…)."""
import json
import logging
import secrets
from datetime import UTC, datetime
from app.db import db
@@ -35,6 +36,48 @@ async def set_source_enabled(name: str, enabled: bool) -> None:
await set_setting(f"source:{name}:enabled", enabled)
logger.info("Source %s %s", name, "activée" if enabled else "désactivée")
async def get_source_health(name: str) -> dict | None:
"""Dernier état de santé connu d'une source (None si jamais testée)."""
value = await get_setting(f"source:{name}:health")
return value if isinstance(value, dict) else None
async def set_source_health(name: str, healthy: bool, detail: str) -> dict:
state = {
"healthy": healthy,
"detail": detail,
"checked_at": datetime.now(UTC).isoformat(),
}
await set_setting(f"source:{name}:health", state)
return state
async def get_source_base_url(name: str) -> str | None:
"""URL personnalisée d'une source (None = valeur par défaut du code)."""
value = await get_setting(f"source:{name}:base_url")
return value if isinstance(value, str) and value else None
async def set_source_base_url(name: str, url: str | None) -> None:
"""Persiste l'URL personnalisée (None/'' → retour à la valeur par défaut)."""
key = f"source:{name}:base_url"
if url:
await set_setting(key, url)
else:
await db.execute("DELETE FROM settings WHERE key = ?", (key,))
async def apply_source_base_urls() -> None:
"""Applique les URL personnalisées aux instances de sources (au démarrage)."""
from app.scrapers.base import all_sources
for source in all_sources():
override = await get_source_base_url(source.name)
if override and override != type(source).base_url:
source.base_url = override
logger.info("URL personnalisée pour %s : %s", source.name, override)
# ---------------------------------------------------------------- intégrations *arr
TORZNAB_APIKEY_KEY = "torznab:apikey"
@@ -70,3 +113,24 @@ async def set_sonarr_config(url: str, apikey: str) -> None:
await set_setting(SONARR_URL_KEY, url.rstrip("/"))
await set_setting(SONARR_APIKEY_KEY, apikey.strip())
logger.info("Configuration Sonarr enregistrée (%s)", url)
# ---------------------------------------------------------------- mise à jour logicielle
UPDATE_GITEA_URL_KEY = "update:gitea_url"
UPDATE_REPO_KEY = "update:repo"
UPDATE_TOKEN_KEY = "update:token"
async def get_update_config() -> dict[str, str]:
return {
"gitea_url": await get_setting(UPDATE_GITEA_URL_KEY, "https://git.lanro.eu"),
"repo": await get_setting(UPDATE_REPO_KEY, "Roman/ohm_streaming"),
"token": await get_setting(UPDATE_TOKEN_KEY, ""),
}
async def set_update_config(gitea_url: str, repo: str, token: str) -> None:
await set_setting(UPDATE_GITEA_URL_KEY, gitea_url.rstrip("/"))
await set_setting(UPDATE_REPO_KEY, repo.strip().strip("/"))
await set_setting(UPDATE_TOKEN_KEY, token.strip())
logger.info("Configuration de mise à jour enregistrée (%s)", repo)