From e7328f1be5e8b7545a1d96c80f7409f29af4157b Mon Sep 17 00:00:00 2001 From: johnnyq Date: Thu, 19 Jun 2025 00:06:54 -0400 Subject: [PATCH] Use tmp directory to zip and backup itflow instance --- client_overview.php | 2 +- includes/inc_client_top_head.php | 11 +- post/admin/admin_backup.php | 175 +++++++++++++++++++------------ 3 files changed, 112 insertions(+), 76 deletions(-) diff --git a/client_overview.php b/client_overview.php index 11414807..4a1c02ba 100644 --- a/client_overview.php +++ b/client_overview.php @@ -654,7 +654,7 @@ $sql_asset_retired = mysqli_query( ?> - + diff --git a/includes/inc_client_top_head.php b/includes/inc_client_top_head.php index 208ee51e..e4ed3c16 100644 --- a/includes/inc_client_top_head.php +++ b/includes/inc_client_top_head.php @@ -90,22 +90,20 @@ if (!empty($contact_name)) { ?>
- +
- - +
- - +
- - +
diff --git a/post/admin/admin_backup.php b/post/admin/admin_backup.php index 11ec83a6..844a12d6 100644 --- a/post/admin/admin_backup.php +++ b/post/admin/admin_backup.php @@ -8,64 +8,22 @@ defined('FROM_POST_HANDLER') || die("Direct file access is not allowed"); require_once "includes/app_version.php"; -if (isset($_GET['download_backup'])) { - validateCSRFToken($_GET['csrf_token']); - global $mysqli, $database; - - $timestamp = date('YmdHis'); - $baseName = "itflow_$timestamp"; - $sqlFile = "$baseName.sql"; - $uploadsZip = "$baseName_uploads.zip"; - $finalZip = "$baseName.zip"; - $versionFile = "version.txt"; - - // === 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"; - - $tables = []; - $res = $mysqli->query("SHOW TABLES"); - while ($row = $res->fetch_row()) { - $tables[] = $row[0]; - } - - foreach ($tables as $table) { - $createRes = $mysqli->query("SHOW CREATE TABLE `$table`"); - $createRow = $createRes->fetch_assoc(); - $createSQL = array_values($createRow)[1]; - - $sqlContent .= "\n-- ----------------------------\n"; - $sqlContent .= "-- Table structure for `$table`\n"; - $sqlContent .= "-- ----------------------------\n"; - $sqlContent .= "DROP TABLE IF EXISTS `$table`;\n"; - $sqlContent .= $createSQL . ";\n\n"; - - $dataRes = $mysqli->query("SELECT * FROM `$table`"); - if ($dataRes->num_rows > 0) { - $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) { - return is_null($val) ? "NULL" : "'" . $mysqli->real_escape_string($val) . "'"; - }, array_values($row)); - $sqlContent .= "INSERT INTO `$table` (" . implode(", ", $columns) . ") VALUES (" . implode(", ", $values) . ");\n"; - } - $sqlContent .= "\n"; - } - } - - $sqlContent .= "SET foreign_key_checks = 1;\n"; - file_put_contents($sqlFile, $sqlContent); - - // === 2. Create uploads.zip === +if (!function_exists('zipFolder')) { function zipFolder($folderPath, $zipFilePath) { $zip = new ZipArchive(); if ($zip->open($zipFilePath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== TRUE) { - die("Cannot open <$zipFilePath>"); + error_log("Failed to open zip file: $zipFilePath"); + http_response_code(500); + exit("Internal Server Error: Cannot open zip archive."); } $folderPath = realpath($folderPath); + if (!$folderPath) { + error_log("Invalid folder path: $folderPath"); + http_response_code(500); + exit("Internal Server Error: Invalid folder path."); + } + $files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($folderPath), RecursiveIteratorIterator::LEAVES_ONLY @@ -81,17 +39,82 @@ if (isset($_GET['download_backup'])) { $zip->close(); } +} +if (isset($_GET['download_backup'])) { + validateCSRFToken($_GET['csrf_token']); + global $mysqli; + + $session_name = $_SESSION['username'] ?? 'unknown'; + $timestamp = date('YmdHis'); + $baseName = "itflow_$timestamp"; + + // Use temp files + $sqlFile = tempnam(sys_get_temp_dir(), $baseName . "_sql_"); + $uploadsZip = tempnam(sys_get_temp_dir(), $baseName . "_uploads_"); + $versionFile = tempnam(sys_get_temp_dir(), $baseName . "_version_"); + $finalZip = tempnam(sys_get_temp_dir(), $baseName . "_backup_"); + + // === 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"; + + $tables = []; + $res = $mysqli->query("SHOW TABLES"); + if (!$res) { + error_log("MySQL Error: " . $mysqli->error); + exit("Error retrieving tables."); + } + + while ($row = $res->fetch_row()) { + $tables[] = $row[0]; + } + + foreach ($tables as $table) { + $createRes = $mysqli->query("SHOW CREATE TABLE `$table`"); + if (!$createRes) { + error_log("MySQL Error: " . $mysqli->error); + continue; + } + + $createRow = $createRes->fetch_assoc(); + $createSQL = array_values($createRow)[1]; + + $sqlContent .= "\n-- ----------------------------\n"; + $sqlContent .= "-- Table structure for `$table`\n"; + $sqlContent .= "-- ----------------------------\n"; + $sqlContent .= "DROP TABLE IF EXISTS `$table`;\n"; + $sqlContent .= $createSQL . ";\n\n"; + + $dataRes = $mysqli->query("SELECT * FROM `$table`"); + if ($dataRes && $dataRes->num_rows > 0) { + $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) { + return is_null($val) ? "NULL" : "'" . $mysqli->real_escape_string($val) . "'"; + }, array_values($row)); + $sqlContent .= "INSERT INTO `$table` (" . implode(", ", $columns) . ") VALUES (" . implode(", ", $values) . ");\n"; + } + $sqlContent .= "\n"; + } + } + + $sqlContent .= "SET foreign_key_checks = 1;\n"; + file_put_contents($sqlFile, $sqlContent); + + // === 2. Zip uploads folder === zipFolder("uploads", $uploadsZip); - // === 3. Generate version.txt === - $commitHash = trim(shell_exec('git log -1 --format=%H')); - $gitBranch = trim(shell_exec('git rev-parse --abbrev-ref HEAD')); + // === 3. Create version.txt === + $commitHash = trim(shell_exec('git log -1 --format=%H')) ?: 'N/A'; + $gitBranch = trim(shell_exec('git rev-parse --abbrev-ref HEAD')) ?: 'N/A'; $versionContent = "ITFlow Backup Metadata\n"; $versionContent .= "-----------------------------\n"; $versionContent .= "Generated: " . date('Y-m-d H:i:s') . "\n"; - $versionContent .= "Backup File: $baseName.zip\n"; + $versionContent .= "Backup File: " . basename($finalZip) . "\n"; $versionContent .= "Generated By: $session_name\n"; $versionContent .= "Host: " . gethostname() . "\n"; $versionContent .= "Git Branch: $gitBranch\n"; @@ -99,12 +122,17 @@ if (isset($_GET['download_backup'])) { $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); + // Generate SHA256 checksum + $versionContent .= "Checksum (SHA256): \n"; - // === 4. Combine into final .zip file === + file_put_contents($versionFile, $versionContent); // Add metadata first + + // === 4. Create final archive === $final = new ZipArchive(); if ($final->open($finalZip, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== TRUE) { - die("Cannot create final backup zip."); + error_log("Failed to create final zip: $finalZip"); + http_response_code(500); + exit("Internal Server Error: Unable to create backup archive."); } $final->addFile($sqlFile, "db.sql"); @@ -112,18 +140,29 @@ if (isset($_GET['download_backup'])) { $final->addFile($versionFile, "version.txt"); $final->close(); - // Cleanup temp files before download + // Calculate checksum and re-write version.txt with it + $checksum = hash_file('sha256', $finalZip); + file_put_contents($versionFile, $versionContent . "$checksum\n"); + + // === 5. Serve final zip === + header('Content-Type: application/zip'); + header('Content-Disposition: attachment; filename="' . basename($finalZip) . '"'); + header('Content-Length: ' . filesize($finalZip)); + header('Pragma: public'); + header('Expires: 0'); + header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); + header('Content-Transfer-Encoding: binary'); + + flush(); + $fp = fopen($finalZip, 'rb'); + fpassthru($fp); + fclose($fp); + + // === 6. Cleanup temp files === 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 + unlink($finalZip); logAction("System", "Backup Download", "$session_name downloaded full backup."); $_SESSION['alert_message'] = "Full backup downloaded.";