diff --git a/admin_backup.php b/admin_backup.php index 3fa4dfa4..63f3566e 100644 --- a/admin_backup.php +++ b/admin_backup.php @@ -8,8 +8,7 @@ require_once "includes/inc_all_admin.php";
If you are unable to back up the entire VM, you'll need to back up the files & database individually. There is no built-in restore. See the docs here.
-

Download database
-

Download Uploads
+

Download Backup
diff --git a/post/admin/admin_backup.php b/post/admin/admin_backup.php index 1ea20c4a..11ec83a6 100644 --- a/post/admin/admin_backup.php +++ b/post/admin/admin_backup.php @@ -6,28 +6,24 @@ defined('FROM_POST_HANDLER') || die("Direct file access is not allowed"); -if (isset($_GET['download_database'])) { - validateCSRFToken($_GET['csrf_token']); +require_once "includes/app_version.php"; +if (isset($_GET['download_backup'])) { + validateCSRFToken($_GET['csrf_token']); global $mysqli, $database; - $backupFileName = date('Y-m-d_H-i-s') . '_backup.sql'; + $timestamp = date('YmdHis'); + $baseName = "itflow_$timestamp"; + $sqlFile = "$baseName.sql"; + $uploadsZip = "$baseName_uploads.zip"; + $finalZip = "$baseName.zip"; + $versionFile = "version.txt"; - header('Content-Type: application/sql'); - header('Content-Disposition: attachment; filename="' . $backupFileName . '"'); - header('Cache-Control: no-store, no-cache, must-revalidate'); - header('Pragma: no-cache'); - header('Expires: 0'); + // === 1. Generate SQL Dump === + $sqlContent = "-- UTF-8 + Foreign Key Safe Dump\n"; + $sqlContent .= "SET NAMES 'utf8mb4';\n"; + $sqlContent .= "SET foreign_key_checks = 0;\n\n"; - if (ob_get_level()) ob_end_clean(); - flush(); - - // Start of dump file — charset declaration - echo "-- UTF-8 + Foreign Key Safe Dump\n"; - echo "SET NAMES 'utf8mb4';\n"; - echo "SET foreign_key_checks = 0;\n\n"; - - // Get all tables $tables = []; $res = $mysqli->query("SHOW TABLES"); while ($row = $res->fetch_row()) { @@ -35,59 +31,47 @@ if (isset($_GET['download_database'])) { } foreach ($tables as $table) { - // Table structure $createRes = $mysqli->query("SHOW CREATE TABLE `$table`"); $createRow = $createRes->fetch_assoc(); $createSQL = array_values($createRow)[1]; - echo "\n-- ----------------------------\n"; - echo "-- Table structure for `$table`\n"; - echo "-- ----------------------------\n"; - echo "DROP TABLE IF EXISTS `$table`;\n"; - echo $createSQL . ";\n\n"; + $sqlContent .= "\n-- ----------------------------\n"; + $sqlContent .= "-- Table structure for `$table`\n"; + $sqlContent .= "-- ----------------------------\n"; + $sqlContent .= "DROP TABLE IF EXISTS `$table`;\n"; + $sqlContent .= $createSQL . ";\n\n"; - // Table data $dataRes = $mysqli->query("SELECT * FROM `$table`"); if ($dataRes->num_rows > 0) { - echo "-- Dumping data for table `$table`\n"; + $sqlContent .= "-- Dumping data for table `$table`\n"; while ($row = $dataRes->fetch_assoc()) { $columns = array_map(fn($col) => '`' . $mysqli->real_escape_string($col) . '`', array_keys($row)); $values = array_map(function ($val) use ($mysqli) { - if (is_null($val)) return "NULL"; - return "'" . $mysqli->real_escape_string($val) . "'"; + return is_null($val) ? "NULL" : "'" . $mysqli->real_escape_string($val) . "'"; }, array_values($row)); - - echo "INSERT INTO `$table` (" . implode(", ", $columns) . ") VALUES (" . implode(", ", $values) . ");\n"; + $sqlContent .= "INSERT INTO `$table` (" . implode(", ", $columns) . ") VALUES (" . implode(", ", $values) . ");\n"; } - echo "\n"; + $sqlContent .= "\n"; } } - //FINAL STEP: Re-enable foreign key checks - echo "\nSET foreign_key_checks = 1;\n"; - - logAction("Database", "Download", "$session_name downloaded the database."); - $_SESSION['alert_message'] = "Database downloaded"; - exit; -} - -if (isset($_GET['download_uploads'])) { - validateCSRFToken($_GET['csrf_token']); + $sqlContent .= "SET foreign_key_checks = 1;\n"; + file_put_contents($sqlFile, $sqlContent); + // === 2. Create uploads.zip === function zipFolder($folderPath, $zipFilePath) { $zip = new ZipArchive(); if ($zip->open($zipFilePath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== TRUE) { - die("Cannot open <$zipFilePath>\n"); + die("Cannot open <$zipFilePath>"); } $folderPath = realpath($folderPath); - $files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($folderPath), RecursiveIteratorIterator::LEAVES_ONLY ); - foreach ($files as $name => $file) { + foreach ($files as $file) { if (!$file->isDir()) { $filePath = $file->getRealPath(); $relativePath = substr($filePath, strlen($folderPath) + 1); @@ -98,27 +82,54 @@ if (isset($_GET['download_uploads'])) { $zip->close(); } - $uploadDir = 'uploads'; - $zipFile = 'uploads.zip'; + zipFolder("uploads", $uploadsZip); - zipFolder($uploadDir, $zipFile); + // === 3. Generate version.txt === + $commitHash = trim(shell_exec('git log -1 --format=%H')); + $gitBranch = trim(shell_exec('git rev-parse --abbrev-ref HEAD')); - // Trigger file download - if (file_exists($zipFile)) { - header('Content-Type: application/zip'); - header('Content-Disposition: attachment; filename="' . basename($zipFile) . '"'); - header('Content-Length: ' . filesize($zipFile)); - flush(); - readfile($zipFile); - unlink($zipFile); // Optional: delete after download - exit; + $versionContent = "ITFlow Backup Metadata\n"; + $versionContent .= "-----------------------------\n"; + $versionContent .= "Generated: " . date('Y-m-d H:i:s') . "\n"; + $versionContent .= "Backup File: $baseName.zip\n"; + $versionContent .= "Generated By: $session_name\n"; + $versionContent .= "Host: " . gethostname() . "\n"; + $versionContent .= "Git Branch: $gitBranch\n"; + $versionContent .= "Git Commit: $commitHash\n"; + $versionContent .= "ITFlow Version: " . (defined('APP_VERSION') ? APP_VERSION : 'Unknown') . "\n"; + $versionContent .= "Database Version: " . (defined('CURRENT_DATABASE_VERSION') ? CURRENT_DATABASE_VERSION : 'Unknown') . "\n"; + + file_put_contents($versionFile, $versionContent); + + // === 4. Combine into final .zip file === + $final = new ZipArchive(); + if ($final->open($finalZip, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== TRUE) { + die("Cannot create final backup zip."); } - logAction("Uploads", "Download", "$session_name downloaded the uploads folder."); + $final->addFile($sqlFile, "db.sql"); + $final->addFile($uploadsZip, "uploads.zip"); + $final->addFile($versionFile, "version.txt"); + $final->close(); + // Cleanup temp files before download + unlink($sqlFile); + unlink($uploadsZip); + unlink($versionFile); + + // === 5. Serve the zip for download === + header('Content-Type: application/zip'); + header('Content-Disposition: attachment; filename="' . $finalZip . '"'); + header('Content-Length: ' . filesize($finalZip)); + flush(); + readfile($finalZip); + unlink($finalZip); // remove final zip after serving + + logAction("System", "Backup Download", "$session_name downloaded full backup."); + $_SESSION['alert_message'] = "Full backup downloaded."; + exit; } - if (isset($_POST['backup_master_key'])) { validateCSRFToken($_POST['csrf_token']); diff --git a/setup.php b/setup.php index dec0b6c5..9d1a4661 100644 --- a/setup.php +++ b/setup.php @@ -110,10 +110,43 @@ if (isset($_POST['add_database'])) { if (isset($_POST['restore'])) { - // === 1. Restore SQL Dump === - if (isset($_FILES["sql_file"])) { + if (!isset($_FILES['backup_zip']) || $_FILES['backup_zip']['error'] !== UPLOAD_ERR_OK) { + die("No backup file uploaded or upload failed."); + } - // Drop all existing tables + $file = $_FILES['backup_zip']; + $fileExt = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)); + if ($fileExt !== "zip") { + die("Only .zip files are allowed."); + } + + // Save uploaded file temporarily + $backupZip = "restore_" . time() . ".zip"; + if (!move_uploaded_file($file["tmp_name"], $backupZip)) { + die("Failed to save uploaded backup file."); + } + + $zip = new ZipArchive; + if ($zip->open($backupZip) !== TRUE) { + unlink($backupZip); + die("Failed to open backup zip file."); + } + + // Extract to a temp directory + $tempDir = "restore_temp_" . time(); + mkdir($tempDir); + + if (!$zip->extractTo($tempDir)) { + $zip->close(); + unlink($backupZip); + die("Failed to extract backup contents."); + } + $zip->close(); + unlink($backupZip); + + // === 1. Restore SQL Dump === + $sqlPath = "$tempDir/db.sql"; + if (file_exists($sqlPath)) { mysqli_query($mysqli, "SET foreign_key_checks = 0"); $tables = mysqli_query($mysqli, "SHOW TABLES"); while ($row = mysqli_fetch_array($tables)) { @@ -121,88 +154,68 @@ if (isset($_POST['restore'])) { } mysqli_query($mysqli, "SET foreign_key_checks = 1"); - $file = $_FILES["sql_file"]; - $filename = $file["name"]; - $tempPath = $file["tmp_name"]; - - $fileExt = pathinfo($filename, PATHINFO_EXTENSION); - if (strtolower($fileExt) !== "sql") { - die("Only .sql files are allowed."); - } - - // Save uploaded file temporarily - $destination = "temp_" . time() . ".sql"; - if (!move_uploaded_file($tempPath, $destination)) { - die("Failed to upload the SQL file."); - } - $command = sprintf( 'mysql -h%s -u%s -p%s %s < %s', escapeshellarg($dbhost), escapeshellarg($dbusername), escapeshellarg($dbpassword), escapeshellarg($database), - escapeshellarg($destination) + escapeshellarg($sqlPath) ); exec($command, $output, $returnCode); - unlink($destination); // cleanup - if ($returnCode !== 0) { die("SQL import failed. Error code: $returnCode"); } + } else { + die("Missing db.sql in the backup archive."); } - // === 2. Restore Upload Folder from ZIP === - if (isset($_FILES["upload_zip"])) { - $uploadDir = __DIR__ . "/uploads/"; + // === 2. Restore Upload Folder === + $uploadDir = __DIR__ . "/uploads/"; + $uploadsZip = "$tempDir/uploads.zip"; - $zipFile = $_FILES["upload_zip"]; - $zipName = basename($zipFile["name"]); - $zipExt = strtolower(pathinfo($zipName, PATHINFO_EXTENSION)); - - if ($zipExt !== "zip") { - die("Only .zip files are allowed for upload restore."); - } - - $tempZip = "upload_restore_" . time() . ".zip"; - if (!move_uploaded_file($zipFile["tmp_name"], $tempZip)) { - die("Failed to upload the zip file."); - } - - $zip = new ZipArchive; - if ($zip->open($tempZip) === TRUE) { - // Clear existing upload folder - foreach (glob($uploadDir . '*') as $file) { - if (is_dir($file)) { - $files = array_diff(scandir($file), array('.', '..')); - foreach ($files as $subfile) { - unlink("$file/$subfile"); - } - rmdir($file); + if (file_exists($uploadsZip)) { + $uploads = new ZipArchive; + if ($uploads->open($uploadsZip) === TRUE) { + // Clean existing uploads + foreach (glob($uploadDir . '*') as $item) { + if (is_dir($item)) { + array_map('unlink', glob("$item/*")); + rmdir($item); } else { - unlink($file); + unlink($item); } } - // Extract new files - $zip->extractTo($uploadDir); - $zip->close(); - unlink($tempZip); // cleanup + $uploads->extractTo($uploadDir); + $uploads->close(); } else { - unlink($tempZip); - die("Failed to open zip file."); + die("Failed to open uploads.zip in backup."); } + } else { + die("Missing uploads.zip in the backup archive."); } - // === 3. Final Setup Stages === + // === 3. Read version.txt (optional log/display) + $versionTxt = "$tempDir/version.txt"; + if (file_exists($versionTxt)) { + $versionInfo = file_get_contents($versionTxt); + // You could log it, show it, or ignore it + // e.g. logAction("Backup Restore", "Version Info", $versionInfo); + } + + // Cleanup temp restore directory + array_map('unlink', glob("$tempDir/*")); + rmdir($tempDir); + + // === 4. Final Setup Stages === $myfile = fopen("config.php", "a"); $txt = "\$config_enable_setup = 0;\n\n"; fwrite($myfile, $txt); fclose($myfile); - $_SESSION['alert_message'] = "Database and uploads restored successfully"; - + $_SESSION['alert_message'] = "Full backup restored successfully."; // header("Location: login.php"); exit; } @@ -1029,18 +1042,11 @@ if (isset($_POST['add_telemetry'])) {
-
Upload SQL File to Import into DB
- - + +
- -
Upload Folder Backup (.zip)
- - -
-