Aller au contenu

Système de configuration

Stream Fusion utilise une architecture de configuration 2-couches qui combine variables d'environnement et base de données PostgreSQL pour offrir à la fois simplicité de déploiement et reconfiguration à chaud sans redémarrage.


Architecture 2-couches

graph TD
    ENV[".env / variables<br/>d'environnement"] -->|"Pydantic-settings"| SINGLETON["Settings()<br/>Singleton ~150 champs"]
    PG[("PostgreSQL<br/>app_settings")] -->|"seed_defaults()<br/>+ apply_overrides_to_singleton()"| SINGLETON
    SINGLETON --> APP["Application<br/>57+ fichiers import"]
    ADMIN["Admin Dashboard<br/>/admin/settings"] -->|"form → set()"| PG
    ADMIN -->|"patch in-process"| SINGLETON
    REDIS[("Redis<br/>cache TTL 5min")] -.->|"invalide + recache"| APP

    style ENV fill:#1b5e20,color:#fff
    style PG fill:#1b5e20,color:#fff
    style SINGLETON fill:#311b92,color:#fff
    style ADMIN fill:#bf360c,color:#fff
    style REDIS fill:#e65100,color:#fff

Couche 1 : Variables d'environnement

Le fichier settings.py définit un singleton Pydantic Settings() (~150 champs) initialisé à partir de :

  • .env à la racine du projet
  • /run/secrets/ (secrets Docker Swarm)
  • Variables d'environnement du conteneur
class Settings(BaseSettings):
    port: int = 8080
    redis_host: str = "redis"
    log_level: LogLevel = LogLevel.INFO
    # ... ~150 champs

    model_config = SettingsConfigDict(
        env_file=".env",
        secrets_dir="/run/secrets",
    )

settings = Settings()

Couche 2 : Surcharges PostgreSQL

Les valeurs stockées dans la table app_settings écrasent les valeurs par défaut de la couche 1 au démarrage. Le service SettingsService (services/settings/settings_service.py) assure la cohérence entre les deux couches :

# Au démarrage (lifespan.py)
settings_service = SettingsService(session_factory, redis_pool)
await settings_service.seed_defaults()          # Insère les valeurs env par défaut
await settings_service.apply_overrides_to_singleton()  # Applique les surcharges DB

Flux de modification admin

Lorsqu'un administrateur modifie un paramètre via le panneau d'administration (/admin/settings) :

sequenceDiagram
    participant Admin as Admin Dashboard
    participant API as FastAPI
    participant PG as PostgreSQL
    participant Redis as Redis
    participant Singleton as Settings()

    Admin->>API: POST /admin/settings/save (key=log_level, value=DEBUG)
    API->>SettingsService: await set("log_level", "DEBUG")
    SettingsService->>PG: upsert(key, raw)
    SettingsService->>Redis: delete cache key
    SettingsService->>Singleton: object.__setattr__(_singleton, key, value)
    Note over Singleton: Changement immédiat<br/>dans ce worker uniquement
    Redis-->>API: Autres workers → refresh via Redis TTL (5 min)

Immédiat et sans redémarrage

Le SettingsService.set() applique le changement sur le singleton dans le processus courant immédiatement. Les autres workers Gunicorn verront la nouvelle valeur dans un délai maximal de 5 minutes (TTL du cache Redis).


SettingsRegistry

Le fichier services/settings/settings_registry.py définit la source de vérité pour tous les paramètres administrables. Chaque paramètre est un SettingDef :

@dataclass
class SettingDef:
    key: str                # Nom de l'attribut sur le singleton Settings
    type: SettingType       # "bool" | "int" | "str" | "float" | "enum"
    label: str              # Libellé français pour l'admin
    category: str           # Catégorie de regroupement
    requires_restart: bool  # Nécessite un redémarrage de l'app ?
    enum_choices: list[str] # Valeurs possibles si type="enum"
    description: str        # Aide contextuelle (français)

100+ définitions

Le registre contient plus de 100 SettingDef, organisés en 16 catégories, tous avec des descriptions en français.


Catégories

Catégorie Clé Description
Général general Paramètres généraux (download, inscription, HTTPS, logs)
Proxy proxy Proxyfication des liens et rate limiting
Cache cache TTL Redis, verrous de refresh
Indexeurs indexers Flags d'activation des indexeurs (C411, Torr9, etc.)
URLs Indexeurs indexer_urls URLs de base des indexeurs
Avancé Indexeurs indexers_advanced Délai entre appels background
Système system Workers Gunicorn, pool PostgreSQL, pools HTTP
TMDB tmdb Langue, rate limit API TMDB
Meilisearch meilisearch Hôte, port, clé, activation cache public
U2P u2p Relais Nostr, catégories, timeouts
DMM dmm Dépôt hashlists, chemins, activation sync
IMDB imdb Build DuckDB, enrichissement, régions AKA
TRaSH trash Scoring, sync, templates
Tâches planifiées tasks Schedule enabled flags et expressions cron
Dumps DB system / tasks Dumps DuckDB, Meilisearch, PostgreSQL
Indexeurs privés indexers Comptes uniques, flags d'activation

Modèle de données

Table app_settings

Colonne Type Description
id uuid Identifiant unique
key varchar(255) Nom du paramètre (ex: log_level)
value text Valeur sérialisée en chaîne
updated_at bigint Timestamp Unix de dernière modification
updated_by varchar(64) Source de la modification (admin, reset, seed)

DAO

Le fichier stream_fusion/services/postgresql/dao/appsetting_dao.py expose :

Méthode Description
get(key) Lire une valeur (chaîne brute)
get_all() Dictionnaire {key: value} de toutes les surcharges
upsert(key, value, updated_by) Insérer ou mettre à jour (ON CONFLICT DO UPDATE)
seed_defaults(defaults) Insérer les valeurs par défaut env (ON CONFLICT DO NOTHING)
reset(key) Restaurer la valeur env par défaut

Flux de démarrage

sequenceDiagram
    participant ENV as .env / Variables env
    participant Settings as Settings() singleton
    participant PG as PostgreSQL
    participant Redis as Redis
    participant Service as SettingsService
    participant App as Application

    ENV->>Settings: Pydantic-settings load (150+ champs)
    Service->>PG: seed_defaults()
    Note over PG: ON CONFLICT DO NOTHING<br/>— préserve les surcharges admin existantes
    Service->>Settings: apply_overrides_to_singleton()
    Note over Settings: object.__setattr__() pour chaque surcharge DB
    Service->>Redis: Cache redis prêt (TTL 5 min)
    App->>Settings: Import “from stream_fusion.settings import settings”
    Note over App: Le singleton contient déjà les surcharges DB

Variables d'environnement vs Administration

Critère Variables d'environnement Panneau d'administration
Persistance Fichier .env, secrets Docker Table app_settings PostgreSQL
Application Au démarrage uniquement Immédiate (ce worker), 5 min (autres)
Redémarrage requis Toujours Rarement (sauf requires_restart=True)
Portée Globale (tous les workers) Globale (via DB + Redis)
Versionnage Git (.env.example) Non versionné (données d'instance)
Utilisateur cible Déploiement initial, secrets Configuration continue, A/B testing
Exemples TMDB_API_KEY, PG_PASS log_level, allow_debrid_download
Sécurité Fichiers protégés, jamais dans les logs Chiffré au repos (PG), redacted dans les logs

Bonne pratique

Utilisez les variables d'environnement pour les secrets (clés API, mots de passe) et les paramètres structurels (taille de pool, hôtes). Utilisez le panneau d'administration pour les paramètres comportementaux que vous pourriez ajuster en cours d'exploitation (niveau de log, activation de fonctionnalités, expressions cron).


API SettingsService

Le service expose les méthodes publiques suivantes :

Méthode Signature Description
get() async (key: str) -> Any Redis → DB → singleton fallback
get_all_effective() async () -> dict[str, Any] Toutes les valeurs effectives (pour l'admin)
set() async (key: str, value: Any) Persiste DB, invalide Redis, patch singleton
set_many() async (updates: dict) -> list[str] Batch update, retourne les clés nécessitant restart
seed_defaults() async () Insère les valeurs env par défaut au démarrage
reset_many() async (keys: list) -> list[str] Restaure les valeurs env par défaut
apply_overrides_to_singleton() async () Applique toutes les surcharges DB au démarrage

Gestion des enums

Le service gère automatiquement la conversion entre types Python et chaînes DB pour les champs enum (LogLevel, DebridService, NoCacheVideoLanguages). Pour no_cache_video_language, le formulaire admin utilise le nom de l'enum (FR/EN) plutôt que sa valeur (URL complète).