fix(backup): dynamic backup versioning and full Disaster Recovery pre-update backup (v1.6.2)
This commit is contained in:
1 parent
469112d6ee
commit
986e8e2e3c
4 files changed
+62
-22
No files matched your search
@@ -4922,7 +4922,13 @@ function renderBackups() {
|
|||||||
? `<span class="px-2 py-0.5 rounded-full text-[10px] font-bold bg-emerald-500/20 text-emerald-300 border border-emerald-500/30">Local</span>`
|
? `<span class="px-2 py-0.5 rounded-full text-[10px] font-bold bg-emerald-500/20 text-emerald-300 border border-emerald-500/30">Local</span>`
|
||||||
: `<span class="px-2 py-0.5 rounded-full text-[10px] font-bold bg-purple-500/20 text-purple-300 border border-purple-500/30">Distant (SSH)</span>`;
|
: `<span class="px-2 py-0.5 rounded-full text-[10px] font-bold bg-purple-500/20 text-purple-300 border border-purple-500/30">Distant (SSH)</span>`;
|
||||||
|
|
||||||
const versionBadge = `<span class="px-1.5 py-0.2 rounded text-[10px] font-mono font-bold bg-indigo-500/20 text-indigo-300 border border-indigo-500/30">v${escapeHtml(b.version || '1.2.0')}</span>`;
|
const versionBadge = `<span class="px-1.5 py-0.2 rounded text-[10px] font-mono font-bold bg-indigo-500/20 text-indigo-300 border border-indigo-500/30">v${escapeHtml(b.version || '1.6.2')}</span>`;
|
||||||
|
let srcBadge = '';
|
||||||
|
if (b.source === 'pre-update') {
|
||||||
|
srcBadge = `<span class="px-1.5 py-0.2 rounded text-[10px] font-bold bg-amber-500/20 text-amber-300 border border-amber-500/30" title="Sauvegarde automatique pré-mise à jour">Pré-MàJ</span>`;
|
||||||
|
} else if (b.source === 'scheduled') {
|
||||||
|
srcBadge = `<span class="px-1.5 py-0.2 rounded text-[10px] font-bold bg-cyan-500/20 text-cyan-300 border border-cyan-500/30" title="Sauvegarde planifiée automatique">Auto</span>`;
|
||||||
|
}
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<tr class="hover:bg-anthracite-750 transition">
|
<tr class="hover:bg-anthracite-750 transition">
|
||||||
@@ -4933,6 +4939,7 @@ function renderBackups() {
|
|||||||
</svg>
|
</svg>
|
||||||
<span class="truncate max-w-xs sm:max-w-md">${escapeHtml(b.filename)}</span>
|
<span class="truncate max-w-xs sm:max-w-md">${escapeHtml(b.filename)}</span>
|
||||||
${versionBadge}
|
${versionBadge}
|
||||||
|
${srcBadge}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3 text-slate-300 whitespace-nowrap">${escapeHtml(b.date_formatted)}</td>
|
<td class="px-4 py-3 text-slate-300 whitespace-nowrap">${escapeHtml(b.date_formatted)}</td>
|
||||||
|
|||||||
+12
-9
@@ -81,15 +81,18 @@ fi
|
|||||||
|
|
||||||
echo -e "\n${C_YELLOW}-> Mise à jour vers la version v${REMOTE_VER}...${C_RESET}"
|
echo -e "\n${C_YELLOW}-> Mise à jour vers la version v${REMOTE_VER}...${C_RESET}"
|
||||||
|
|
||||||
# 1. Sauvegarde préventive automatique
|
# 1. Sauvegarde préventive automatique (Disaster Recovery complète)
|
||||||
echo -e "${C_GRAY}[1/6] Création d'une sauvegarde préventive du système...${C_RESET}"
|
echo -e "${C_GRAY}[1/6] Création d'une sauvegarde préventive du système (Disaster Recovery)...${C_RESET}"
|
||||||
mkdir -p "$APP_DIR/storage/backups"
|
php -r '
|
||||||
BACKUP_DATE=$(date +%Y%m%d_%H%M%S)
|
require_once "/var/www/nexus-pbx/src/Database.php";
|
||||||
BACKUP_FILE="$APP_DIR/storage/backups/pre-update-${BACKUP_DATE}.tar.gz"
|
require_once "/var/www/nexus-pbx/src/BackupManager.php";
|
||||||
tar -czf "$BACKUP_FILE" \
|
try {
|
||||||
-C "$APP_DIR/storage" database.sqlite \
|
$res = App\BackupManager::createBackup("pre-update");
|
||||||
-C /etc/asterisk pjsip.custom.conf extensions.custom.conf 2>/dev/null || true
|
echo " ✓ Sauvegarde Disaster Recovery créée : " . $res["filename"] . " (" . $res["size_formatted"] . ")\n";
|
||||||
echo -e "${C_GREEN} ✓ Sauvegarde de sécurité créée : $(basename "$BACKUP_FILE")${C_RESET}"
|
} catch (\Throwable $e) {
|
||||||
|
echo " ! Avertissement sauvegarde préventive : " . $e->getMessage() . "\n";
|
||||||
|
}
|
||||||
|
' || true
|
||||||
|
|
||||||
# 2. Application des derniers fichiers de code Git
|
# 2. Application des derniers fichiers de code Git
|
||||||
echo -e "${C_GRAY}[2/6] Déploiement des nouveaux fichiers de code...${C_RESET}"
|
echo -e "${C_GRAY}[2/6] Déploiement des nouveaux fichiers de code...${C_RESET}"
|
||||||
|
|||||||
+36
-5
@@ -11,7 +11,22 @@ use InvalidArgumentException;
|
|||||||
class BackupManager
|
class BackupManager
|
||||||
{
|
{
|
||||||
public const BACKUP_DIR = '/var/www/nexus-pbx/storage/system_backups';
|
public const BACKUP_DIR = '/var/www/nexus-pbx/storage/system_backups';
|
||||||
public const VERSION = '1.2.0';
|
public const VERSION = '1.6.2';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retourne dynamiquement la version courante de NexusPBX lue depuis version.json
|
||||||
|
*/
|
||||||
|
public static function getAppVersion(): string
|
||||||
|
{
|
||||||
|
$vFile = dirname(__DIR__) . '/version.json';
|
||||||
|
if (file_exists($vFile)) {
|
||||||
|
$data = json_decode((string)file_get_contents($vFile), true);
|
||||||
|
if (!empty($data['version'])) {
|
||||||
|
return (string)$data['version'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return self::VERSION;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialise le répertoire des sauvegardes.
|
* Initialise le répertoire des sauvegardes.
|
||||||
@@ -182,7 +197,8 @@ class BackupManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
$timestamp = date('Ymd_His');
|
$timestamp = date('Ymd_His');
|
||||||
$filename = "nexus_backup_v" . self::VERSION . "_{$timestamp}.tar.gz";
|
$currentVer = self::getAppVersion();
|
||||||
|
$filename = "nexus_backup_v{$currentVer}_{$timestamp}.tar.gz";
|
||||||
$localArchivePath = self::BACKUP_DIR . '/' . $filename;
|
$localArchivePath = self::BACKUP_DIR . '/' . $filename;
|
||||||
|
|
||||||
// 2. Préparation du dossier de staging
|
// 2. Préparation du dossier de staging
|
||||||
@@ -268,7 +284,7 @@ class BackupManager
|
|||||||
|
|
||||||
// G. Création du Manifeste
|
// G. Création du Manifeste
|
||||||
$manifest = [
|
$manifest = [
|
||||||
'nexus_version' => self::VERSION,
|
'nexus_version' => $currentVer,
|
||||||
'created_at' => date('c'),
|
'created_at' => date('c'),
|
||||||
'timestamp' => time(),
|
'timestamp' => time(),
|
||||||
'filename' => $filename,
|
'filename' => $filename,
|
||||||
@@ -360,6 +376,14 @@ class BackupManager
|
|||||||
// Tenter de lire le manifest depuis l'archive sans l'extraire entièrement
|
// Tenter de lire le manifest depuis l'archive sans l'extraire entièrement
|
||||||
$manifest = self::readManifestFromTar($file);
|
$manifest = self::readManifestFromTar($file);
|
||||||
|
|
||||||
|
$archiveVer = $manifest['nexus_version'] ?? null;
|
||||||
|
if (!$archiveVer && preg_match('/_v([0-9]+\.[0-9]+(?:\.[0-9]+)?)/', $name, $vm)) {
|
||||||
|
$archiveVer = $vm[1];
|
||||||
|
}
|
||||||
|
if (!$archiveVer) {
|
||||||
|
$archiveVer = self::getAppVersion();
|
||||||
|
}
|
||||||
|
|
||||||
$backups[] = [
|
$backups[] = [
|
||||||
'filename' => $name,
|
'filename' => $name,
|
||||||
'storage_type' => 'local',
|
'storage_type' => 'local',
|
||||||
@@ -367,7 +391,7 @@ class BackupManager
|
|||||||
'size_formatted' => self::formatBytes($size),
|
'size_formatted' => self::formatBytes($size),
|
||||||
'timestamp' => $mtime,
|
'timestamp' => $mtime,
|
||||||
'date_formatted' => date('d/m/Y H:i:s', $mtime),
|
'date_formatted' => date('d/m/Y H:i:s', $mtime),
|
||||||
'version' => $manifest['nexus_version'] ?? self::VERSION,
|
'version' => $archiveVer,
|
||||||
'source' => $manifest['source'] ?? 'local',
|
'source' => $manifest['source'] ?? 'local',
|
||||||
'is_local' => true,
|
'is_local' => true,
|
||||||
];
|
];
|
||||||
@@ -807,6 +831,13 @@ class BackupManager
|
|||||||
$name = basename($fullPath);
|
$name = basename($fullPath);
|
||||||
|
|
||||||
if (str_ends_with($name, '.tar.gz')) {
|
if (str_ends_with($name, '.tar.gz')) {
|
||||||
|
$remoteVer = null;
|
||||||
|
if (preg_match('/_v([0-9]+\.[0-9]+(?:\.[0-9]+)?)/', $name, $vm)) {
|
||||||
|
$remoteVer = $vm[1];
|
||||||
|
}
|
||||||
|
if (!$remoteVer) {
|
||||||
|
$remoteVer = self::getAppVersion();
|
||||||
|
}
|
||||||
$results[] = [
|
$results[] = [
|
||||||
'filename' => $name,
|
'filename' => $name,
|
||||||
'storage_type' => 'ssh',
|
'storage_type' => 'ssh',
|
||||||
@@ -814,7 +845,7 @@ class BackupManager
|
|||||||
'size_formatted' => self::formatBytes($size),
|
'size_formatted' => self::formatBytes($size),
|
||||||
'timestamp' => $timestamp > 0 ? $timestamp : time(),
|
'timestamp' => $timestamp > 0 ? $timestamp : time(),
|
||||||
'date_formatted' => date('d/m/Y H:i:s', $timestamp > 0 ? $timestamp : time()),
|
'date_formatted' => date('d/m/Y H:i:s', $timestamp > 0 ? $timestamp : time()),
|
||||||
'version' => self::VERSION,
|
'version' => $remoteVer,
|
||||||
'source' => 'distant (SSH)',
|
'source' => 'distant (SSH)',
|
||||||
'is_local' => false,
|
'is_local' => false,
|
||||||
];
|
];
|
||||||
|
|||||||
+6
-7
@@ -1,16 +1,15 @@
|
|||||||
{
|
{
|
||||||
"version": "1.6.1",
|
"version": "1.6.2",
|
||||||
"codename": "Helios",
|
"codename": "Helios",
|
||||||
"release_date": "2026-11-05",
|
"release_date": "2026-11-05",
|
||||||
"min_php_version": "8.2",
|
"min_php_version": "8.2",
|
||||||
"changelog": [
|
"changelog": [
|
||||||
|
"Sauvegarde & Restauration : versionnage dynamique automatique basé sur la version installée (résolution du tag figé v1.2.0)",
|
||||||
|
"Mises à jour système OTA : déclenchement garanti d'une sauvegarde Disaster Recovery complète pré-mise à jour (visible et restaurable dans l'interface)",
|
||||||
|
"Interface Sauvegardes : nouveau badge identifiant la source (Pré-MàJ, Auto, Manuel)",
|
||||||
"Identité visuelle officielle : intégration du nouveau logo NexusPBX 3D haute définition épuré et du favicon",
|
"Identité visuelle officielle : intégration du nouveau logo NexusPBX 3D haute définition épuré et du favicon",
|
||||||
"Nouveau slogan télécom : « Supervision & Téléphonie IP • Powered by Asterisk »",
|
"Nouveau slogan télécom : « Supervision & Téléphonie IP • Powered by Asterisk »",
|
||||||
"Amélioration des scripts d'installation et de mise à jour : initialisation automatique des permissions et ACLs du module CDR (Master.csv)",
|
"Module Journal d'Appels & CDR avec filtres et export CSV",
|
||||||
"Nouveau module Journal d'Appels & CDR : traçabilité complète des flux entrants, sortants, internes et services avec détection automatique des Trunks SIP et postes associés",
|
"Support universel TLSv1.2/TLSv1.3 et suppression CertificateRequest pour postes Yealink"
|
||||||
"Filtres multicritères CDR en temps réel (période, direction, statut décroché/manqué, recherche textuelle) et export CSV Excel",
|
|
||||||
"Optimisation TLS SIP PJSIP : négociation universelle TLSv1.2/TLSv1.3 (method=sslv23)",
|
|
||||||
"Suppression de CertificateRequest en mode TLS standard pour compatibilité totale avec les téléphones IP Yealink, Fanvil et Grandstream",
|
|
||||||
"Plafond d'expiration d'enregistrement SIP standard (maximum_expiration = 300s, default = 120s) garantissant la reconnexion automatique rapide de tous les postes"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user