← tous les articles

Surveiller les releases de mes dépendances — sans tout réinventer

3 juillet 2026 · 8 min de lecture

grafanan8nntfyautomatisationobservabiliteconfig-as-code

Le homelab suit une trentaine d’images à épingler : Traefik, Postgres, Authelia, Grafana, CrowdSec… Savoir qu’une nouvelle version sort — avec ses correctifs de sécurité — fait partie du travail. J’avais déjà un cron n8n qui interroge Docker Hub une fois par semaine, mais il ne couvre que Docker Hub, et pas les release notes, ni les CVE, ni GitHub ou npm.

newreleases.io fait exactement ça, pour tous les providers, et le fait bien. La tentation du homelab, c’est de tout self-héberger. La bonne décision, ici, c’était l’inverse : laisser le SaaS faire la veille — mais rapatrier sa sortie dans ma propre tuyauterie. Un compte existait déjà, avec les projets suivis. Je n’avais qu’à le brancher.

Deux morceaux, zéro nouvelle brique

Rien à déployer de neuf : je réutilise les stacks en place.

MorceauRôle
DashboardGrafana (observabilité)voir les projets suivis, par provider, par canal de notif
Alertesn8n + ntfywebhook → mise en forme → push sur mon téléphone

Pas de Prometheus dans l’histoire : il n’y a pas de métrique historique à tracer ici, juste un état courant à afficher. Ajouter un exporter + une série temporelle aurait été de la complexité gratuite. Savoir dire non à Prometheus, c’est aussi de l’observabilité.

Le dashboard : interroger une API REST, sans exporter

Grafana sait faire ça grâce au datasource Infinity : il interroge n’importe quelle API JSON et la transforme en table. Je le pointe directement sur api.newreleases.io, l’authentification passe par un en-tête X-Key injecté depuis un secret, et — détail qui compte — je verrouille l’hôte autorisé pour que ce datasource ne puisse pas devenir un proxy vers des adresses arbitraires :

jsonData:
  httpHeaderName1: X-Key
  allowedHosts:
    - https://api.newreleases.io   # et rien d'autre — pas de SSRF

Quatre panneaux : le total suivi, une répartition par provider, une par canal de notif e-mail, et la table détaillée. Ce troisième panneau n’est pas décoratif : il me sert de compteur de migration (j’y reviens).

Le piège : une pagination qui refuse de fusionner

L’API pagine par tranches de dix projets. Infinity a un mode « pagination automatique » intégré — et c’est là que ça a coincé. Dès qu’il dépasse la dernière page, l’API renvoie une liste vide, et Grafana refuse de fusionner cette page vide avec les précédentes :

unable to merge fields due to different fields

Le mode automatique marche si on lui donne le nombre exact de pages — mais ce nombre change dès qu’on ajoute un projet. Fragile. La parade est moins maligne mais robuste : une requête explicite par page (page 1 à 4, de quoi voir venir jusqu’à ~40 projets), fusionnées par une transformation Grafana. Une page vide renvoie alors zéro ligne, proprement, sans casser la fusion.

Leçon rangée avec les autres : l’astuce élégante qui dépend d’une valeur mouvante est un piège ; la version un peu bête mais insensible aux cas limites vieillit mieux.

Les alertes : de l’e-mail vers ma propre fabrique

Par défaut, newreleases notifie par e-mail. Or j’ai déjà un canal maison pour tout le reste — alertes Grafana, déploiements : ntfy, en push sur le téléphone. Autant y verser les releases aussi, sur un topic dédié pour ne pas les noyer dans les alertes système.

Le chemin : newreleases envoie un webhook → un flux n8n public le capte, met en forme, et republie vers ntfy.

newreleases.io  ──webhook──►  n8n  ──►  ntfy (topic « releases »)  ──►  téléphone

Le nœud de mise en forme aplatit les release notes (qui arrivent en HTML) en texte lisible, et élève la priorité quand des CVE sont listées — une faille corrigée n’a pas le même poids qu’un bugfix mineur.

Le choix assumé : le jeton d’en-tête plutôt que la signature

newreleases signe chaque webhook (HMAC-SHA256 dans un en-tête). C’est la bonne pratique, et j’aurais aimé la vérifier. Mais dans n8n, le nœud de code ne peut pas lire un identifiant stocké pour recalculer la signature — la vérification HMAC demanderait de mettre le secret en clair dans le flux, ce qui est pire que le mal.

La parade est celle que j’utilise déjà pour mes autres webhooks : newreleases permet d’ajouter des en-têtes HTTP personnalisés. J’en ajoute un jeton partagé, et n8n l’authentifie nativement. Le secret vit dans le coffre chiffré de n8n, jamais dans le dépôt.

newreleases (en-tête personnalisé)   X-Webhook-Token: ••••••
        └── n8n : credential « Header Auth », vérifiée à la réception

Moins sophistiqué que le HMAC ? Oui. Mais vérifiable pour de vrai avec les outils en place, plutôt qu’une élégance qui obligerait à exposer un secret. Le bon compromis n’est pas le plus brillant sur le papier — c’est celui qui tient dans la vraie tuyauterie.

Tester sans attendre une vraie release

Une chaîne de notification qu’on n’a jamais vue passer est une chaîne qu’on croit fonctionnelle. Plutôt que d’attendre qu’un projet publie, j’ai simulé le webhook à la main avec un payload réaliste : 200 renvoyé, et le push est arrivé sur le téléphone, titre et notes en clair. Bout-en-bout confirmé — pas « probablement bon ».

Reste alors la seule action que le code ne peut pas faire à ma place : basculer, projet par projet, la notification d’e-mail vers le webhook. Et c’est là que le troisième panneau du dashboard prend son sens — il compte les projets encore en e-mail, et descend vers zéro à mesure que je migre. Le tableau de bord surveille sa propre migration.

Épilogue — la migration que je n’ai jamais faite à la main

Le paragraphe précédent n’a pas survécu une semaine. « Basculer projet par projet dans l’UI » : quinze fois le même clic, c’est exactement le genre de corvée qu’on repousse — et le compteur du dashboard restait obstinément au-dessus de zéro. La vraie solution était un cran au-dessus : arrêter de toucher l’UI tout court.

J’avais déjà ce pattern pour un autre SaaS de monitoring : un petit démon Python qui réconcilie l’état du compte avec un YAML versionné, via l’API. Même recette ici (dépôt public) : la liste des projets suivis, leurs filtres de version, leurs canaux de notification — tout vit dans un manager.yml en git, et le démon fait converger le compte toutes les quinze minutes.

defaults:
  email_notification: none      # plus d'e-mail nulle part…
  webhooks: ['zebbox.net']      # …tout part vers n8n → ntfy

projects:
  - github: restic/restic
    exclude_prereleases: true
  - dockerhub: php
    exclude_version_regexp:
      - {value: '^8\.[4-5]\.\d+($|-)', inverse: true}

Ajouter un check = trois lignes de YAML. Et surtout : dry-run par défaut. L’outil calcule son plan et l’affiche, mais ne peut pas écrire sans le flag explicite — parce qu’un réconciliateur qui sait supprimer des choses mérite la même prudence qu’un terraform apply. Premier lancement à blanc : le plan annonçait exactement les douze bascules e-mail→webhook attendues, rien d’autre. Apply. Douze projets migrés en dix secondes, compteur à zéro. La corvée que je repoussais depuis une semaine a disparu dans un git push.

Une leçon de conception au passage : vérifier les limites de l’API avant d’écrire le schéma YAML. L’API NewReleases liste les webhooks mais ne sait ni les créer ni les modifier — le canal reste géré une fois pour toutes dans l’UI, et le YAML se contente de l’y référencer par son nom. Si j’avais dessiné le schéma d’abord, j’aurais promis dans le fichier de config quelque chose que l’API ne sait pas tenir.

Le tiret qui a corrompu ma clé API en silence

Premier déploiement du démon : 401 Unauthorized à chaque appel. La clé était bonne — vérifiée à la main contre l’API. Le YAML était bon. Après inspection dans le conteneur : la variable d’environnement ne contenait pas ma clé, mais… un fragment de mon propre message d’erreur.

# Ce que j'avais écrit :
NEWRELEASES_API_KEY: ${NEWRELEASES_API_KEY:?set the key in apps/newreleases-manager .env}
# Ce que la variable valait à l'exécution : un morceau du message. Silencieusement.

Le parseur de compose s’emmêle dès qu’un message ${VAR:?…} contient un tiret — même enfoui dans un mot comme newreleases-manager — et, au lieu d’échouer bruyamment, substitue un bout du message à la place de la valeur. Le pire des deux mondes : ni la bonne valeur, ni une erreur.

L’ironie : je connaissais ce bug. Je l’avais documenté moi-même, six semaines plus tôt, dans un autre stack du même dépôt — pour un message contenant « read-only ». Mais un gotcha documenté ne protège que celui qui le relit : dans un mot composé anodin, le tiret est passé inaperçu. Depuis, la règle est mécanique : les messages :? n’ont droit ni aux tirets, ni aux parenthèses, ni aux flèches. Du texte plat, point.


Un dashboard, un flux d’automatisation, un topic de notification — puis un YAML qui pilote le compte entier, dry-run d’abord, et plus jamais l’interface web. Une trentaine de dépendances viennent me chercher sur le téléphone quand elles bougent, au lieu de l’inverse ; en ajouter une coûte trois lignes en git. La meilleure décision d’infra reste de laisser quelqu’un d’autre faire tourner le moteur — à condition de tenir le volant depuis chez soi. Le code est public : l’infra et le manager.


📬 La newsletter homelab

Les nouveaux articles + retours d'expérience self-hosting, sans spam. Désinscription en un clic.