139 lines
4.7 KiB
PHP
139 lines
4.7 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
namespace App\Console\Commands;
|
||
|
|
|
||
|
|
use Illuminate\Console\Command;
|
||
|
|
use Illuminate\Support\Facades\File;
|
||
|
|
use Illuminate\Support\Facades\Cache;
|
||
|
|
|
||
|
|
class SiteRestore extends Command
|
||
|
|
{
|
||
|
|
/**
|
||
|
|
* Cache key for restoration progress.
|
||
|
|
*/
|
||
|
|
const PROGRESS_KEY = 'site_restore_progress';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Update progress in Cache.
|
||
|
|
*
|
||
|
|
* @param string $message Progress message.
|
||
|
|
* @param int $percentage Progress percentage.
|
||
|
|
* @return void
|
||
|
|
*/
|
||
|
|
protected function updateProgress(string $message, int $percentage): void
|
||
|
|
{
|
||
|
|
Cache::put(self::PROGRESS_KEY, [
|
||
|
|
'status' => $message,
|
||
|
|
'percent' => $percentage,
|
||
|
|
'timestamp' => now()->timestamp,
|
||
|
|
], 300); // 5-minute TTL
|
||
|
|
|
||
|
|
$this->info("[$percentage%] $message");
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* The name and signature of the console command.
|
||
|
|
*
|
||
|
|
* @var string
|
||
|
|
*/
|
||
|
|
protected $signature = 'sw:site:restore {filename} {--force}';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* The console command description.
|
||
|
|
*
|
||
|
|
* @var string
|
||
|
|
*/
|
||
|
|
protected $description = 'Restore the site from a backup archive.';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Execute the console command.
|
||
|
|
*/
|
||
|
|
public function handle()
|
||
|
|
{
|
||
|
|
$filename = $this->argument('filename');
|
||
|
|
$backupPath = storage_path("app/backups/{$filename}");
|
||
|
|
|
||
|
|
if (!File::exists($backupPath)) {
|
||
|
|
$this->error("Backup file not found: {$filename}");
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($this->option('force') || $this->confirm("Are you sure you want to restore from {$filename}? This will overwrite your current database and files.")) {
|
||
|
|
$this->updateProgress("Starting restoration...", 5);
|
||
|
|
|
||
|
|
$tempPath = storage_path('app/temp_restore');
|
||
|
|
if (File::exists($tempPath)) {
|
||
|
|
File::deleteDirectory($tempPath);
|
||
|
|
}
|
||
|
|
mkdir($tempPath, 0755, true);
|
||
|
|
|
||
|
|
// Extract archive
|
||
|
|
$this->updateProgress("Extracting backup archive...", 20);
|
||
|
|
$command = "tar -xzf " . escapeshellarg($backupPath) . " -C " . escapeshellarg($tempPath);
|
||
|
|
exec($command, $output, $returnVar);
|
||
|
|
|
||
|
|
if ($returnVar !== 0) {
|
||
|
|
$this->updateProgress("Failed to extract backup.", 0);
|
||
|
|
$this->error("Failed to extract backup.");
|
||
|
|
File::deleteDirectory($tempPath);
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Database restoration
|
||
|
|
$this->updateProgress("Restoring database...", 40);
|
||
|
|
$dbPath = config('database.connections.sqlite.database');
|
||
|
|
$dbFilename = basename($dbPath);
|
||
|
|
|
||
|
|
// Skip restoration if database is in memory (tests)
|
||
|
|
if ($dbPath === ':memory:') {
|
||
|
|
$this->warn('Skipping database restoration because it is in-memory.');
|
||
|
|
// We'll still progress
|
||
|
|
} else {
|
||
|
|
// Look for database file in temp path. It should now be in the root of the temp path.
|
||
|
|
$extractedDbFile = $tempPath . '/' . $dbFilename;
|
||
|
|
|
||
|
|
if (File::exists($extractedDbFile)) {
|
||
|
|
File::copy($extractedDbFile, $dbPath);
|
||
|
|
} else {
|
||
|
|
// Compatibility for old absolute path backups (tar stripping leading slash)
|
||
|
|
$oldPathDb = $tempPath . ltrim($dbPath, '/');
|
||
|
|
if (File::exists($oldPathDb)) {
|
||
|
|
File::copy($oldPathDb, $dbPath);
|
||
|
|
} else {
|
||
|
|
$this->warn("Database file ($dbFilename) not found in backup.");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Media restoration
|
||
|
|
$this->updateProgress("Restoring media files...", 60);
|
||
|
|
$extractedMedia = $tempPath . '/media';
|
||
|
|
if (File::exists($extractedMedia)) {
|
||
|
|
$targetMedia = storage_path('app/public/media');
|
||
|
|
if (File::exists($targetMedia)) {
|
||
|
|
File::deleteDirectory($targetMedia);
|
||
|
|
}
|
||
|
|
File::moveDirectory($extractedMedia, $targetMedia);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Themes restoration
|
||
|
|
$this->updateProgress("Restoring themes...", 80);
|
||
|
|
$extractedThemes = $tempPath . '/themes';
|
||
|
|
if (File::exists($extractedThemes)) {
|
||
|
|
$targetThemes = base_path('themes');
|
||
|
|
if (File::exists($targetThemes)) {
|
||
|
|
File::deleteDirectory($targetThemes);
|
||
|
|
}
|
||
|
|
File::moveDirectory($extractedThemes, $targetThemes);
|
||
|
|
}
|
||
|
|
|
||
|
|
$this->updateProgress("Cleaning up...", 95);
|
||
|
|
File::deleteDirectory($tempPath);
|
||
|
|
$this->updateProgress("Restoration completed successfully.", 100);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
$this->info("Restoration cancelled.");
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
}
|