Trackers: cache 1h + persistance disque (/data) pour survivre aux redéploiements
This commit is contained in:
@@ -41,6 +41,10 @@ MONITORINK_CODEX_TOKEN_FILE=/hermes/auth.json
|
|||||||
# Liste des clés actives, puis UN bloc par tracker. v1 : type "unit3d_nuxt" (ex. c411).
|
# Liste des clés actives, puis UN bloc par tracker. v1 : type "unit3d_nuxt" (ex. c411).
|
||||||
# Le ratio n'est PAS lisible au token API -> login requis (username/password du compte).
|
# Le ratio n'est PAS lisible au token API -> login requis (username/password du compte).
|
||||||
# Laisser MONITORINK_TRACKERS vide pour masquer la section.
|
# Laisser MONITORINK_TRACKERS vide pour masquer la section.
|
||||||
|
# Cache : le ratio bouge lentement, on le garde 1 h (TTL) et on le persiste sur disque
|
||||||
|
# (volume /data) pour ne pas reloguer les 4 trackers après chaque redéploiement.
|
||||||
|
#MONITORINK_TRACKER_TTL=3600
|
||||||
|
#MONITORINK_TRACKER_CACHE_FILE=/data/trackers.json
|
||||||
#MONITORINK_TRACKERS=c411,torr9
|
#MONITORINK_TRACKERS=c411,torr9
|
||||||
#MONITORINK_TRACKER_C411_LABEL=c411
|
#MONITORINK_TRACKER_C411_LABEL=c411
|
||||||
#MONITORINK_TRACKER_C411_TYPE=unit3d_nuxt
|
#MONITORINK_TRACKER_C411_TYPE=unit3d_nuxt
|
||||||
|
|||||||
@@ -98,10 +98,15 @@ class Config:
|
|||||||
)
|
)
|
||||||
|
|
||||||
# --- Trackers torrent privés (ratio du compte) ---
|
# --- Trackers torrent privés (ratio du compte) ---
|
||||||
# Le ratio change lentement et le login est coûteux (CSRF + session) : on cache
|
# Le ratio change lentement (~30 min/1 h côté trackers) et le login est coûteux
|
||||||
# le résultat plus longtemps que le reste (défaut 30 min).
|
# (CSRF + session) : on cache le résultat plus longtemps que le reste (défaut 1 h).
|
||||||
tracker_ttl_seconds: int = field(
|
tracker_ttl_seconds: int = field(
|
||||||
default_factory=lambda: int(_get("MONITORINK_TRACKER_TTL", "1800"))
|
default_factory=lambda: int(_get("MONITORINK_TRACKER_TTL", "3600"))
|
||||||
|
)
|
||||||
|
# Cache persistant sur disque : survit aux redéploiements du conteneur, évite de
|
||||||
|
# reloguer les 4 trackers d'un coup après chaque rebuild. Monté via le volume /data.
|
||||||
|
tracker_cache_file: str = field(
|
||||||
|
default_factory=lambda: _get("MONITORINK_TRACKER_CACHE_FILE", "/data/trackers.json")
|
||||||
)
|
)
|
||||||
|
|
||||||
# --- Cache / rafraîchissement serveur ---
|
# --- Cache / rafraîchissement serveur ---
|
||||||
|
|||||||
@@ -18,9 +18,11 @@ renvoie un `TrackerStat`, et l'enregistrer dans `_FETCHERS` sous sa clé de `typ
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import json
|
||||||
|
import os
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
from dataclasses import dataclass
|
from dataclasses import asdict, dataclass
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
@@ -80,6 +82,42 @@ class TrackerStat:
|
|||||||
# Sessions réutilisées (cookies httpx) et derniers résultats connus, par tracker.
|
# Sessions réutilisées (cookies httpx) et derniers résultats connus, par tracker.
|
||||||
_sessions: dict[str, httpx.Cookies] = {}
|
_sessions: dict[str, httpx.Cookies] = {}
|
||||||
_cache: dict[str, dict] = {}
|
_cache: dict[str, dict] = {}
|
||||||
|
_loaded = False # le cache disque n'est lu qu'une fois par process
|
||||||
|
|
||||||
|
|
||||||
|
def _load_cache() -> None:
|
||||||
|
"""Hydrate `_cache` depuis le fichier JSON persistant (une fois par process).
|
||||||
|
Permet de survivre à un redéploiement sans reloguer les 4 trackers d'un coup.
|
||||||
|
Tout fichier absent/illisible/corrompu est ignoré silencieusement."""
|
||||||
|
global _loaded
|
||||||
|
if _loaded:
|
||||||
|
return
|
||||||
|
_loaded = True
|
||||||
|
try:
|
||||||
|
with open(config.tracker_cache_file, encoding="utf-8") as fh:
|
||||||
|
data = json.load(fh)
|
||||||
|
for key, entry in data.items():
|
||||||
|
_cache[key] = {"value": TrackerStat(**entry["value"]), "ts": float(entry["ts"])}
|
||||||
|
except (OSError, ValueError, KeyError, TypeError):
|
||||||
|
pass # cache absent/corrompu -> on repart d'un cache vide, re-fetch propre
|
||||||
|
|
||||||
|
|
||||||
|
def _save_cache() -> None:
|
||||||
|
"""Réécrit tout `_cache` sur disque de façon atomique (tmp + os.replace), comme le
|
||||||
|
refresh du token Claude. Une erreur d'écriture ne doit jamais casser le rendu."""
|
||||||
|
path = config.tracker_cache_file
|
||||||
|
payload = {
|
||||||
|
key: {"value": asdict(entry["value"]), "ts": entry["ts"]}
|
||||||
|
for key, entry in _cache.items()
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
os.makedirs(os.path.dirname(path) or ".", exist_ok=True)
|
||||||
|
tmp = f"{path}.tmp"
|
||||||
|
with open(tmp, "w", encoding="utf-8") as fh:
|
||||||
|
json.dump(payload, fh)
|
||||||
|
os.replace(tmp, path)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class _AuthError(Exception):
|
class _AuthError(Exception):
|
||||||
@@ -275,6 +313,7 @@ async def _fetch_one(spec: TrackerSpec) -> TrackerStat:
|
|||||||
return TrackerStat(spec.key, spec.label, ok=False, error=str(exc) or "injoignable")
|
return TrackerStat(spec.key, spec.label, ok=False, error=str(exc) or "injoignable")
|
||||||
|
|
||||||
_cache[spec.key] = {"value": value, "ts": now}
|
_cache[spec.key] = {"value": value, "ts": now}
|
||||||
|
_save_cache() # persiste la nouvelle valeur (survit aux redéploiements)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
@@ -282,4 +321,5 @@ async def fetch_all() -> list[TrackerStat]:
|
|||||||
specs = config.trackers
|
specs = config.trackers
|
||||||
if not specs:
|
if not specs:
|
||||||
return []
|
return []
|
||||||
|
_load_cache() # hydrate le cache depuis le disque au premier appel
|
||||||
return list(await asyncio.gather(*(_fetch_one(s) for s in specs)))
|
return list(await asyncio.gather(*(_fetch_one(s) for s in specs)))
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ services:
|
|||||||
# l'inode capturé au démarrage -> le conteneur servirait un token périmé (401).
|
# l'inode capturé au démarrage -> le conteneur servirait un token périmé (401).
|
||||||
# Monter le dossier fait re-résoudre /hermes/auth.json à chaque open().
|
# Monter le dossier fait re-résoudre /hermes/auth.json à chaque open().
|
||||||
- /home/jerem/.hermes:/hermes:ro
|
- /home/jerem/.hermes:/hermes:ro
|
||||||
|
# Cache persistant des ratios trackers (survit aux redéploiements -> pas de
|
||||||
|
# re-login massif des 4 trackers après chaque rebuild).
|
||||||
|
- /home/jerem/.monitorink-data:/data:rw
|
||||||
# Optionnel : burn rate via ccusage (lecture seule des logs Claude Code principaux).
|
# Optionnel : burn rate via ccusage (lecture seule des logs Claude Code principaux).
|
||||||
# Décommenter + MONITORINK_CCUSAGE=1.
|
# Décommenter + MONITORINK_CCUSAGE=1.
|
||||||
# - /home/jerem/.claude/projects:/root/.claude/projects:ro
|
# - /home/jerem/.claude/projects:/root/.claude/projects:ro
|
||||||
|
|||||||
Reference in New Issue
Block a user