# Proposition : réplication de fichiers système nœud à nœud (SSH) Statut : validée par Cédric le 2026-10-23 (liste v1, API dès que possible, identifiants SSH hérités du cluster avec surcharge par nœud), puis implémentée. Écart avec la proposition : `/etc/network/interfaces` passe par l'API PVE (`/nodes/{node}/network`) et non par fichier, et le DNS est ajouté par l'API (`/nodes/{node}/dns`). Le fonctionnement réel est décrit dans le README (« Réplication de nœuds »). Deuxième mode de réplication, séparé de la réplication cluster à cluster (API) : un **nœud maître** et un ou plusieurs **nœuds cibles** (même cluster ou non), fichiers lus et écrits en SSH root. Même moteur : lecture, normalisation, diff, plan, apply, vérification. Constat dans le code actuel : l'accès SSH est défini **par cluster, sur un seul hôte** (`SSHHost`, `internal/config/config.go:33`). Il faut le passer **par nœud** : nœuds découverts par l'API (`GET /cluster/status`, champ `ip`), identifiants root hérités du cluster et surchargeables par nœud, clé d'hôte épinglée par nœud (même TOFU que maintenant). ## 1. Fichiers candidats Légende : 🟢 v1 recommandée, 🟡 v1 possible avec garde-fous, 🔴 exclu. | Fichier | Intérêt | Risques | Propre au nœud (mapper / exclure) | Rechargement | |---|---|---|---|---| | 🟢 `/etc/network/interfaces` | bonds, VLAN, bridges, MTU identiques partout | **perte d'accès au nœud** ; noms de NIC différents d'un nœud à l'autre | `address`, `gateway`, `hwaddress`, IP de management, éventuellement noms de NIC (`nic1` maître ↔ `enp65s0f0` cible) | `ifreload -a` | | 🟢 `/etc/network/interfaces.d/*` | idem | **`interfaces.d/sdn` est généré par le SDN PVE** | exclure `sdn` d'office | `ifreload -a` | | 🟢 `/etc/multipath.conf` | `defaults`, `blacklist`, `devices` identiques sur tout le cluster | erreur de syntaxe = chemins perdus | rien en général | `multipathd reconfigure` | | 🟢 `/etc/multipath/wwids` | mêmes LUN vues par tous les nœuds | ajouter un WWID local (disque système) par erreur | mode additif seulement, jamais de suppression | `multipathd reconfigure` | | 🟡 `/etc/multipath/bindings` | alias `mpathX` identiques sur tous les nœuds (important si LVM partagé référence les noms) | renommer un alias en service | additif seulement, conflit d'alias = bloquant | `multipathd reconfigure` | | 🟢 `/etc/chrony/chrony.conf` (+ `sources.d`) | mêmes sources NTP | faible | rien | `systemctl restart chrony` | | 🟡 `/etc/systemd/timesyncd.conf` | si chrony absent | faible | rien | `systemctl restart systemd-timesyncd` | | 🟢 `/etc/sysctl.d/*.conf` | tuning réseau / mémoire homogène | réglage inadapté au matériel | rien ; ne jamais toucher les fichiers des paquets (`/usr/lib/sysctl.d`) | `sysctl --system` | | 🟡 `/etc/modprobe.d/*.conf` | options ZFS (`zfs_arc_max`), blacklist de modules | effet au reboot seulement ; ARC dépend de la RAM | `zfs_arc_max` à surcharger par nœud | `update-initramfs -u`, **redémarrage requis** (signalé, jamais fait) | | 🟡 `/etc/apt/sources.list.d/*.sources` | mêmes dépôts (enterprise / no-subscription / ceph) | dépôt enterprise sans abonnement sur la cible | rien | aucun (pas d'`apt update` automatique) | | 🟡 `/etc/lvm/lvm.conf` | `global_filter` pour iSCSI / multipath | **filtre faux = volumes invisibles** | rien en général | aucun ; vérification `pvs` avant / après | | 🟡 `/etc/iscsi/iscsid.conf` | timeouts, `node.startup` | **contient les secrets CHAP** | secrets chiffrés au repos, jamais affichés en clair dans le diff | `systemctl restart iscsid` (signalé) | | 🟡 `/etc/vzdump.conf` | défauts de sauvegarde par nœud | faible | `tmpdir` éventuellement | aucun | | 🟡 `/etc/postfix/main.cf` | relais SMTP des notifications | faible | `myhostname` | `postfix reload` | | 🔵 `/etc/resolv.conf` | DNS identiques | écrasé par DHCP / resolvconf | rien | aucun. **Existe aussi en API** (`/nodes/{node}/dns`), à préférer : à vérifier dans l'API viewer | | 🔵 `/etc/hosts` | résolution des nœuds entre eux | ligne du nœud lui-même ; corosync dépend du nom | ligne `127.0.1.1` et la ligne du nœud cible | aucun. **Existe aussi en API** (`/nodes/{node}/hosts`), à vérifier | | 🔴 `/etc/pve/*` (dont `corosync.conf`) | déjà partagé par pmxcfs dans un cluster | casser le cluster | | | | 🔴 `/etc/corosync/*`, `/etc/hostname`, `/etc/machine-id`, clés SSH d'hôte, `/etc/iscsi/initiatorname.iscsi` | | identité unique du nœud | | | | 🔴 `/etc/frr/*` | | géré par le SDN EVPN (hors périmètre) | | | | 🔴 `/etc/kernel/cmdline`, `/etc/default/grub` | | démarrage du nœud | | v2 éventuellement | 🔵 = je propose de passer par l'API PVE plutôt que par le fichier (déjà le modèle du reste de l'outil). **Ma recommandation v1 :** `interfaces` (+ `interfaces.d` hors `sdn`), `multipath.conf`, `wwids`, `chrony.conf`, `sysctl.d`. Le reste en v1.1 une fois le mécanisme éprouvé. ## 2. Modèle **Profil de réplication** : un nœud maître, des nœuds cibles, une liste de fichiers, et par fichier des règles. **Règles par fichier**, deux niveaux : - **Adaptateur structuré** pour `interfaces` (stanzas `iface`) et `multipath.conf` (sections) : politique par champ comme pour l'API (sync / ignorer / surcharger par cible). Exemple : `iface vmbr0 / address` = surcharger, valeur par cible. - **Mode texte** pour les autres : diff ligne à ligne, avec des **lignes protégées** (regex) conservées telles quelles sur la cible, et des **variables** (`{{hostname}}`, `{{ip}}`, valeurs saisies par cible). - **Mapping de noms de NIC** par cible (`eno1` → `enp1s0f0`), appliqué avant le diff. **Déroulé :** 1. **Lecture** : `cat` + `sha256sum` + propriétaire et droits, sur le maître et chaque cible. 2. **Diff** : diff unifié par fichier et par cible, après application des règles. Statut : à jour / écarts / bloquant (ex. NIC du maître absente sur la cible, alias multipath en conflit). 3. **Dry-run** (par défaut) : rien n'est écrit, on affiche le fichier final exact et la commande de rechargement. 4. **Apply**, après **confirmation explicite** (comme le SDN), **un nœud à la fois**, arrêt au premier échec : - le hash de la cible doit être celui lu au plan (sinon : modifié entre-temps, on refuse) ; - **sauvegarde** sur le nœud (`/var/lib/pve-confsync/backup/.`) et en base (SQLite, historique) ; - **validation** avant écriture quand l'outil existe : `multipath -t` sur un fichier temporaire, contrôle de syntaxe ifupdown2 (`ifup --no-act`, option exacte à vérifier) ; - écriture **atomique** (fichier temporaire + `mv`), propriétaire et droits conservés ; - **rechargement** (colonne de droite du tableau), seulement si coché ; - **vérification** : relecture du hash, et pour le réseau, reconnexion SSH. 5. **Filet de sécurité réseau** : avant `ifreload -a`, on programme sur le nœud une restauration automatique (`systemd-run --on-active=120s` qui remet la sauvegarde et relance `ifreload -a`). Si l'outil se reconnecte en SSH dans le délai, il annule la restauration. Sinon le nœud revient seul à l'ancienne config. 6. **Suppression** : jamais par défaut. Un fichier absent du maître n'est pas supprimé sur la cible sauf option explicite (cohérent avec le mode additif du reste). **Journal et audit** : chaque exécution (profil, nœud, fichier, hash avant / après, sauvegarde, sortie du rechargement) dans le journal existant, avec un type « Nœuds ». **Droits** : opérateur = dry-run, admin = apply (le SSH root est le droit le plus fort de l'outil). ## 3. Maquette de l'interface Un sélecteur en tête sépare les deux modes. Chaque mode garde les onglets Écarts / Profils (ou Clusters) / Journal. ``` ┌──────────────────────────────────────────────────────────────────────┐ │ pve-confsync [ Clusters (API) | Nœuds (fichiers) ] 👤 ⏻ │ ├──────────────────────────────────────────────────────────────────────┤ │ [Écarts] [Profils] [Journal] Profil: [Réseau stockage ▾] 2 écarts │ │ [Dry-run global] [Appliquer]│ ├────────────────────────────┬─────────────────────────────────────────┤ │ MAÎTRE pve1 (lab-a) │ pve2 · /etc/network/interfaces ÉCARTS │ │ │ │ │ CIBLES │ auto bond0 │ │ ┌────────────────────────┐ │ iface bond0 inet manual │ │ │ pve2 (lab-a) ÉCARTS │ │ - bond-mode active-backup │ │ │ interfaces ✎ 3 │ │ + bond-mode 802.3ad │ │ │ multipath.conf ✓ │ │ + bond-xmit-hash-policy layer3+4 │ │ │ chrony.conf ✓ │ │ iface vmbr0 inet static │ │ └────────────────────────┘ │ address 10.0.0.12/24 🔒 surchargé │ │ ┌────────────────────────┐ │ bridge-ports bond0 │ │ │ pve3 (lab-a) À JOUR │ │ │ │ └────────────────────────┘ │ Après écriture : ifreload -a ☑ │ │ ┌────────────────────────┐ │ Restauration auto si perte SSH : 120 s │ │ │ pve7 (lab-b) BLOQUANT │ │ │ │ │ interfaces NIC eno2 │ │ [Dry-run] [Appliquer sur pve2] │ │ │ absente, à mapper │ │ │ │ └────────────────────────┘ │ │ └────────────────────────────┴─────────────────────────────────────────┘ ``` Onglet **Profils** : maître, cibles (nœuds découverts par cluster, cases à cocher), fichiers (cases parmi la liste du §1), règles par fichier (champs à surcharger, lignes protégées, mapping de NIC par cible). Fenêtre de **confirmation** d'apply : liste des nœuds dans l'ordre, fichiers, commandes de rechargement, avertissements (« redémarrage requis », « accès réseau »), saisie du nom du nœud pour confirmer quand `interfaces` est concerné. ## Questions ouvertes 1. Liste v1 : celle recommandée ci-dessus, ou plus large ? 2. `resolv.conf` et `hosts` par l'API plutôt que par fichier : d'accord ? 3. Identifiants SSH : hérités du cluster avec surcharge par nœud, d'accord ?