# Déploiement en production Cette page détaille le déploiement d'un ensemble master + noeuds en production, au-delà du premier noeud isolé couvert par [Installation](installation.md). ## Vue d'ensemble Trois façons d'utiliser ce projet, du plus simple au plus complet — la même installation de base (`sudo make install`) sert de socle aux trois : - **Solo** : `sudo make install`, seul. Ce serveur détecte et bannit localement, sans rien centraliser ailleurs. Suffisant pour protéger un unique VPS. Le service de relais vers un master (`fail2ban-emitter-master-client`) tourne quand même, mais réessaie silencieusement en boucle tant qu'aucun master n'est configuré (aucune gêne, `Restart=on-failure`). - **Solo + master** : `sudo make install` **puis** `sudo make install-master`, sur le **même** serveur. Ce VPS protège ses propres services (comme en mode solo) et sert en plus de master, prêt à accueillir d'autres noeuds plus tard — utile pour démarrer seul aujourd'hui sans fermer la porte à un second serveur demain. - **Master + noeuds** (plusieurs VPS) : un serveur en mode solo+master, et un ou plusieurs autres serveurs, chacun installé en solo puis rattaché à ce master via l'auto-inscription par jeton (ci-dessous). Le master lui-même reste un noeud comme les autres (il détecte et bannit localement en plus de centraliser la décision) — passer de "solo+master tout seul" à "master + noeuds" ne demande donc aucune réinstallation du master, seulement d'y ajouter des noeuds au fur et à mesure. Ordre recommandé pour passer à plusieurs noeuds : 1. Déployer le master (une seule fois — solo+master si ce même serveur doit aussi se protéger lui-même, ce qui est le cas courant). 2. Sur le master, émettre un jeton pour chaque nouveau noeud. 3. Sur chaque nouveau noeud, s'auto-inscrire avec ce jeton. ## Déployer le master ```sh sudo make install-master ``` Nécessite un premier `sudo make install` déjà effectué sur cette même machine. Provisionne : - une **CA privée** dédiée (`scripts/master-ca-init.sh`), distincte du certificat Let's Encrypt qui sert l'identité TLS du broker — elle ne sert qu'à signer les certificats clients des noeuds (authentification mTLS) ; - le listener Mosquitto public (port 8883, TLS, `MQTT_MASTER_DOMAIN` dans `.env`) avec ACL par noeud ; - l'ingestion côté master (`fail2ban-emitter-master-listen`, tourne avec sa propre identité mTLS `master-internal`, générée automatiquement). `MQTT_MASTER_DOMAIN` doit pointer vers un domaine public réel (DNS déjà en place) — le broker obtient son certificat Let's Encrypt à ce nom. ## Auto-inscription d'un noeud (recommandé) ### Le problème Ajouter un noeud à la main demanderait de générer une clé + un certificat sur le master, puis de copier ces fichiers vers le nouveau noeud (`scp`), de les renommer correctement, et d'éditer son `.env` — plusieurs étapes manuelles, cross-machine, et la clé privée transiterait par le réseau (jamais une bonne pratique). ### Le modèle Le master émet un **jeton à usage unique**, à courte durée de vie (1h par défaut). Le nouveau noeud utilise ce jeton pour s'inscrire lui-même : - Le noeud génère sa **propre clé privée localement** et ne l'envoie jamais — seule une CSR (demande de signature) part vers le master. - Le jeton n'est jamais stocké en clair côté master : seul son hash SHA-256 est persisté (même logique qu'un hachage de mot de passe). - Le jeton est **marqué utilisé de façon atomique** dès sa validation, et **rendu automatiquement** si une étape suivante échoue (CSR incohérente, échec de signature) — un accroc transitoire ne force pas à réémettre un jeton entièrement nouveau. - Le CN de la CSR reçue doit correspondre au nom de noeud déclaré au moment de l'émission du jeton — un jeton volé ne permet pas de réclamer un autre nom. - Toute erreur renvoyée au client est **volontairement générique** ("jeton invalide ou expiré"), quelle que soit la cause réelle — le détail (jeton inconnu, expiré, déjà utilisé, CN incohérent, échec de signature) part uniquement dans les journaux du master, pour ne donner aucun indice à un tiers qui sonderait l'endpoint. - Le transport est le tableau de bord HTTPS du master (déjà exposé publiquement, nginx + Let's Encrypt) : aucun nouveau port ni service à ouvrir, et ça résout le problème d'oeuf-et-poule (un noeud ne peut pas utiliser mTLS MQTT pour obtenir... son premier certificat mTLS). ### Étape par étape Sur le **master** : ```sh make join-token NODE=nom-du-noeud ``` Affiche le jeton en clair **une seule fois**, avec la commande complète à lancer sur le nouveau noeud (jeton perdu : simplement en émettre un nouveau, celui-ci reste valide indépendamment jusqu'à expiration). Sur le **nouveau noeud** (déjà passé par `sudo make install` au préalable) : ```sh sudo make join MASTER=https:// NODE=nom-du-noeud TOKEN= sudo make install ``` Le second `make install` applique la configuration reçue (`MQTT_MASTER_NODE_NAME` dans `.env`, certificat en place) et redémarre les services. ### Ce qui se passe en coulisse ``` [nouveau noeud] [master] génère clé + CSR (locale, jamais transmise) POST /api/join/ {node_name, token, csr} ──► vérifie le jeton (hash, expiration, usage unique) vérifie le CN de la CSR == node_name signe la CSR via la CA privée ajoute l'entitlement ACL pour ce noeud redémarre mosquitto {cert, ca_cert} ◄──── écrit cert/ca_cert, met à jour .env ``` ### Dépannage - **"Jeton invalide ou expiré"** — le message est volontairement générique. Causes possibles : jeton déjà utilisé (relancer `make join-token` pour en émettre un nouveau), jeton expiré (durée par défaut 1h, ajustable via `make join-token NODE=... TTL=`), ou faute de frappe dans le nom de noeud (doit être identique entre l'émission du jeton et `make join`). - Le nouveau noeud doit pouvoir joindre le master en HTTPS (443) — un pare-feu qui bloquerait la sortie empêcherait l'auto-inscription. ## Bases de données SQLite est le défaut, sans rien à configurer. Deux alternatives : ```sh sudo make install-mariadb # provisionne un serveur MariaDB local + base/utilisateur dédiés sudo make install # à relancer ensuite pour appliquer les migrations sur la nouvelle base ``` PostgreSQL est supporté côté configuration (`DB_ENGINE=postgresql` dans `.env`, driver `emitter/requirements-postgresql.txt`) mais sans sous-commande de provisionnement automatique pour l'instant — serveur et base à créer manuellement avant de basculer `DB_ENGINE`. Portage d'une base vers une autre (dump indépendant du moteur, via `manage.py dumpdata`/`loaddata`) : ```sh make dump-db # écrit emitter/db-dump-.json (exclu de git, contient des données réelles) # ... bascule DB_ENGINE, migrate ... emitter/.venv/bin/python emitter/manage.py loaddata .json ``` ## Reverse proxy nginx + TLS public ```sh sudo make install-nginx # lit DASHBOARD_DOMAIN dans .env ``` Obtient un certificat Let's Encrypt et déploie un vhost nginx devant le tableau de bord (WebSocket inclus) — nécessaire pour un accès public ; sans ça, le tableau de bord n'est accessible qu'en local/VPN. ## Alternative à systemd : supervisor ```sh sudo make install-supervisor # bascule les 4 services de l'émetteur vers supervisor sudo make uninstall-supervisor # retour arrière (réactive systemd) ``` ## Mise à jour ```sh git pull sudo make install ``` Réapplique dépendances/migrations et redémarre les services concernés, sur n'importe quel noeud (y compris le master).