From b76490a4dca9dcb7c5175b94505c3fa8050c6ce7 Mon Sep 17 00:00:00 2001 From: sorcier Date: Thu, 5 Nov 2026 19:35:10 +0100 Subject: [PATCH] fix(cluster): support custom peer port, fix netmask & vrid persistence, and fix static call --- public/assets/app.js | 10 +++++ public/index.php | 11 +++-- src/ClusterManager.php | 97 ++++++++++++++++++++++++++++++------------ src/Database.php | 7 +++ 4 files changed, 94 insertions(+), 31 deletions(-) diff --git a/public/assets/app.js b/public/assets/app.js index 277a3e5..7525941 100644 --- a/public/assets/app.js +++ b/public/assets/app.js @@ -6376,12 +6376,21 @@ async function refreshClusterStatus() { const vipInput = document.getElementById('cluster-virtual-ip'); if (vipInput) vipInput.value = st.virtual_ip || ''; + const vmaskInput = document.getElementById('cluster-virtual-netmask'); + if (vmaskInput && st.virtual_netmask) vmaskInput.value = st.virtual_netmask; + const vridInput = document.getElementById('cluster-vrid'); if (vridInput && st.vrid) vridInput.value = st.vrid; const peerIpInput = document.getElementById('cluster-peer-ip'); if (peerIpInput) peerIpInput.value = st.peer_ip || ''; + const peerPortInput = document.getElementById('cluster-peer-port'); + if (peerPortInput && st.peer_port) peerPortInput.value = st.peer_port; + + const autoSyncChk = document.getElementById('cluster-auto-sync'); + if (autoSyncChk && st.auto_sync !== undefined) autoSyncChk.checked = Boolean(st.auto_sync); + const secretInput = document.getElementById('cluster-secret'); if (secretInput && st.cluster_secret) secretInput.value = st.cluster_secret; @@ -6572,6 +6581,7 @@ async function saveClusterSettings(e) { virtual_ip: form.elements['virtual_ip'].value.trim(), virtual_netmask: form.elements['virtual_netmask'].value.trim() || '24', peer_ip: form.elements['peer_ip'].value.trim(), + peer_port: parseInt(form.elements['peer_port'] ? form.elements['peer_port'].value : 8443, 10) || 8443, cluster_secret: form.elements['cluster_secret'].value.trim(), vrid: parseInt(form.elements['vrid'].value, 10) || 51, auto_sync: form.elements['auto_sync'].checked ? 1 : 0 diff --git a/public/index.php b/public/index.php index a18378a..37f905c 100644 --- a/public/index.php +++ b/public/index.php @@ -2917,9 +2917,14 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
- - + +
+ + : + +
diff --git a/src/ClusterManager.php b/src/ClusterManager.php index 070bb73..4de3f50 100644 --- a/src/ClusterManager.php +++ b/src/ClusterManager.php @@ -38,6 +38,7 @@ class ClusterManager 'virtual_netmask' => '24', 'interface' => 'eth0', 'peer_ip' => '', + 'peer_port' => 8443, 'cluster_secret' => bin2hex(random_bytes(16)), 'vrid' => 51, 'priority' => 100, @@ -48,6 +49,9 @@ class ClusterManager 'last_sync_message' => '', ]; } + if (empty($row['peer_port'])) { + $row['peer_port'] = 8443; + } return $row; } @@ -61,13 +65,35 @@ class ClusterManager $virtualIp = trim((string)($data['virtual_ip'] ?? '')); $virtualNetmask = preg_replace('/[^0-9]/', '', (string)($data['virtual_netmask'] ?? '24')) ?: '24'; $interface = preg_replace('/[^a-zA-Z0-9_\-\.]/', '', trim((string)($data['interface'] ?? 'eth0'))) ?: 'eth0'; - $peerIp = trim((string)($data['peer_ip'] ?? '')); + $peerIpRaw = trim((string)($data['peer_ip'] ?? '')); + $peerPort = (int)($data['peer_port'] ?? 0); $clusterSecret = trim((string)($data['cluster_secret'] ?? '')); $vrid = max(1, min(255, (int)($data['vrid'] ?? 51))); $priority = max(1, min(254, (int)($data['priority'] ?? ($role === 'master' ? 100 : 90)))); $syncInterval = max(5, min(3600, (int)($data['sync_interval'] ?? 15))); $autoSync = !empty($data['auto_sync']) ? 1 : 0; + // Détection et parsing si le port est saisi dans l'IP (ex: 10.10.11.197:2356) + if (str_contains($peerIpRaw, ':')) { + $parts = explode(':', $peerIpRaw, 2); + $peerIp = trim($parts[0]); + $parsedPort = (int)trim($parts[1]); + if ($parsedPort > 0 && $parsedPort <= 65535) { + $peerPort = $parsedPort; + } + } else { + $peerIp = $peerIpRaw; + } + + if ($peerPort <= 0 || $peerPort > 65535) { + try { + $st = $this->db->query("SELECT admin_https_port FROM settings WHERE id = 1")->fetch(PDO::FETCH_ASSOC); + $peerPort = (int)($st['admin_https_port'] ?? 8443); + } catch (\Throwable $e) { + $peerPort = 8443; + } + } + if ($clusterSecret === '') { $clusterSecret = bin2hex(random_bytes(16)); } @@ -94,18 +120,19 @@ class ClusterManager $stmt = $this->db->prepare(' UPDATE cluster_settings SET - enabled = :enabled, - role = :role, - virtual_ip = :vip, + enabled = :enabled, + role = :role, + virtual_ip = :vip, virtual_netmask = :vmask, - interface = :iface, - peer_ip = :peer, - cluster_secret = :secret, - vrid = :vrid, - priority = :prio, - sync_interval = :sync_int, - auto_sync = :auto_sync, - updated_at = CURRENT_TIMESTAMP + interface = :iface, + peer_ip = :peer, + peer_port = :peer_port, + cluster_secret = :secret, + vrid = :vrid, + priority = :prio, + sync_interval = :sync_int, + auto_sync = :auto_sync, + updated_at = CURRENT_TIMESTAMP WHERE id = 1 '); @@ -116,6 +143,7 @@ class ClusterManager ':vmask' => $virtualNetmask, ':iface' => $interface, ':peer' => $peerIp, + ':peer_port' => $peerPort, ':secret' => $clusterSecret, ':vrid' => $vrid, ':prio' => $priority, @@ -253,6 +281,11 @@ class ClusterManager 'configured_role' => $settings['role'], 'effective_state' => $effectiveState, 'virtual_ip' => $vip, + 'virtual_netmask' => $settings['virtual_netmask'] ?? '24', + 'vrid' => (int)($settings['vrid'] ?? 51), + 'priority' => (int)($settings['priority'] ?? 100), + 'peer_port' => (int)($settings['peer_port'] ?? 8443), + 'auto_sync' => (bool)($settings['auto_sync'] ?? 1), 'virtual_ip_active' => $isVipActive, 'interface' => $settings['interface'], 'peer_ip' => $peerIp, @@ -402,22 +435,24 @@ class ClusterManager { $settings = $this->getSettings(); $peerIp = trim((string)($settings['peer_ip'] ?? '')); + $peerPort = (int)($settings['peer_port'] ?? 8443); + if ($peerPort <= 0 || $peerPort > 65535) { + $peerPort = 8443; + } $secret = trim((string)($settings['cluster_secret'] ?? '')); if ($peerIp === '') { throw new RuntimeException("L'adresse IP du Nœud pair (Peer IP) n'est pas configurée."); } - // Port d'administration HTTPS configuré - $adminPort = 8443; + // Port d'administration HTTPS local configuré pour éventuel fallback + $localAdminPort = 8443; try { $st = $this->db->query("SELECT admin_https_port FROM settings WHERE id = 1")->fetch(PDO::FETCH_ASSOC); if (!empty($st['admin_https_port'])) { - $adminPort = (int)$st['admin_https_port']; + $localAdminPort = (int)$st['admin_https_port']; } - } catch (\Throwable $e) { - $adminPort = 8443; - } + } catch (\Throwable $e) {} // Création de l'archive de réplication $dbPath = Database::getDatabasePath(); @@ -451,16 +486,19 @@ class ClusterManager $jsonPayload = json_encode($payload); $signature = hash_hmac('sha256', $jsonPayload, $secret); - // URLs d'accès candidates vers l'API du second nœud + // URLs d'accès candidates vers l'API du second nœud (priorité au port configuré) $candidateUrls = [ - "https://{$peerIp}:{$adminPort}/api.php?action=cluster_receive_sync", + "https://{$peerIp}:{$peerPort}/api.php?action=cluster_receive_sync", ]; - if ($adminPort !== 443) { - $candidateUrls[] = "https://{$peerIp}/api.php?action=cluster_receive_sync"; + if ($peerPort !== $localAdminPort) { + $candidateUrls[] = "https://{$peerIp}:{$localAdminPort}/api.php?action=cluster_receive_sync"; } - if ($adminPort !== 8443) { + if ($peerPort !== 8443 && $localAdminPort !== 8443) { $candidateUrls[] = "https://{$peerIp}:8443/api.php?action=cluster_receive_sync"; } + if ($peerPort !== 443 && $localAdminPort !== 443) { + $candidateUrls[] = "https://{$peerIp}/api.php?action=cluster_receive_sync"; + } $candidateUrls[] = "http://{$peerIp}/api.php?action=cluster_receive_sync"; $candidateUrls = array_values(array_unique($candidateUrls)); @@ -494,7 +532,9 @@ class ClusterManager } $lastError = "Rejet par le pair ({$targetUrl}) : " . ($resJson['message'] ?? $resJson['error'] ?? 'Erreur inconnue'); } else { - $lastError = "Échec d'envoi vers {$targetUrl} (HTTP {$httpCode})" . ($err ? " : {$err}" : ""); + $resJson = json_decode((string)$response, true); + $detail = !empty($resJson['message']) ? " - " . $resJson['message'] : ($err ? " : {$err}" : ""); + $lastError = "Échec d'envoi vers {$targetUrl} (HTTP {$httpCode}){$detail}"; } } @@ -503,11 +543,11 @@ class ClusterManager throw new RuntimeException($lastError); } - $this->updateSyncStatus('success', "Synchronisation réussie avec le nœud {$peerIp}"); + $this->updateSyncStatus('success', "Synchronisation réussie avec le nœud {$peerIp}:{$peerPort}"); return [ 'success' => true, - 'message' => "Réplication transmise et appliquée sur le nœud pair {$peerIp} avec succès.", + 'message' => "Réplication transmise et appliquée sur le nœud pair {$peerIp}:{$peerPort} avec succès.", ]; } @@ -571,6 +611,7 @@ class ClusterManager role = :role, interface = :iface, peer_ip = :peer_ip, + peer_port = :peer_port, priority = :priority, virtual_ip = :virtual_ip, virtual_netmask = :virtual_netmask, @@ -584,6 +625,7 @@ class ClusterManager ':role' => $localClusterSettings['role'] ?? 'backup', ':iface' => $localClusterSettings['interface'] ?? 'eth0', ':peer_ip' => $localClusterSettings['peer_ip'] ?? '', + ':peer_port' => (int)($localClusterSettings['peer_port'] ?? 8443), ':priority' => $localClusterSettings['priority'] ?? 90, ':virtual_ip' => $localClusterSettings['virtual_ip'] ?? '', ':virtual_netmask' => $localClusterSettings['virtual_netmask'] ?? '24', @@ -607,8 +649,7 @@ class ClusterManager } // Régénérer les configurations PJSIP et Asterisk - $generator = new ConfigGenerator($newDb); - $generator->generateAndApply(); + ConfigGenerator::generateAndApply($newDb); // Si le cluster est actif, actualiser le service Keepalived if (!empty($localClusterSettings['enabled'])) { diff --git a/src/Database.php b/src/Database.php index a9d5b8d..a13fed0 100644 --- a/src/Database.php +++ b/src/Database.php @@ -317,6 +317,7 @@ class Database virtual_netmask VARCHAR(16) NOT NULL DEFAULT '24', interface VARCHAR(32) NOT NULL DEFAULT 'eth0', peer_ip VARCHAR(45) NOT NULL DEFAULT '', + peer_port INTEGER NOT NULL DEFAULT 8443, cluster_secret VARCHAR(64) NOT NULL DEFAULT '', vrid INTEGER NOT NULL DEFAULT 51, priority INTEGER NOT NULL DEFAULT 100, @@ -784,6 +785,7 @@ class Database virtual_netmask VARCHAR(16) NOT NULL DEFAULT '24', interface VARCHAR(32) NOT NULL DEFAULT 'eth0', peer_ip VARCHAR(45) NOT NULL DEFAULT '', + peer_port INTEGER NOT NULL DEFAULT 8443, cluster_secret VARCHAR(64) NOT NULL DEFAULT '', vrid INTEGER NOT NULL DEFAULT 51, priority INTEGER NOT NULL DEFAULT 100, @@ -797,6 +799,11 @@ class Database ); "); + // Migration de la colonne peer_port si elle n'existe pas encore + try { + $db->exec("ALTER TABLE cluster_settings ADD COLUMN peer_port INTEGER NOT NULL DEFAULT 8443"); + } catch (\Throwable $e) {} + $count = (int)$db->query('SELECT COUNT(*) FROM cluster_settings')->fetchColumn(); if ($count === 0) { $defaultSecret = bin2hex(random_bytes(16));