Suivre les versions de ses applications avec Argus (GeoNature)
Ce tutoriel décrit la mise en place d”Argus, un outil qui surveille les versions des applications d’un parc et signale celles qui ont pris du retard. Il couvre l’installation, l’exposition derrière un proxy HTTPS, la configuration générale, puis le cas détaillé de GeoNature et de ses modules, qui demande un peu de travail parce que GeoNature n’expose pas la version de ses modules sans authentification.
L’idée directrice
Argus compare, pour chaque application, deux nombres : la dernière version publiée par l’éditeur et la version réellement déployée chez vous. Tout le travail de configuration consiste à lui indiquer où trouver ces deux nombres.
Note
Ce tutoriel n’est pas issu d’un atelier : il documente une mise en place réalisée sur l’infrastructure de RNF. Le public visé est un administrateur système à l’aise avec Linux, Apache et Docker.
Principe de fonctionnement
Pour chaque application suivie, Argus compare deux valeurs :
latest_versionLa dernière version publiée. Argus sait lire les releases d’un dépôt GitHub (le cas le plus courant) ou extraire un numéro d’une page web par expression régulière.
deployed_versionLa version installée chez vous. Argus la récupère par une requête HTTP — en pointant une clé dans une réponse JSON, ou par expression régulière — ou bien vous la saisissez à la main.
Avertissement
Argus ne sait interroger qu’en HTTP(S). Il ne se connecte pas en SSH, ne lit pas de fichier local, n’interroge pas une base de données. Pour une application qui n’expose pas sa version sans authentification, la méthode retenue ici consiste à publier un petit fichier JSON sur le serveur de l’application, généré par un script et servi par le serveur web déjà en place, avec un accès restreint à la seule IP d’Argus.
Argus répond à la question « suis-je à jour ? ». Il ne dit rien des dépendances internes d’une application développée en interne : pour cela, voir OWASP Dependency-Track et les SBOM CycloneDX, qui sortent du périmètre de ce tutoriel.
Prérequis et conventions
Ce qu’il vous faut
Un serveur (ou conteneur) dédié pour Argus, avec Docker.
Un accès administrateur aux serveurs des applications à suivre.
Un reverse proxy HTTPS si vous voulez exposer l’interface d’Argus publiquement.
Un jeton GitHub de lecture (facultatif mais fortement recommandé, voir plus bas).
Valeurs à remplacer
Avertissement
Tous les noms de domaine et toutes les adresses IP de ce tutoriel sont des exemples.
Remplacez-les par les vôtres. De même, aucun jeton d’authentification ne doit être
recopié tel quel ni committé dans un dépôt : les blocs ci-dessous utilisent des
variables d’environnement et des placeholders <…>.
Dans ce tutoriel |
À remplacer par |
|---|---|
|
l’IP interne de votre serveur Argus |
|
les IP internes de vos serveurs applicatifs |
|
le nom de domaine public de votre Argus |
|
le nom de domaine de votre instance GeoNature |
|
vos jetons, jamais en clair dans un dépôt |
La contrainte réseau à comprendre d’abord
C’est le point qui fait perdre le plus de temps si on le découvre en route. Dans une infrastructure où le TLS est terminé sur un proxy public, les serveurs applicatifs ne peuvent pas se joindre entre eux par leur nom de domaine public : le DNS renvoie l’IP du proxy, pas celle du conteneur. Trois conséquences :
Il faut forcer la résolution nom → IP interne dans le conteneur Argus, via
extra_hosts.Appeler l’IP seule ne suffit pas : Apache choisit son vhost d’après l’en-tête
Host. Une requête vershttp://10.0.0.22/atterrit sur le mauvais vhost et renvoie typiquement un404. D’où la combinaison vrai nom de domaine + extra_hosts.Le port 443 n’est généralement pas ouvert sur les conteneurs applicatifs, le TLS étant terminé au proxy. Argus les interroge donc en
http://sur le port 80.
Installer Argus
Préparer Docker
Sur Debian, le paquet docker.io ne fournit pas docker-compose-plugin. Il faut
installer Compose v2 manuellement — l’ancien docker-compose v1 ne sait pas lire les
fichiers de composition récents :
sudo mkdir -p /usr/local/lib/docker/cli-plugins
sudo curl -SL https://github.com/docker/compose/releases/latest/download/docker-compose-linux-x86_64 \
-o /usr/local/lib/docker/cli-plugins/docker-compose
sudo chmod +x /usr/local/lib/docker/cli-plugins/docker-compose
docker compose version
Vérification attendue : docker compose version affiche une version v2.x.
Autorisez ensuite votre utilisateur à piloter Docker :
sudo usermod -aG docker <utilisateur>
newgrp docker
Avertissement
Appartenir au groupe docker équivaut à disposer des droits root sur la machine.
N’y ajoutez que les comptes qui administrent réellement le service.
Créer l’arborescence
~/argus/
├── docker-compose.yml
├── config.yml # propriétaire UID 911
└── data/ # base SQLite argus.db, propriétaire UID 911
L’image tourne sous l’UID 911 : les fichiers montés doivent lui appartenir, sinon Argus ne pourra pas écrire sa base ni réenregistrer sa configuration.
mkdir -p ~/argus/data && cd ~/argus
touch config.yml
sudo chown -R 911:911 ~/argus/data ~/argus/config.yml
Le fichier docker-compose.yml
services:
argus:
image: releaseargus/argus:latest
volumes:
- ./config.yml:/app/config.yml
- ./data:/app/data
ports:
- "127.0.0.1:8092:8080"
healthcheck:
test: ["CMD", "/healthcheck", "http://localhost:8080/api/v1/healthcheck"]
interval: 1m
timeout: 10s
retries: 3
start_period: 10s
restart: unless-stopped
environment:
GITHUB_TOKEN: "<JETON_GITHUB>"
extra_hosts:
- "geonature-saisie.example.org:10.0.0.22"
- "geonature.example.org:10.0.0.21"
Deux choses à noter :
Le port est lié à
127.0.0.1: Argus n’est pas joignable directement depuis le réseau, uniquement via le proxy local décrit plus bas. Choisissez un port libre sur l’hôte (ici 8092) ; un port déjà pris provoquebind: address already in use.extra_hostscontient une ligne par application à interroger — c’est la réponse à la contrainte réseau vue plus haut.
Avertissement
Toute modification de ce fichier (variables d’environnement, extra_hosts) exige
docker compose up -d. Un simple docker compose restart ne recharge pas
l’environnement du conteneur, et vous chercherez longtemps pourquoi votre jeton reste
vide.
Premier démarrage
docker compose up -d
docker compose logs -f argus
Avertissement
Avec l’authentification activée, la page de création du premier administrateur est accessible sans identifiant : le premier visiteur devient administrateur. Créez ce compte immédiatement après le démarrage, avant d’exposer le service.
Exposer Argus en HTTPS
Vhost local sur le serveur Argus
Dans /etc/apache2/sites-available/argus.conf :
<VirtualHost *:80>
ServerName argus.example.org
RequestHeader set X-Forwarded-Proto "https"
ProxyPreserveHost On
ProxyRequests Off
ProxyPass /ws ws://127.0.0.1:8092/ws
ProxyPassReverse /ws ws://127.0.0.1:8092/ws
ProxyPass / http://127.0.0.1:8092/ upgrade=websocket
ProxyPassReverse / http://127.0.0.1:8092/
ErrorLog ${APACHE_LOG_DIR}/argus_error.log
CustomLog ${APACHE_LOG_DIR}/argus_access.log combined
</VirtualHost>
sudo a2enmod proxy proxy_http proxy_wstunnel headers
sudo a2ensite argus
sudo apachectl configtest && sudo systemctl reload apache2
Vérification attendue : configtest répond Syntax OK.
Proxy HTTPS public
Le piège : les WebSockets
L’interface d’Argus se rafraîchit via un WebSocket sur /ws. Si le proxy public ne le
relaie pas explicitement, la page affiche « WebSocket connecting » en boucle et Argus
journalise 'upgrade' token not found in 'Connection' header. L’interface semble
installée mais ne montre jamais de données.
<VirtualHost *:443>
ServerName argus.example.org
ProxyPreserveHost On
ProxyPass /ws ws://10.0.0.10/ws
ProxyPassReverse /ws ws://10.0.0.10/ws
ProxyPass / http://10.0.0.10/ upgrade=websocket
ProxyPassReverse / http://10.0.0.10/
RequestHeader set X-Forwarded-Proto "https"
# ModSecurity : exclure l'API et le WebSocket
<Location /api/v1>
SecRuleEngine Off
</Location>
<Location /ws>
SecRuleEngine Off
</Location>
ErrorLog ${APACHE_LOG_DIR}/argus-error.log
CustomLog ${APACHE_LOG_DIR}/argus-access.log combined
SSLCertificateFile /etc/letsencrypt/live/argus.example.org/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/argus.example.org/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf
</VirtualHost>
sudo a2enmod proxy_wstunnel est également nécessaire sur le proxy. La directive
upgrade=websocket demande Apache 2.4.47 ou plus récent.
Les exclusions ModSecurity sur /api/v1 et /ws évitent que le jeu de règles CRS
bloque l’API interne et la négociation du WebSocket.
Configurer Argus : le fichier config.yml
Structure générale
settings:
auth:
enabled: true
session:
secure_cookie: true # site servi uniquement en HTTPS
log:
level: INFO
web:
trusted_proxies:
- 127.0.0.1
- 172.16.0.0/12 # passerelle du réseau Docker
defaults:
service:
options:
interval: 6h
latest_version:
access_token: ${GITHUB_TOKEN}
service:
# … un bloc par application
Pourquoi un jeton GitHub
Sans jeton, GitHub limite l’API à 60 requêtes par heure et par IP : avec une vingtaine
de services vérifiés toutes les 6 heures, la limite tombe vite et Argus journalise
rate limit reached for GitHub. Un fine-grained token sans aucune permission
suffit — il ne sert qu’à lever la limite sur des dépôts publics — et fait passer le
quota à 5000 requêtes par heure. Notez sa date d’expiration.
Règles de fonctionnement
Cinq règles évitent la quasi-totalité des mauvaises surprises :
Argus lit
config.ymluniquement au démarrage. Après une édition manuelle :docker compose restart argus.L’interface web réécrit ``config.yml``. N’éditez jamais le fichier à la main pour ensuite enregistrer depuis l’interface sans redémarrage intermédiaire : vos modifications manuelles seraient écrasées. Les ancres YAML sont par ailleurs développées lors d’une réécriture.
Les services sont indentés de 2 espaces sous
service:, sans tabulation.Les variables
${VAR}sont substituées dansurl,headers,basic_authetaccess_token— pas dans ``body``. Un jeton placé dansbodypartira littéralement et produira un401.Validez toujours la syntaxe avant de redémarrer :
python3 -c 'import yaml; yaml.safe_load(open("config.yml")); print("YAML OK")'
Avertissement
Une erreur de syntaxe dans config.yml fait redémarrer le conteneur en boucle et
l’interface affiche une page vide. Le diagnostic se lit dans
docker compose logs argus.
Dans le tableau de bord, Skip ignore une version jusqu’à la suivante, et Approve déclenche les commandes et webhooks associés au service (aucun n’est configuré dans cette mise en place).
Ajouter une instance GeoNature
Pourquoi ce n’est pas immédiat
GeoNature n’offre pas de route utilisable directement :
GET /api/gn_commons/configest publique et renvoieGEONATURE_VERSION, mais pas les versions des modules.GET /api/gn_commons/modulesdonne les modules, mais exige une session (jeton JWT à durée limitée) : inutilisable par Argus.
La solution retenue s’appuie sur une observation simple : les versions de GeoNature et de tous ses modules sont celles des paquets Python installés dans le venv. Un script les publie dans un seul fichier JSON, qui devient la source unique pour l’application et tous ses modules.
Étape 1 — Le script de génération
Sur chaque serveur GeoNature. Vérifiez d’abord le chemin du venv avec
ls ~/geonature/backend/venv/bin/pip, puis adaptez <user> (le compte d’installation
de GeoNature) :
sudo tee /usr/local/bin/gn-versions.sh > /dev/null <<'EOF'
#!/bin/sh
/home/<user>/geonature/backend/venv/bin/pip list --format=json 2>/dev/null \
| python3 -c 'import json,sys; print(json.dumps({p["name"].lower(): p["version"] for p in json.load(sys.stdin)}))' \
> /var/www/argus/gn-versions.json
EOF
sudo mkdir -p /var/www/argus
sudo chmod +x /usr/local/bin/gn-versions.sh
sudo /usr/local/bin/gn-versions.sh && head -c 300 /var/www/argus/gn-versions.json
Vérification attendue : un JSON du type
{"geonature": "2.17.1", "gn_module_export": "1.8.2", …}.
Puis la mise à jour périodique :
echo '*/30 * * * * root /usr/local/bin/gn-versions.sh' | sudo tee /etc/cron.d/gn-versions
Note
Après une montée de version de GeoNature, relancez le script à la main
(sudo /usr/local/bin/gn-versions.sh) plutôt que d’attendre le cron : Argus reflétera
immédiatement la nouvelle version.
Étape 2 — Publier le fichier avec Apache
Avertissement
Ajoutez ces lignes dans le fichier du site (/etc/apache2/sites-enabled/…, bloc
<VirtualHost *:80>) et pas dans conf-available/geonature.conf : ce dernier est
géré par l’installation de GeoNature et sera écrasé à la prochaine mise à jour.
<VirtualHost *:80>
ServerName geonature-saisie.example.org
# Versions pour Argus
Alias /argus/gn-versions.json /var/www/argus/gn-versions.json
<Location "/argus/gn-versions.json">
Require ip 10.0.0.10
</Location>
IncludeOptional /etc/apache2/conf-available/geonature.conf
IncludeOptional /etc/apache2/conf-available/usershub.conf
ErrorLog "/var/log/apache2/geonature_error.log"
CustomLog "/var/log/apache2/geonature_access.log" combined
</VirtualHost>
Le Require ip limite la lecture du fichier à la seule IP d’Argus : la liste des
versions installées n’est pas exposée publiquement.
Si ce vhost redirige tout vers HTTPS, placez l’exception avant la règle de
redirection, sinon Argus recevra un 301 au lieu du fichier :
RewriteCond %{REQUEST_URI} !^/argus/
sudo apachectl configtest && sudo systemctl reload apache2
Étape 3 — Tester depuis le serveur Argus
Le nom dans --resolve et celui dans l’URL doivent être identiques, sans quoi le test
ne reproduit pas ce que fera Argus :
curl -sS -i --resolve geonature-saisie.example.org:80:10.0.0.22 \
http://geonature-saisie.example.org/argus/gn-versions.json | head -20
Vérification attendue : un code 200 et le JSON des versions. Un 403, 404 ou
301 renvoie à la section Dépannage — corrigez avant d’aller plus loin, Argus ne fera
pas mieux que curl.
Note
Si le serveur GeoNature est dans un autre sous-réseau, l’IP d’Argus vue par Apache peut
être celle d’une passerelle ou d’un NAT. En cas de 403, relevez l’adresse réellement
refusée dans le journal d’erreurs d’Apache et ajoutez-la au Require ip.
Étape 4 — Déclarer l’hôte dans Docker
Ajoutez la ligne correspondante dans extra_hosts du docker-compose.yml, puis :
docker compose up -d
docker compose exec argus cat /etc/hosts
Vérification attendue : la correspondance nom → IP apparaît dans le /etc/hosts du
conteneur.
Étape 5 — Déclarer les services dans config.yml
Un bloc par composant suivi. Voici le modèle pour une instance et ses modules — répétez en adaptant le nom, le domaine, le tag et la liste des modules :
GeoNature Saisie:
latest_version:
type: github
url: PnX-SI/GeoNature
url_commands:
- type: regex
regex: ^v?([0-9.]+)$
deployed_version:
type: url
url: http://geonature-saisie.example.org/argus/gn-versions.json
json: geonature
dashboard:
tags: [geonature-saisie]
GeoNature Saisie - Monitoring:
latest_version:
type: github
url: PnX-SI/gn_module_monitoring
url_commands:
- type: regex
regex: ^v?([0-9.]+)$
deployed_version:
type: url
url: http://geonature-saisie.example.org/argus/gn-versions.json
json: gn_module_monitoring
dashboard:
tags: [geonature-saisie]
GeoNature Saisie - Export:
latest_version:
type: github
url: PnX-SI/gn_module_export
url_commands:
- type: regex
regex: ^v?([0-9.]+)$
deployed_version:
type: url
url: http://geonature-saisie.example.org/argus/gn-versions.json
json: gn_module_export
dashboard:
tags: [geonature-saisie]
L’expression ^v?([0-9.]+)$ isole le numéro de version des tags GitHub, qu’ils soient
écrits v2.17.1 ou 2.17.1, et écarte les pré-versions.
Le tags permet de filtrer le tableau de bord par instance — pratique dès qu’on suit
plusieurs GeoNature.
Correspondances dépôts et clés JSON
Composant |
Dépôt GitHub ( |
Clé JSON ( |
|---|---|---|
GeoNature |
|
|
Monitoring |
|
|
Export |
|
|
Dashboard |
|
|
TaxHub |
|
|
Note
Occtax, Occhab et Validation sont livrés avec GeoNature (clés occtax,
gn_module_occhab, gn_module_validation) : inutile de les suivre séparément, leur
version est celle de GeoNature.
pypnusershub est la bibliothèque d’authentification, à ne pas confondre avec
l’application UsersHub, qui se suit séparément.
Étape 6 — Vérifier
Validez le YAML, redémarrez, puis dans l’interface cliquez sur Refresh pour chaque service :
python3 -c 'import yaml; yaml.safe_load(open("config.yml")); print("YAML OK")'
docker compose restart argus
Vérification attendue : chaque service affiche une version déployée et une dernière version publiée.
Argus révèle les mises à jour incomplètes
Sur une instance de test, après une montée en 2.17.5, le JSON ne contenait plus aucun
module : la migration avait recréé le venv sans réinstaller les modules. L’erreur
failed to find value for "…" a mis le problème en évidence — il serait autrement
passé inaperçu jusqu’au premier utilisateur bloqué.
Réinstallation depuis le venv activé (vérifiez les codes de module dans la documentation de chacun) :
geonature install-gn-module ~/gn_module_monitoring MONITORINGS
geonature install-gn-module ~/gn_module_export EXPORTS
geonature install-gn-module ~/gn_module_dashboard DASHBOARD
sudo systemctl restart geonature geonature-worker
sudo /usr/local/bin/gn-versions.sh
Option : suivre PostgreSQL et PostGIS
Si PostgreSQL tourne sur le même serveur, ajoutez au script de génération (en adaptant le nom de la base) :
PG=$(sudo -u postgres psql -Atc "SHOW server_version" | cut -d' ' -f1)
PGIS=$(sudo -u postgres psql -d geonature2db -Atc "SELECT postgis_lib_version()" 2>/dev/null)
echo "{\"postgresql\":\"$PG\",\"postgis\":\"$PGIS\"}" > /var/www/argus/pg-version.json
Exposez pg-version.json avec un Alias identique à celui de gn-versions.json.
Pour PostgreSQL, ce qui compte n’est pas la dernière version majeure existante mais la dernière mineure de la branche majeure installée — c’est elle qui porte les correctifs de sécurité. L’API endoflife.date le donne directement :
PostgreSQL - GeoNature Saisie:
latest_version:
type: url
url: https://endoflife.date/api/v1/products/postgresql/releases/17
url_commands:
- type: regex
regex: '"latest":\{"name":"([0-9.]+)"'
deployed_version:
type: url
url: http://geonature-saisie.example.org/argus/pg-version.json
json: postgresql
dashboard:
tags: [geonature-saisie, postgresql]
Ajouter d’autres applications
Application exposant sa version derrière un jeton d’en-tête
Exemple avec Zammad. Le jeton est créé dans Profil → Jeton d’accès, sur un compte technique dédié :
Zammad:
latest_version:
type: github
url: zammad/zammad
url_commands:
- type: regex
regex: ^v?([0-9.]+)$
deployed_version:
type: url
url: http://10.0.0.12/api/v1/version
headers:
- key: Authorization
value: 'Token token=${ZAMMAD_TOKEN}'
json: version
regex: ^([0-9.]+)
dashboard:
tags: [support]
Le regex final nettoie la valeur lue : Zammad renvoie une chaîne du type
7.0.1-1778078595.d438e0a.debian13, dont on ne garde que 7.0.1.
Application exposant sa version via un jeton dans l’URL
Exemple avec Matomo. Le jeton se crée sur un compte technique en lecture seule, en décochant « N’autoriser que les requêtes sécurisées (POST) » pour permettre l’appel en GET :
Matomo:
latest_version:
type: github
url: matomo-org/matomo
url_commands:
- type: regex
regex: ^v?([0-9.]+)$
deployed_version:
type: url
url: http://matomo.example.org/index.php?module=API&method=API.getMatomoVersion&format=json&token_auth=${MATOMO_TOKEN}
json: value
dashboard:
tags: [matomo]
Application sans version exposée
Pour Keycloak, LimeSurvey et les applications comparables, la version n’est pas accessible sans authentification. Deux options :
Option |
Mise en œuvre |
|---|---|
Suivi manuel |
|
Script maison |
Publier un |
Le suivi manuel a un défaut à connaître : il n’échoue jamais bruyamment. Si personne ne pense à le mettre à jour, Argus affichera indéfiniment une version obsolète comme si elle était à jour.
Dépannage
Symptôme |
Cause |
Correction |
|---|---|---|
|
Plugin Compose v2 absent |
Installation manuelle du plugin |
|
Ancien |
Utiliser |
|
Utilisateur hors du groupe |
|
|
Port hôte déjà occupé |
Changer le port de gauche dans |
Page vide, conteneur qui redémarre en boucle |
Erreur dans |
Valider le YAML, lire |
Modification manuelle ignorée |
Pas de redémarrage |
|
Variable d’environnement vide dans le conteneur |
|
|
|
Mauvais vhost sélectionné |
Vrai nom de domaine + |
|
TLS non disponible en interne |
Interroger en |
|
Vhost port 80 qui redirige, ou résolution via le proxy public |
Exception |
|
IP d’Argus non autorisée, ou requête passée par le proxy public |
Vérifier |
|
Clé absente du JSON (module non installé) |
Vérifier le module ; après migration, le réinstaller |
|
60 requêtes/h sans jeton |
|
|
|
Mettre le jeton dans l’URL ou en en-tête |
« WebSocket connecting » en boucle |
Le proxy public ne relaie pas |
Règles |
Maintenance
Ajouter un module à une instance existante
Relancer
gn-versions.shsur le serveur concerné et vérifier que la clé du module apparaît dans le JSON.Ajouter le bloc de service dans
config.yml.Valider le YAML,
docker compose restart argus, puis Refresh dans l’interface.
Mettre à jour Argus
cd ~/argus
docker compose pull
docker compose up -d
docker compose logs -f argus
Sauvegardez config.yml et data/argus.db avant une montée de version majeure.
Renouveler les jetons
Les jetons expirent. Notez leurs dates d’expiration quelque part de fiable : un jeton
GitHub expiré se manifeste par le retour du rate limit reached, ce qui n’est pas
évident à relier à sa cause.
Sécurité
À respecter
Aucun jeton dans un dépôt. Variables d’environnement dans
docker-compose.yml, et ce fichier exclu de tout versionnement.Comptes techniques dédiés, avec les droits minimaux — lecture seule partout où c’est possible.
Jeton GitHub sans permission : la lecture des dépôts publics n’en demande aucune.
Fichiers de version restreints à la seule IP d’Argus via
Require ip.Argus lui-même : authentification activée, port lié à
127.0.0.1, accès public uniquement via le proxy HTTPS.
Avertissement
Un jeton qui a circulé en clair — dans un ticket, un canal de discussion, un copier-coller — doit être considéré comme compromis : révoquez-le et créez-en un nouveau, même si l’incident semble sans conséquence.
Pour aller plus loin
endoflife.date — fins de support et dernières versions mineures, utile pour tout ce qui n’est pas sur GitHub