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"
|
DB_PATH="/var/www/nexus-pbx/storage/database.sqlite"
|
||||||
if [ -f "$DB_PATH" ]; then
|
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
|
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
|
fi
|
||||||
|
|
||||||
# 2. Désenregistrement des Trunks PJSIP pour éviter les conflits d'appels entrants avec le Master
|
# 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
|
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
|
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
|
fi
|
||||||
|
|
||||||
# 2. Vérification et réparation d'intégrité de la base SQLite AstDB (contacts téléphones)
|
# 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.");
|
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();
|
$dbPath = Database::getDatabasePath();
|
||||||
if (file_exists($dbPath)) {
|
if (file_exists($dbPath)) {
|
||||||
@copy($dbPath, $dbPath . '.pre_cluster_sync.bak');
|
@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);
|
$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) {}
|
} catch (\Throwable $e) {}
|
||||||
|
|
||||||
// Libérer les verrous PDO SQLite locaux et purger les résidus WAL/SHM pour éviter tout malformed
|
// Restauration officielle en ligne via l'API SQLite .restore
|
||||||
Database::resetConnection();
|
// Ne détruit aucun descripteur de fichier, ne touche pas aux journaux WAL/SHM,
|
||||||
@unlink($dbPath . '-wal');
|
// et n'invalide aucune lecture concurrente dans PHP-FPM.
|
||||||
@unlink($dbPath . '-shm');
|
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
|
@unlink($extractedDb);
|
||||||
rename($extractedDb, $dbPath);
|
|
||||||
@chmod($dbPath, 0660);
|
// Contrôle d'intégrité immédiat après synchronisation
|
||||||
@chown($dbPath, 'www-data');
|
$postCheck = trim(shell_exec("sqlite3 " . escapeshellarg($dbPath) . " 'PRAGMA integrity_check;' 2>/dev/null") ?? '');
|
||||||
@chgrp($dbPath, 'asterisk');
|
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
|
// Extraction des sons personnalisés si présents
|
||||||
if (is_dir('/var/lib/asterisk/sounds')) {
|
if (is_dir('/var/lib/asterisk/sounds')) {
|
||||||
|
|||||||
Reference in new issue
Block a user