Bonjour à toutes et tous,
Pourquoi ce projet ?
FR-Alert avertit surtout les personnes déjà présentes dans une zone dangereuse. Je voulais aller plus loin : être informé avant d’entrer dans un secteur concerné ou pouvoir suivre une situation à distance.
Les alertes peuvent concerner, par exemple : feu de forêt / inondation / accident industriel / fuite de produits dangereux / évacuation de population / attaque terroriste
L’objectif est donc simple : récupérer les alertes publiées sur le site officiel FR-Alert, les transmettre à Home Assistant et recevoir une notification avec le type de danger, le lieu, l’autorité émettrice et les consignes.
Lors des premiers tests, le système a notamment détecté une alerte réelle concernant un feu de forêt sur la presqu’île du Cap Ferret. Cela a permis de valider toute la chaîne, depuis la publication officielle jusqu’à la notification sur le téléphone.
Problème => pas d'API fournie par FR-Alert. Il a fallu réfléchir et bosser pas mal.
Au final, on obtient ça sur HA (alors que je suis basé à Lyon) :
Mise en place d'une VM Debian
J’ai créé une petite VM indépendante de Home Assistant afin d’y exécuter les services de veille.
Installation des outils nécessaires après l’installation minimale de Debian :
sudo apt update
sudo apt full-upgrade -y
sudo apt install -y \
ca-certificates \
curl \
git \
python3 \
python3-venv \
python3-pip \
nftables \
unattended-upgrades
Les mises à jour automatiques de sécurité sont ensuite activées :
sudo dpkg-reconfigure -plow unattended-upgrades
sudo systemctl enable --now unattended-upgrades
Pare-feu nftables : aucun service ne doit être accessible depuis Internet. Seul SSH est autorisé depuis le réseau local et le réseau WireGuard.
Fichier /etc/nftables.conf :
table inet filter {
chain input {
type filter hook input priority filter;
policy drop;
iifname "lo" accept
ct state invalid drop
ct state established,related accept
ip protocol icmp accept
meta l4proto ipv6-icmp accept
udp sport 67 udp dport 68 accept
iifname "enp0s5" \
ip saddr { 192.168.0.0/24, 192.168.27.0/24 } \
tcp dport 22 accept
}
chain forward {
type filter hook forward priority filter;
policy drop;
}
chain output {
type filter hook output priority filter;
policy accept;
}
}
Activation :
sudo nft -f /etc/nftables.conf
sudo systemctl enable --now nftables
Création d’un compte de service : le collecteur ne tourne pas avec mon compte utilisateur ni avec les droits root.
sudo useradd \
--system \
--home-dir /opt/webwatch \
--shell /usr/sbin/nologin \
webwatch
Création des dossiers :
sudo mkdir -p \
/opt/webwatch/app \
/opt/webwatch/venv \
/var/lib/webwatch/fralert/alerts
sudo chown -R root:root /opt/webwatch/app
sudo chown -R webwatch:webwatch /var/lib/webwatch
Création de l’environnement Python :
sudo python3 -m venv /opt/webwatch/venv
sudo /opt/webwatch/venv/bin/pip install \
--upgrade pip aiohttp
Le code reste protégé et appartient à root. Seul le dossier contenant les données et les archives est accessible en écriture au compte webwatch.
Gestion du certificat du site FR-Alert : le serveur FR-Alert ne fournissait pas toute la chaîne de certification TLS. Plutôt que de désactiver la vérification avec curl -k, j’ai créé un bundle dédié contenant les certificats Debian et le certificat intermédiaire nécessaire.
sudo mkdir -p /etc/webwatch/certs
curl \
http://autorite.certigna.fr/servicesca_rootca.der \
-o /tmp/certigna-services-ca.der
openssl x509 \
-inform DER \
-in /tmp/certigna-services-ca.der \
-out /tmp/certigna-services-ca.pem
Installation du certificat :
sudo install \
-o root \
-g root \
-m 0644 \
/tmp/certigna-services-ca.pem \
/etc/webwatch/certs/certigna-services-ca.pem
Création du bundle utilisé uniquement par le collecteur :
sudo sh -c '
cat \
/etc/ssl/certs/ca-certificates.crt \
/etc/webwatch/certs/certigna-services-ca.pem \
> /etc/webwatch/certs/fr-alert-ca-bundle.pem
'
Cette méthode conserve une validation TLS normale sans modifier globalement les certificats reconnus par Debian.
Préparation du service de veille
Le script Python de surveillance est installé ici :
/opt/webwatch/app/fralert_watch.py
Les données sont stockées ici :
/var/lib/webwatch/fralert/
├── state.json
└── alerts/
Le fichier state.json conserve notamment les identifiants déjà vus afin d’éviter les doublons. Chaque nouvelle alerte est archivée sous forme HTML et JSON.
Le service systemd utilisé pour lancer le collecteur est le suivant :
[Unit]
Description=Surveillance du site officiel FR-Alert
Wants=network-online.target
After=network-online.target nftables.service
[Service]
Type=simple
User=webwatch
Group=webwatch
WorkingDirectory=/opt/webwatch/app
ExecStart=/opt/webwatch/venv/bin/python \
/opt/webwatch/app/fralert_watch.py \
--poll-seconds 60
Restart=on-failure
RestartSec=15
Environment=PYTHONUNBUFFERED=1
NoNewPrivileges=yes
PrivateTmp=yes
PrivateDevices=yes
ProtectSystem=strict
ProtectHome=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
RestrictSUIDSGID=yes
LockPersonality=yes
CapabilityBoundingSet=
AmbientCapabilities=
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
ReadWritePaths=/var/lib/webwatch/fralert
UMask=0077
[Install]
WantedBy=multi-user.target
Le fichier est enregistré sous :
/etc/systemd/system/fralert-watch.service
Puis le service est activé :
sudo systemctl daemon-reload
sudo systemctl enable --now fralert-watch.service
Vérification :
sudo systemctl status fralert-watch.service
sudo journalctl -u fralert-watch.service -n 30
La VM est alors prête à interroger régulièrement le site officiel FR-Alert, détecter les nouvelles publications et les archiver localement.
Réception des alertes dans Home Assistant
Côté HA, j’ai créé une automatisation déclenchée par un webhook local.
Le serveur Debian envoie un JSON à HA dès qu’une nouvelle alerte est détectée :
Site FR-Alert
↓
VM Debian
↓
Webhook local
↓
Automatisation Home Assistant
↓
Notification persistante + téléphone
Cette solution ne nécessite ni jeton API Home Assistant ni ouverture de port sur Internet.
Création du webhook dans HA :
Paramètres
→ Automatisations et scènes
→ Créer une automatisation
→ Ajouter un déclencheur
→ Webhook
Réglages utilisés :
- méthode autorisée : POST ;
- webhook limité au réseau local ;
- identifiant généré par Home Assistant et conservé secret.
L’adresse complète du webhook est ensuite enregistrée sur Debian :
FRALERT_WEBHOOK_URL=http://IP_DE_HOME_ASSISTANT:8123/api/webhook/IDENTIFIANT_SECRET
Automatisation Home Assistant : exemple de YAML
alias: FR-Alert - Notification
description: Réception des alertes envoyées par la VM Debian
triggers:
- trigger: webhook
webhook_id: IDENTIFIANT_SECRET
allowed_methods:
- POST
local_only: true
actions:
- action: persistent_notification.create
data:
title: >-
🚨 {{ trigger.json.get('headline', 'FR-Alert') }}
· {{ trigger.json.get('area', 'Zone inconnue') }}
message: |-
📍 Lieu : {{ trigger.json.get('area', 'Non précisé') }}
🏛️ Autorité : {{ trigger.json.get('authority', 'Non précisée') }}
Type : {{ trigger.json.get('alert_type', 'Non précisé') }}
{{ trigger.json.get('description', 'Aucune description disponible.') }}
Identifiant : {{ trigger.json.get('alert_id', 'inconnu') }}
{{ trigger.json.get('url', '') }}
- action: notify.mobile_app_mon_telephone
data:
title: >-
🚨 {{ trigger.json.get('headline', 'FR-Alert') }}
· {{ trigger.json.get('area', 'Zone inconnue') }}
message: |-
📍 Lieu : {{ trigger.json.get('area', 'Non précisé') }}
🏛️ Autorité : {{ trigger.json.get('authority', 'Non précisée') }}
{{ trigger.json.get('description', 'Aucune description disponible.') }}
mode: queued
max: 10
Le collecteur Debian transmet notamment :
{
"headline": "Incendie - Feu de forêt",
"area": "Presqu'île du Cap Ferret",
"authority": "Préfecture de la Gironde",
"alert_type": "Réel",
"description": "Message et consignes officielles...",
"alert_id": "FR-ALERT.1784874174.90000.0",
"url": "https://fr-alert.gouv.fr/les-alertes/..."
}
HA utilise directement ces champs dans les modèles Jinja avec :
{{ trigger.json.get('area', 'Zone inconnue') }}
Le résultat est une notification contenant le type de danger, le lieu, l’autorité, les consignes et le lien vers la publication officielle :
Voilà ![]()
J'espère que l'idée vous aura plu !
@ +


