fix(cluster): use native sqlite3 .restore API to eliminate WAL/SHM desynchronization and corruption during live sync
This commit is contained in:
1 parent
2db63d66ce
commit
2db19ed2dd
3 files changed
+30
-10
No files matched your search
@@ -14,6 +14,8 @@ echo "[$TIMESTAMP] [CLUSTER] <<< Transition vers l'état BACKUP / STANDBY (En ve
|
||||
DB_PATH="/var/www/nexus-pbx/storage/database.sqlite"
|
||||
if [ -f "$DB_PATH" ]; then
|
||||
sqlite3 "$DB_PATH" "UPDATE cluster_settings SET last_sync_status='standby_idle', updated_at=CURRENT_TIMESTAMP WHERE id=1;" 2>/dev/null || true
|
||||
chown www-data:asterisk "$DB_PATH" "${DB_PATH}-wal" "${DB_PATH}-shm" 2>/dev/null || true
|
||||
chmod 660 "$DB_PATH" "${DB_PATH}-wal" "${DB_PATH}-shm" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# 2. Désenregistrement des Trunks PJSIP pour éviter les conflits d'appels entrants avec le Master
|
||||
|
||||
@@ -16,6 +16,8 @@ ASTDB_PATH="/var/lib/asterisk/astdb.sqlite3"
|
||||
|
||||
if [ -f "$DB_PATH" ]; then
|
||||
sqlite3 "$DB_PATH" "UPDATE cluster_settings SET last_sync_status='master_active', updated_at=CURRENT_TIMESTAMP WHERE id=1;" 2>/dev/null || true
|
||||
chown www-data:asterisk "$DB_PATH" "${DB_PATH}-wal" "${DB_PATH}-shm" 2>/dev/null || true
|
||||
chmod 660 "$DB_PATH" "${DB_PATH}-wal" "${DB_PATH}-shm" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# 2. Vérification et réparation d'intégrité de la base SQLite AstDB (contacts téléphones)
|
||||
|
||||
+26
-10
@@ -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')) {
|
||||
|
||||
Reference in new issue
Block a user