Barre de sante communautaire pour TR4KER : sondes temps reel (site/API/tracker/chat/torznab/recherche/cert), dashboard latence, incidents/MTTR, change-tracker, abonnements. Projet communautaire independant, non affilie.
  • Python 55.7%
  • JavaScript 19.1%
  • CSS 14.8%
  • HTML 10.3%
Find a file
2026-08-19 21:06:32 +00:00
docs doc : tracker profond (contenu des bundles), sources produit, api_shapes anti-flap, versionnage git prive 2026-08-18 00:59:02 +00:00
web chip categorie Contenu (annonces/boutique/badges/teams) 2026-08-19 21:06:32 +00:00
.env.example t4health: initial public release 2026-08-07 03:58:35 +02:00
.gitignore t4health: initial public release 2026-08-07 03:58:35 +02:00
bugs_job.py t4health: initial public release 2026-08-07 03:58:35 +02:00
changes_interp_job.py interpretation v3 : extraction bundles reelle (backticks + regex plein texte -> 79 API, 50 routes) + repli deterministe 2026-08-18 00:13:11 +00:00
docker-compose.shot.yml t4health: initial public release 2026-08-07 03:58:35 +02:00
docker-compose.yml t4health: initial public release 2026-08-07 03:58:35 +02:00
LICENSE t4health: initial public release 2026-08-07 03:58:35 +02:00
monitor.py sources produit en categorie dediee « content » (Contenu) 2026-08-19 21:06:31 +00:00
README.md t4health: initial public release 2026-08-07 03:58:35 +02:00
refresh_watcher.sh t4health: initial public release 2026-08-07 03:58:35 +02:00
RUNBOOK.md t4health: initial public release 2026-08-07 03:58:35 +02:00
shot.py t4health: initial public release 2026-08-07 03:58:35 +02:00
site_snapshot_git.py versionnage git de l etat du site (materialise les snapshots -> depot prive, un commit par changement) 2026-08-18 00:55:40 +00:00

t4health — barre de santé communautaire de TR4KER

Live : https://t4health.ekaii.fr · statut & latence en temps réel de tr4ker.net et de tous ses services.

Projet communautaire indépendant — non affilié à TR4KER, ni géré par son équipe. Fait par des membres, pour les membres. Source d'info indicative : en cas de doute, référez-vous au site officiel.

  • Backend : un seul fichier Python, stdlib pure (monitor.py, aucune dépendance pip), SQLite, ~9 Mo de RAM.
  • Frontend : HTML/CSS/JS self-contained (Apache ECharts vendored, zéro CDN), thème sombre, responsive, PWA.
  • Fallback sans JavaScript : /nojs (page rendue serveur, courbes SVG, meta-refresh).

1. Pourquoi un simple ping ne suffit pas (l'insight fondateur)

TR4KER est derrière Cloudflare. Le front est une SPA : l'edge CF sert le HTML statique (et répond 200 OK) même quand le backend d'origine est mort. Un monitoring naïf (« GET / → 200 → tout va bien ») est donc structurellement aveugle : il verra un site « opérationnel » pendant que l'API, le tracker et le chat sont hors service.

La conséquence architecturale : une sonde par service applicatif, chacune exerçant le vrai chemin de code du service (une vraie annonce bencodée pour le tracker, une vraie recherche pour l'index, un vrai handshake WebSocket pour le chat…). La sonde site (GET / sur l'edge) est conservée exprès comme témoin : quand elle est verte et que tout le reste est rouge, c'est précisément la signature « edge vivant / origine morte ».

Second niveau du même problème : les pages d'erreur Cloudflare (52x) reviennent parfois en 200 habillé ou en HTML — d'où is_cf_502() qui reconnaît à la fois les codes 502/503/504 et les corps d'erreur signés Cloudflare, pour classer « origine injoignable » et non « site down ».

2. Architecture

                        ┌───────────────────────────── hôte ─────────────────────────────┐
  internet ── Traefik ──►  conteneur t4health (python:3.12-slim, stdlib pure)             │
  t4health.ekaii.fr     │   monitor.py :9790                                              │
                        │   ├─ prober_loop    : 8 sondes / 30 s ──► SQLite /data          │
                        │   ├─ stats_loop     : stats publiques TR4KER / 5 min            │
                        │   ├─ tracker_loop   : change-tracker (10 sources) / 10 min      │
                        │   └─ HTTP server    : / (SPA), /nojs, /api/*, /admin, RSS…      │
                        │                                                                 │
                        │  conteneur t4shot (playwright)                                  │
                        │   └─ screenshots tr4ker.net / 5 min ──► /data/shots (partagé)   │
                        │                                                                 │
                        │  hôte : bugs_job.py (cron horaire) — mine le chat TR4KER,       │
                        │   résume les bugs via LLM ──► data/bugs.json (servi par /bugs)  │
                        └─────────────────────────────────────────────────────────────────┘

Tout le code est monté en volume (:ro) dans les conteneurs : mettre à jour = éditer le fichier sur l'hôte + docker restart t4health. Pas d'image à rebuilder.

3. Les 8 sondes — ce que chacune teste vraiment

# Sonde Cible Ce qu'elle prouve réellement
1 site GET / (HTML) L'edge Cloudflare sert le site. Volontairement superficielle : c'est le témoin — verte + tout le reste rouge = origine morte derrière un edge vivant.
2 api GET /api/public/config L'application d'origine répond. Exige 200 ET un corps JSON : un 200 HTML est le fallback SPA de l'edge, pas l'API (→ warn). 502/503/504 ou page d'erreur CF = origine injoignable.
3 session GET /api/me (cookie) Le chemin authentifié complet (middleware session + DB utilisateur). Un 401/403 = l'origine va bien, c'est notre cookie qui a expiré → warn, jamais down : on ne fait pas passer une panne du monitoring pour une panne du service.
4 chat handshake WebSocket /api/ws Le serveur temps réel. La stdlib n'a pas de client WS : on fait l'Upgrade HTTP/1.1 à la main sur un socket TLS et on exige le 101 Switching Protocols. Un reverse-proxy qui répond 200 en HTML échouerait ici.
5 tracker vraie annonce BitTorrent sur tk.tr4ker.net/announce/<passkey> L'application tracker elle-même, pas sa reachability : info_hash synthétique + event=stopped (ne laisse aucun état peer), et on exige une réponse bencodée (d…e). Même un failure reason bencodé compte up : l'appli a parsé et répondu. Sans passkey, dégrade en simple reachability (annoncé tel quel dans detail).
6 torznab GET /api/torznab?t=caps (clé API) La surface d'intégration *arr (Prowlarr/Radarr/Sonarr) : exige le XML <caps>.
7 search torznab t=search&q=1984 Une vraie recherche qui traverse l'index/la DB (pas un simple caps statique) ; compte les <item>. 0 résultat = warn (l'index répond mais vide — suspect).
8 cert TLS notAfter de tr4ker.net + tk.tr4ker.net Expiration du certificat (min des deux hôtes). Sondée 1 round sur 20, et exclue du graphe de latence : son « ms » est un handshake TLS, pas comparable. <12 j = warn, expiré = down.

Chaque échantillon stocke (state, http, latency_ms, detail) — le detail est toujours une phrase honnête sur ce qui a été observé (« annonce ok », « origine injoignable (CF 502) », « cookie expiré (401) »…), jamais un code brut sans contexte.

Services critiques : api et tracker — eux seuls peuvent faire basculer l'état global en « Panne majeure ».

Les sondes authentifiées (session, chat, stats, profils, avatars) réutilisent un cookie de session déjà établi, monté en lecture seule dans le conteneur (/secrets/t4.cookie). Pourquoi ne pas se loguer à chaque sonde ?

  • le login TR4KER est rate-limité (et surveillé) : 2 880 logins/jour feraient bloquer le compte ;
  • aucun mot de passe n'a besoin d'exister dans la config du monitor (le cookie suffit, et il est révocable) ;
  • l'expiration du cookie est un état prévu et distinct : session passe warn avec « cookie expiré », le reste du monitoring continue.

Le cookie appartient au bot d'entraide communautaire (même compte membre) et est partagé entre t4health, t4shot et bugs_job via des montages :ro.

5. État global glissant & anti-flap

compute_overall() ne regarde pas que le dernier échantillon — sinon on afficherait « Opérationnel » entre deux coupures d'une panne qui flappe. Il combine :

  • instantané : états courants des 8 sondes ;
  • fenêtre glissante (T4H_STATE_WINDOW, 300 s) : nombre de down et médiane de latence par service ;
  • lenteur : médiane récente OU latence courante > T4H_SLOW_MS (2 500 ms) → état slow (« Ralentissements ») même en HTTP 200 ;
  • instabilité : ≥ 2 coupures dans la fenêtre alors que le service est up maintenant → « Instable » ;
  • panne soutenue qui flappe : un service critique majoritairement down sur la fenêtre (> 50 %) est traité comme down même si l'échantillon courant est up.

Hiérarchie résultante : down (critique) > degraded (panne non critique / instable / warn) > slow > up.

Les alertes abonnés ont leur propre débounce, plus conservateur (evaluate_alerts()) : une panne critique doit être soutenue ≥ 180 s (T4H_ALERT_DOWN_AFTER) avant d'alerter, et le « rétabli » exige 120 s stables (T4H_ALERT_UP_AFTER). La phase (up/down) est persistée en DB (meta_kv) : un restart du conteneur ne renvoie pas de fausses alertes. Promesse aux abonnés : uniquement les vraies pannes, pas de spam — c'est ce mécanisme qui la tient.

Les incidents (table incidents) sont les transitions franchissant la frontière down (up/warn ↔ down), notifiées sur Discord/ntfy et exposées en RSS (/incidents.xml).

6. Change-tracker (« Nouveautés »)

Toutes les 10 min (tracker_loop), 10 sources sont capturées, canonicalisées, puis diffées contre leur dernier snapshot (table snapshots) :

Source Ce qu'on détecte
config /api/public/config : ouverture/fermeture des inscriptions, mode maintenance, tout paramètre public
frontend hash des assets (= déploiement front détecté), noms de chunks (= nouvelles features), endpoints /api/* référencés dans le JS, nouveaux hôtes tiers contactés
api_shapes schéma (clés + types) de 3 endpoints JSON : champs ajoutés/retirés/retypés
torznab catégories de recherche, capacités searching
tracker interval/min interval d'annonce, apparition de /scrape
robots / security diff ligne à ligne de robots.txt / security.txt (avec garde anti-fallback SPA)
headers en-têtes de sécurité (CSP, HSTS…), noms des cookies posés
dns A/AAAA/MX/TXT/NS via DNS-over-HTTPS (indépendant du résolveur local)
cert émetteur, serial (= renouvellement), SANs (= nouveaux domaines)

Deux règles de robustesse qui évitent tout le bruit :

  1. fetch échoué → cycle ignoré (jamais de faux « tout a été supprimé » pendant une panne) ;
  2. premier passage = baseline muette (aucun événement au bootstrap).

Les événements ont une sévérité (info/notable/major) ; notable+ part sur Discord. UI : /changes, flux RSS /changes.xml, modération (masquage) via l'admin. En bonus, track_stats_anomaly() alerte sur toute variation ≥ 15 % des compteurs membres/torrents (± 35 % = major) — le canari des purges et des migrations.

7. Abonnements aux pannes

POST /api/subscribe accepte deux cibles : un topic ntfy (ou URL ntfy self-hostée) et un webhook HTTPS (payload spécifique Discord détecté par hôte, sinon JSON générique). Garde-fous :

  • anti-SSRF (safe_public_url) : HTTPS obligatoire, résolution DNS de l'hôte et rejet de toute IP privée / loopback / link-local / réservée — le monitor ne peut pas être utilisé pour frapper le LAN ;
  • confirmation par l'envoi : l'abonnement n'est stocké que si le message de bienvenue part vraiment (une cible invalide n'entre jamais en base) ;
  • token de désinscription dans chaque message (/api/unsub?t=…), plafond global (T4H_SUBS_MAX), rate-limit 6 tentatives / 10 min / IP ;
  • en-tête Title ntfy translittéré en ASCII (exigence ntfy), corps UTF-8.

8. Couche communautaire

  • Chat (/api/messages) : pseudo optionnel mémorisé, réponses (fils), badge staff. Anti-spam : champ honeypot + 8 s entre messages par IP, 400 caractères max, rétention des 500 derniers. Rendu XSS-safe (échappement systématique, jamais d'innerHTML sur données utilisateur). Modération (suppression, réponse « Staff ») via /admin.
  • « Je suis affecté » (/api/affected) : compteur façon Downdetector, dédupliqué 10 min/IP, histogramme temporel — corrèle le ressenti utilisateur avec les sondes.
  • Profils (/api/user?u=) : lookup d'un membre TR4KER (via la session), en respectant les drapeaux de vie privée (hide_uploads/hide_ratio filtrés côté serveur), cache 5 min, rate-limit 30/min/IP. Avatars proxifiés (/api/avatar) car l'image d'origine exige le cookie.
  • Bugs & réponses staff (/bugs) : bugs_job.py (cron hôte) mine les canaux de chat TR4KER, un LLM en extrait les bugs distincts + la position du staff → data/bugs.json. Un statut « corrigé/reconnu » exige un message staff ; les messages du bot lui-même sont exclus (anti-boucle d'écho). Bouton « rafraîchir » de l'admin → flag data/bugs.refresh relu par un watcher hôte.
  • Vantages (/api/vantages) : points de vue multi-réseaux. Le monitor s'auto-déclare (« Hébergeur EU ») ; d'autres sondes peuvent poster via POST /api/vantage + token. Règle de lecture affichée : un seul réseau rouge = problème local ; tous rouges = vraie panne.

9. t4shot — monitoring visuel

Conteneur séparé (image Playwright), car un navigateur n'a rien à faire dans un conteneur stdlib. Toutes les 5 min : screenshot de tr4ker.net (avec le cookie de session), delta visuel vs capture précédente (niveaux de gris 64×64, différence moyenne normalisée 01). Attrape la classe de pannes invisible en HTTP : le site répond 200 mais a l'air cassé (CSS mort, page blanche, page d'erreur habillée). Conserve les 10 dernières captures + meta.json, servis par t4health (/api/shots, /shots/…) — le dossier data/shots est le seul point de contact entre les deux conteneurs.

10. Stockage

SQLite (/data/t4health.db, WAL) — tables : checks (échantillons), incidents, laststate, messages, stats, vantages, affected, subs, meta_kv, snapshots, changes. Écritures sérialisées par un lock process-unique ; lectures concurrentes libres (serveur HTTP threadé). history_json downsample côté serveur (~400 points max par série) pour que le front reste léger sur 7 j.

11. Frontend

  • Self-contained : ECharts vendored (web/echarts.min.js, Apache-2.0), aucun CDN, aucune requête tierce — la page de statut doit marcher quand tout le reste brûle.
  • Assets versionnés par query-string (style.css?v=N, app.js?v=N) — voir RUNBOOK.
  • /nojs : version 100 % serveur (SVG inline, meta-refresh) pour Tor Browser / JS désactivé, chat inclus (formulaire POST classique).
  • /badge.svg : badge de statut embarquable.
  • PWA (manifest + service-worker minimal), robots.txt/sitemap.xml (la page est censée être trouvée par « tr4ker down »).

12. Sécurité & vie privée

  • Aucun secret dans ce dépôt : tout vient d'.env (voir .env.example) et d'un cookie monté hors repo. Le .gitignore refuse .env, *.cookie, data/, *.db, *.log.
  • Admin : header X-Admin-Token comparé en temps constant (hmac.compare_digest) ; pas de session, pas de cookie admin.
  • Anti-SSRF sur les cibles d'abonnement (voir §7). Path-traversal bloqué sur les fichiers statiques et les captures (normpath + préfixe).
  • IPs des visiteurs : utilisées pour le rate-limiting et stockées brutes pour affected/messages (modération) — jamais affichées publiquement.
  • Publier le code révèle les mécanismes anti-spam et de sonde : assumé, ils ne reposent pas sur le secret.

13. Déployer

Voir RUNBOOK.md (déploiement complet, mises à jour, gotchas) et docker-compose.yml / .env.example. Résumé : cp .env.example .env (remplir), déposer le cookie dans ./secrets/t4.cookie, docker compose up -d.

14. Licence

MIT (voir LICENSE). web/echarts.min.js est Apache ECharts, licence Apache-2.0, vendored non modifié.