fix(cluster): use native sqlite3 .restore API to eliminate WAL/SHM desynchronization and corruption during live sync

This commit is contained in:
sorcier committed 2026-11-06 00:10:44 +01:00
1 parent 2db63d66ce
commit 2db19ed2dd
3 files changed
+30 -10

No files matched your search

+26 -10
View File
@@ -653,7 +653,7 @@ class ClusterManager
throw new RuntimeException("La base de données reçue est corrompue ({$check}) : rejet strict de l'application.");
}
// Sauvegarde d'urgence de la base actuelle avant remplacement
// Sauvegarde d'urgence de la base actuelle avant synchronisation
$dbPath = Database::getDatabasePath();
if (file_exists($dbPath)) {
@copy($dbPath, $dbPath . '.pre_cluster_sync.bak');
@@ -666,16 +666,32 @@ class ClusterManager
$localSettingsRow = $this->db->query("SELECT local_net, local_networks_json, admin_https_port FROM settings WHERE id = 1")->fetch(PDO::FETCH_ASSOC);
} catch (\Throwable $e) {}
// Libérer les verrous PDO SQLite locaux et purger les résidus WAL/SHM pour éviter tout malformed
Database::resetConnection();
@unlink($dbPath . '-wal');
@unlink($dbPath . '-shm');
// Restauration officielle en ligne via l'API SQLite .restore
// Ne détruit aucun descripteur de fichier, ne touche pas aux journaux WAL/SHM,
// et n'invalide aucune lecture concurrente dans PHP-FPM.
if (file_exists($dbPath)) {
$restoreCmd = "sqlite3 " . escapeshellarg($dbPath) . " \".restore " . escapeshellarg($extractedDb) . "\" 2>&1";
$restoreOut = shell_exec($restoreCmd);
if (!empty($restoreOut)) {
error_log("[CLUSTER] Avertissement sqlite3 .restore : " . trim($restoreOut));
}
} else {
@copy($extractedDb, $dbPath);
@chmod($dbPath, 0660);
@chown($dbPath, 'www-data');
@chgrp($dbPath, 'asterisk');
}
// Remplacement atomique de la base de données
rename($extractedDb, $dbPath);
@chmod($dbPath, 0660);
@chown($dbPath, 'www-data');
@chgrp($dbPath, 'asterisk');
@unlink($extractedDb);
// Contrôle d'intégrité immédiat après synchronisation
$postCheck = trim(shell_exec("sqlite3 " . escapeshellarg($dbPath) . " 'PRAGMA integrity_check;' 2>/dev/null") ?? '');
if ($postCheck !== 'ok') {
if (file_exists($dbPath . '.pre_cluster_sync.bak')) {
shell_exec("sqlite3 " . escapeshellarg($dbPath) . " \".restore " . escapeshellarg($dbPath . '.pre_cluster_sync.bak') . "\" 2>/dev/null || true");
}
throw new RuntimeException("Échec de vérification d'intégrité après synchronisation ({$postCheck}). Restauration préventive effectuée.");
}
// Extraction des sons personnalisés si présents
if (is_dir('/var/lib/asterisk/sounds')) {