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'])) {