Progressed restore command a little

This commit is contained in:
Dan Brown 2023-03-07 12:05:04 +00:00
parent 2894ce275c
commit 74b5fadf60
No known key found for this signature in database
GPG key ID: 46D9F943C24A2EF9
3 changed files with 135 additions and 5 deletions

View file

@ -4,6 +4,9 @@ namespace Cli\Commands;
use Cli\Services\AppLocator;
use Cli\Services\ArtisanRunner;
use Cli\Services\BackupZip;
use Cli\Services\EnvironmentLoader;
use Cli\Services\InteractiveConsole;
use Cli\Services\RequirementsValidator;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
@ -26,16 +29,52 @@ class RestoreCommand extends Command
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$interactions = new InteractiveConsole($this->getHelper('question'), $input, $output);
$output->writeln("<info>Warning!</info>");
$output->writeln("<info>- A restore operation will overwrite and remove files & content from an existing instance.</info>");
$output->writeln("<info>- Any existing tables within the configured database will be dropped.</info>");
$output->writeln("<info>- You should only restore into an instance of the same or newer BookStack version.</info>");
$output->writeln("<info>- This command won't handle, restore or address any server configuration.</info>");
$appDir = AppLocator::require($input->getOption('app-directory'));
$output->writeln("<info>Checking system requirements...</info>");
RequirementsValidator::validate();
// TODO - Warn that potentially dangerous,
// warn for same/forward versions only,
// warn this won't handle server-level stuff
$zipPath = realpath($input->getArgument('backup-zip'));
$zip = new BackupZip($zipPath);
$contents = $zip->getContentsOverview();
// TODO - Validate provided backup zip contents
// - Display and prompt to user
$output->writeln("\n<info>Contents found in the backup ZIP:</info>");
$hasContent = false;
foreach ($contents as $info) {
$output->writeln(($info['exists'] ? '✔ ' : '❌ ') . $info['desc']);
if ($info['exists']) {
$hasContent = true;
}
}
if (!$hasContent) {
throw new CommandError("Provided ZIP backup [{$zipPath}] does not have any expected restore-able content.");
}
$output->writeln("<info>The checked elements will be restored into [{$appDir}].</info>");
$output->writeln("<info>Existing content may be overwritten.</info>");
$output->writeln("<info>Do you want to continue?</info>");
if (!$interactions->confirm("Do you want to continue?")) {
$output->writeln("<info>Stopping restore operation.</info>");
return Command::SUCCESS;
}
$output->writeln("<info>Extracting ZIP into temporary directory...</info>");
$extractDir = $appDir . DIRECTORY_SEPARATOR . 'restore-temp-' . time();
if (!mkdir($extractDir)) {
throw new CommandError("Could not create temporary extraction directory at [{$extractDir}].");
}
$zip->extractInto($extractDir);
// TODO - Cleanup temp extract dir
// TODO - Environment handling
// - Restore of old .env
@ -60,4 +99,14 @@ class RestoreCommand extends Command
return Command::SUCCESS;
}
protected function restoreEnv(string $extractDir, string $appDir, InteractiveConsole $interactions)
{
$extractEnv = EnvironmentLoader::load($extractDir);
$appEnv = EnvironmentLoader::load($appDir); // TODO - Probably pass in since we'll need the APP_URL later on.
// TODO - Create mysql runner to take variables to a programrunner instance.
// Then test each, backup existing env, then replace env with old then overwrite
// db options if the new app env options are the valid ones.
}
}

View file

@ -0,0 +1,54 @@
<?php
namespace Cli\Services;
use ZipArchive;
class BackupZip
{
protected ZipArchive $zip;
public function __construct(
protected string $filePath
) {
$this->zip = new ZipArchive();
$status = $this->zip->open($this->filePath);
if (!file_exists($this->filePath) || $status !== true) {
throw new \Exception("Could not open file [{$this->filePath}] as ZIP");
}
}
public function getContentsOverview(): array
{
return [
'env' => [
'desc' => '.env Config File',
'exists' => boolval($this->zip->statName('.env')),
],
'themes' => [
'desc' => 'Themes Folder',
'exists' => boolval($this->zip->statName('themes')),
],
'public-uploads' => [
'desc' => 'Public File Uploads',
'exists' => boolval($this->zip->statName('public/uploads')),
],
'storage-uploads' => [
'desc' => 'Private File Uploads',
'exists' => boolval($this->zip->statName('storage/uploads')),
],
'db' => [
'desc' => 'Database Dump',
'exists' => boolval($this->zip->statName('db.sql')),
],
];
}
public function extractInto(string $directoryPath): void
{
$result = $this->zip->extractTo($directoryPath);
if (!$result) {
throw new \Exception("Failed extraction of ZIP into [{$directoryPath}].");
}
}
}

View file

@ -0,0 +1,27 @@
<?php
namespace Cli\Services;
use Illuminate\Console\QuestionHelper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
class InteractiveConsole
{
public function __construct(
protected QuestionHelper $helper,
protected InputInterface $input,
protected OutputInterface $output,
)
{
}
public function confirm(string $text): bool
{
$question = new ConfirmationQuestion($text, false);
return $this->helper->ask($this->input, $this->output, $question);
}
}