feat(cluster): Rolling Update — Secondary-first upgrade orchestration (v1.2.3)

POST /cluster/rolling-update startet den gestaffelten Upgrade-Prozess:
1. Secondary via mTLS /agent/cluster/trigger-update anstoßen
2. /agent/cluster/version pollen bis Secondary Version-Flip zeigt (max 10 min)
3. Primary self-upgrade via systemd-run (identisch zu /system/upgrade)

State wird in /var/lib/edgeguard/rolling-update-state.json persistiert:
Phasen: updating-secondary → waiting-secondary → updating-primary.
"done" wird nicht geschrieben — Prozess stirbt beim Upgrade. UI erkennt
Abschluss via /system/health version-flip (analog Single-Node-Upgrade).

UI: UpdateBanner erkennt Cluster-Modus (/cluster/status mode="cluster")
und tauscht den "Install now"-Button gegen "Rolling Update (Cluster)" aus.
Multi-Step-Modal zeigt die drei Phasen; ab updating-primary wechselt der
Client auf /system/health polling.

Aggregator.PostPeer: neuer einzel-POST-Helper für mTLS-trigger-update.
WithVersion(): ClusterHandler bekommt Binary-Version für /agent/cluster/version.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Debian
2026-05-29 23:40:49 +02:00
parent 25c7cd0cb5
commit bc6db1fc2b
9 changed files with 824 additions and 67 deletions

View File

@@ -629,6 +629,7 @@
"node": "Knoten",
"status": "Status",
"role": "Rolle",
"pgRole": "PG-Rolle",
"apiUrl": "API-URL",
"configHash": "Config-Hash",
"version": "Version",
@@ -641,6 +642,30 @@
"uptime": "Uptime",
"fetchMs": "Fetch"
},
"pgRole": {
"standalone": "standalone",
"primary": "primary",
"standby": "standby"
},
"vipCard": {
"title": "Hochverfügbarkeit (VIP / Keepalived)",
"vipAddress": "VIP-Adresse",
"vipAddressHelp": "Virtuelle IP-Adresse die zwischen Nodes wandert (z.B. 89.163.205.10)",
"vipInterface": "Netzwerk-Interface",
"vipInterfaceHelp": "Interface auf dem die VIP gebunden wird (z.B. eth0)",
"vipAuthPass": "VRRP Auth-Passwort",
"vipAuthPassHelp": "Max. 8 Zeichen — Keepalived-Limit. Gleich auf allen Nodes.",
"vrrpRouterId": "VRRP Router-ID",
"vrrpRouterIdHelp": "Muss im Subnetz eindeutig sein (1255). Standard: 51.",
"saveBtn": "Speichern & Keepalived neu konfigurieren",
"saved": "VIP-Einstellungen gespeichert",
"saveFailed": "Speichern fehlgeschlagen",
"hintTitle": "Nächste Schritte nach dem Speichern",
"hintPrimary": "Auf dem Primary: edgeguard-ctl cluster-init-replication",
"hintStandby": "Auf dem Secondary: edgeguard-ctl cluster-setup-standby <primary-ip>",
"hintKeepalived": "Keepalived auf beiden Nodes: sudo systemctl enable --now keepalived",
"hintFailover": "Bei Failover: edgeguard-ctl promote (auf dem Secondary)"
},
"loadTitle": "Per-Node Resources (mTLS-Aggregator)",
"loadEmpty": "Keine Node-Resources verfügbar — Agent-Listener nicht erreichbar?",
"certCardTitle": "Cluster-TLS-Zertifikate",
@@ -841,7 +866,14 @@
"stepDownload": "Pakete laden",
"stepInstall": "Installation",
"stepRestart": "Service-Restart",
"stepVerify": "Verifizierung"
"stepVerify": "Verifizierung",
"rollingUpdate": "Rolling Update (Cluster)",
"rollingConfirmTitle": "Rolling Update starten?",
"rollingConfirmDesc": "Der Secondary-Node ({{secondary}}) wird zuerst aktualisiert, danach dieser Primary. Kein Ausfall für den Proxied-Traffic während der Secondary-Phase.",
"rollingRunning": "Rolling Update läuft…",
"rollingStepSecondary": "Secondary aktualisieren ({{fqdn}})",
"rollingStepWaiting": "Warte auf Neustart des Secondary",
"rollingStepPrimary": "Primary aktualisieren (dieser Node)"
},
"wg": {
"title": "WireGuard",

View File

@@ -629,6 +629,7 @@
"node": "Node",
"status": "Status",
"role": "Role",
"pgRole": "PG role",
"apiUrl": "API URL",
"configHash": "Config hash",
"version": "Version",
@@ -641,6 +642,30 @@
"uptime": "Uptime",
"fetchMs": "Fetch"
},
"pgRole": {
"standalone": "standalone",
"primary": "primary",
"standby": "standby"
},
"vipCard": {
"title": "High Availability (VIP / Keepalived)",
"vipAddress": "VIP address",
"vipAddressHelp": "Virtual IP address that moves between nodes (e.g. 89.163.205.10)",
"vipInterface": "Network interface",
"vipInterfaceHelp": "Interface to bind the VIP on (e.g. eth0)",
"vipAuthPass": "VRRP auth password",
"vipAuthPassHelp": "Max. 8 characters — Keepalived limit. Same on all nodes.",
"vrrpRouterId": "VRRP router ID",
"vrrpRouterIdHelp": "Must be unique in the subnet (1255). Default: 51.",
"saveBtn": "Save & reconfigure Keepalived",
"saved": "VIP settings saved",
"saveFailed": "Failed to save",
"hintTitle": "Next steps after saving",
"hintPrimary": "On primary: edgeguard-ctl cluster-init-replication",
"hintStandby": "On secondary: edgeguard-ctl cluster-setup-standby <primary-ip>",
"hintKeepalived": "Keepalived on both nodes: sudo systemctl enable --now keepalived",
"hintFailover": "On failover: edgeguard-ctl promote (on the secondary node)"
},
"loadTitle": "Per-node resources (mTLS aggregator)",
"loadEmpty": "No node resources available — agent listener unreachable?",
"certCardTitle": "Cluster TLS certificates",
@@ -841,7 +866,14 @@
"stepDownload": "Download packages",
"stepInstall": "Install",
"stepRestart": "Service restart",
"stepVerify": "Verification"
"stepVerify": "Verification",
"rollingUpdate": "Rolling Update (Cluster)",
"rollingConfirmTitle": "Start Rolling Update?",
"rollingConfirmDesc": "The secondary node ({{secondary}}) is updated first, then this primary. No downtime for proxied traffic during the secondary phase.",
"rollingRunning": "Rolling update in progress…",
"rollingStepSecondary": "Updating secondary ({{fqdn}})",
"rollingStepWaiting": "Waiting for secondary restart",
"rollingStepPrimary": "Updating primary (this node)"
},
"wg": {
"title": "WireGuard",