Réplication de nœuds : réseau et DNS par l'API, multipath, chrony, sysctl par SSH

Deuxième mode de réplication, séparé de la réplication cluster vers cluster
(sélecteur « Clusters (API) | Nœuds (fichiers) » en haut de page) : un profil
réplique la configuration système d'un nœud maître vers des nœuds cibles.

- réseau par /nodes/{node}/network (interfaces.new puis rechargement), adresses
  et passerelles propres à chaque nœud, mapping des noms d'interfaces par cible ;
  DNS par /nodes/{node}/dns ; multipath.conf, wwids (ajout seul), chrony,
  sysctl.d, interfaces.d (hors sdn) par SSH
- découverte des nœuds (/cluster/status), accès SSH par nœud (mot de passe du
  cluster ou propre), clé d'hôte épinglée, contrôle du nom du nœud
- dry-run avec diff des fichiers ; apply (admin) avec contrôle SHA-256,
  sauvegarde et restore.sh sur le nœud, écriture atomique, rechargements,
  restauration si un rechargement échoue, timer systemd de restauration si le
  nœud est injoignable après le rechargement du réseau, confirmation tapée
- vue Nœuds et profils, journal séparé par mode, démo avec nœuds simulés

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
cedricandClaude Opus 5.5 committed 2026-10-23 20:29:55 +02:00
1 parent dc1a7ff40e
commit 1e667ce8b9
24 files changed
+4973 -65

No files matched your search

+50
View File
@@ -756,3 +756,53 @@ body.login-page { min-height: 100vh; padding: 16px; display: grid; place-items:
.plan-kinds .seg button:disabled { cursor: default; }
.plan-kinds .seg button:disabled:not([aria-checked="true"]) { opacity: .55; }
@media (max-width: 560px) { .plan-kinds .pk-row { grid-template-columns: minmax(0, 1fr) auto; } .plan-kinds .pk-row .kind, .plan-kinds .pk-n { display: none; } }
/* ---------- node replication ---------- */
/* the two replications, side by side in the top bar */
.mode-sw { flex: none; }
.mode-sw button { display: inline-flex; align-items: center; gap: 6px; height: 28px; padding: 0 12px; font-weight: 600; }
.mode-sw svg { width: 15px; height: 15px; }
.mode-sw button[aria-checked="true"] { background: var(--accent); color: #fff; }
body.mode-node .brand svg rect { fill: var(--k-vnet); }
.kind.network { --k: var(--k-zone); }
.kind.dns { --k: var(--k-vnet); }
.kind.multipath, .kind.wwids { --k: var(--k-acc); }
.kind.chrony { --k: var(--k-dc); }
.kind.sysctl { --k: var(--k-subnet); }
.kind.interfaces-d { --k: var(--k-zone); }
.badge.network { --c: var(--k-zone); margin-left: 4px; }
/* unified diff of a file */
.udiff { margin: 4px 0 0; padding: 6px 0; border: 1px solid var(--border); border-radius: 8px; background: var(--panel-2);
font: 12px/1.45 var(--mono); overflow-x: auto; max-height: 420px; overflow-y: auto; }
.udiff div { padding: 0 10px; white-space: pre; min-height: 1.45em; }
.udiff .d-add { color: var(--op-create); background: color-mix(in srgb, var(--op-create) 10%, transparent); }
.udiff .d-del { color: var(--op-delete); background: color-mix(in srgb, var(--op-delete) 10%, transparent); }
.udiff .d-hunk { color: var(--info); padding-top: 4px; }
.plan .udiff { margin-left: 1.2rem; }
.plan .reloads { margin: 6px 0 0; color: var(--muted); font-size: 12px; }
.sec-title { margin: 18px 0 8px; font-size: 11px; font-weight: 650; letter-spacing: .04em; text-transform: uppercase; color: var(--muted); }
.sec-title:first-of-type { margin-top: 6px; }
.citem.node { grid-template-columns: minmax(9rem, max-content) minmax(0, 1fr) max-content; }
.citem.prof { grid-template-columns: minmax(9rem, max-content) minmax(0, 1fr) max-content; }
.citem .items { grid-column: 1 / -1; display: flex; flex-wrap: wrap; gap: 4px; }
.citem .items .kind { width: auto; }
dialog.wide { width: min(980px, calc(100vw - 32px)); }
/* mapping of one target in the profile editor */
.pmap { display: grid; grid-template-columns: 11rem minmax(0, 1fr); gap: 6px 10px; align-items: start; padding: 8px 10px;
border: 1px solid var(--border); border-radius: 8px; }
.pmap > b { grid-column: 1 / -1; font: 600 12.5px var(--mono); }
.pmap label { font-size: 12px; color: var(--muted); padding-top: 6px; }
.pmap textarea { min-height: 3.2em; font-family: var(--mono); font-size: 12px; }
.pprot { display: grid; grid-template-columns: 11rem minmax(0, 1fr); gap: 6px 10px; align-items: start; }
.pprot textarea { min-height: 2.4em; font-family: var(--mono); font-size: 12px; }
@media (max-width: 1100px) {
.mode-sw span { display: none; }
.mode-sw button { padding: 0 9px; }
}
@media (max-width: 760px) {
.pmap, .pprot { grid-template-columns: 1fr; }
}