147 lines
4.3 KiB
PHP
147 lines
4.3 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
namespace App\Services;
|
||
|
|
|
||
|
|
use Illuminate\Http\UploadedFile;
|
||
|
|
use Illuminate\Support\Facades\Artisan;
|
||
|
|
use Illuminate\Support\Facades\File;
|
||
|
|
use Illuminate\Support\Facades\Storage;
|
||
|
|
use Exception;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Service to handle site Backup and Restore operations.
|
||
|
|
*/
|
||
|
|
class BackupService
|
||
|
|
{
|
||
|
|
/**
|
||
|
|
* Get all available backup files from storage.
|
||
|
|
*
|
||
|
|
* @return array List of backups with metadata.
|
||
|
|
*/
|
||
|
|
public function getBackups(): array
|
||
|
|
{
|
||
|
|
$backupDir = storage_path('app/backups');
|
||
|
|
if (!File::exists($backupDir)) {
|
||
|
|
File::makeDirectory($backupDir, 0755, true);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
// Ensure any cached file list is cleared
|
||
|
|
clearstatcache();
|
||
|
|
|
||
|
|
$files = File::files($backupDir);
|
||
|
|
$backups = [];
|
||
|
|
|
||
|
|
foreach ($files as $file) {
|
||
|
|
if ($file->getExtension() === 'gz') {
|
||
|
|
$backups[] = [
|
||
|
|
'name' => $file->getFilename(),
|
||
|
|
'size' => round($file->getSize() / 1024 / 1024, 2) . ' MB',
|
||
|
|
'date' => date('Y-m-d H:i:s', $file->getMTime()),
|
||
|
|
];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Sort by newest first
|
||
|
|
usort($backups, fn($a, $b) => $b['date'] <=> $a['date']);
|
||
|
|
|
||
|
|
return $backups;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Trigger a new site backup process.
|
||
|
|
*
|
||
|
|
* @return bool True if backup was successful.
|
||
|
|
*/
|
||
|
|
public function create(): bool
|
||
|
|
{
|
||
|
|
return Artisan::call('sw:site:backup') === 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Create a pre-restore snapshot of the current state.
|
||
|
|
*
|
||
|
|
* @return string The filename of the created snapshot.
|
||
|
|
* @throws Exception If snapshot creation fails.
|
||
|
|
*/
|
||
|
|
public function createPreRestoreSnapshot(): string
|
||
|
|
{
|
||
|
|
$timestamp = date('Y-m-d_H-i-s');
|
||
|
|
$snapshotName = "pre-restore-snapshot_{$timestamp}.gz";
|
||
|
|
|
||
|
|
// We use the same artisan command logic but with a specific name if possible,
|
||
|
|
// or rename the latest backup. Since sw:site:backup doesn't take a name,
|
||
|
|
// we'll run it and then identify/rename if needed, but for KISS,
|
||
|
|
// let's just run it and it will have its own timestamped name.
|
||
|
|
if (Artisan::call('sw:site:backup') !== 0) {
|
||
|
|
throw new Exception('Failed to create pre-restore snapshot.');
|
||
|
|
}
|
||
|
|
|
||
|
|
// The sw:site:backup command creates a file like siteweaver_backup_Y-m-d_H-i-s.gz
|
||
|
|
// Let's find the most recent one.
|
||
|
|
$backups = $this->getBackups();
|
||
|
|
return $backups[0]['name'];
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Restore the site from a specific backup file.
|
||
|
|
*
|
||
|
|
* @param string $filename The name of the backup file to restore.
|
||
|
|
* @param bool $createSnapshot Whether to create a pre-restore snapshot.
|
||
|
|
* @return bool True if restore was successful.
|
||
|
|
* @throws Exception If the backup file is missing or snapshot fails.
|
||
|
|
*/
|
||
|
|
public function restore(string $filename, bool $createSnapshot = true): bool
|
||
|
|
{
|
||
|
|
$backupPath = storage_path('app/backups/' . $filename);
|
||
|
|
if (!File::exists($backupPath)) {
|
||
|
|
throw new Exception('Backup file not found.');
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($createSnapshot) {
|
||
|
|
$this->createPreRestoreSnapshot();
|
||
|
|
}
|
||
|
|
|
||
|
|
return Artisan::call('sw:site:restore', [
|
||
|
|
'filename' => $filename,
|
||
|
|
'--force' => true,
|
||
|
|
]) === 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Upload a backup file to the backup directory.
|
||
|
|
*
|
||
|
|
* @param UploadedFile $file The uploaded .gz backup file.
|
||
|
|
* @return bool True if upload was successful.
|
||
|
|
*/
|
||
|
|
public function upload(UploadedFile $file): bool
|
||
|
|
{
|
||
|
|
$filename = $file->getClientOriginalName();
|
||
|
|
|
||
|
|
// Basic sanitization of filename
|
||
|
|
$filename = preg_replace('/[^a-zA-Z0-9.\-_]/', '_', $filename);
|
||
|
|
|
||
|
|
if (!str_ends_with($filename, '.gz')) {
|
||
|
|
$filename .= '.gz';
|
||
|
|
}
|
||
|
|
|
||
|
|
$backupDir = storage_path('app/backups');
|
||
|
|
if (!File::exists($backupDir)) {
|
||
|
|
File::makeDirectory($backupDir, 0755, true);
|
||
|
|
}
|
||
|
|
|
||
|
|
$file->move($backupDir, $filename);
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Get the current restoration progress.
|
||
|
|
*
|
||
|
|
* @return array|null The progress data or null if not found.
|
||
|
|
*/
|
||
|
|
public function getProgress(): ?array
|
||
|
|
{
|
||
|
|
return \Illuminate\Support\Facades\Cache::get(\App\Console\Commands\SiteRestore::PROGRESS_KEY);
|
||
|
|
}
|
||
|
|
}
|