mirror of https://github.com/itflow-org/itflow
Update the backup code to be a full backup zip file download of uploads and db dump along with version meta data file. Also allow to restore a single file in setup currently hidden
This commit is contained in:
parent
069772f27d
commit
2ffb2be083
|
|
@ -8,8 +8,7 @@ require_once "includes/inc_all_admin.php";
|
|||
</div>
|
||||
<div class="card-body" style="text-align: center;">
|
||||
<div class="alert alert-secondary">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 <a href="https://docs.itflow.org/backups" target="_blank">docs here</a>.</div>
|
||||
<a class="btn btn-primary btn-lg p-3" href="post.php?download_database&csrf_token=<?php echo $_SESSION['csrf_token'] ?>"><i class="fas fa-fw fa-4x fa-download"></i><br><br>Download database</a>
|
||||
<a class="btn btn-primary btn-lg p-3" href="post.php?download_uploads&csrf_token=<?php echo $_SESSION['csrf_token'] ?>"><i class="fas fa-fw fa-4x fa-download"></i><br><br>Download Uploads</a>
|
||||
<a class="btn btn-primary btn-lg p-3" href="post.php?download_backup&csrf_token=<?php echo $_SESSION['csrf_token'] ?>"><i class="fas fa-fw fa-4x fa-download"></i><br><br>Download Backup</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -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']);
|
||||
|
|
|
|||
140
setup.php
140
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'])) {
|
|||
</div>
|
||||
<div class="card-body">
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
<h5>Upload SQL File to Import into DB</h5>
|
||||
<input type="file" name="sql_file" accept=".sql" required>
|
||||
|
||||
<label>Restore ITFlow Backup (.zip)</label>
|
||||
<input type="file" name="backup_zip" accept=".zip" required>
|
||||
<hr>
|
||||
|
||||
<h5>Upload Folder Backup (.zip)</h5>
|
||||
<input type="file" name="upload_zip" accept=".zip" required>
|
||||
|
||||
<hr>
|
||||
|
||||
<button type="submit" name="restore" class="btn btn-primary text-bold">
|
||||
Restore then login<i class="fas fa-fw fa-arrow-circle-right ml-2"></i>
|
||||
Restore Backup<i class="fas fa-fw fa-upload ml-2"></i>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue