From c84d8aa4c10c025900da4861197b051324e2434f Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 9 Mar 2023 14:42:28 +0000 Subject: [PATCH] Got restore command to a working state --- scripts/Commands/InitCommand.php | 13 +++++++--- scripts/Commands/RestoreCommand.php | 34 ++++++++++++++++++++----- scripts/Services/BackupZip.php | 17 ++++++++++--- scripts/Services/InteractiveConsole.php | 14 ++++++---- scripts/Services/MySqlRunner.php | 2 +- 5 files changed, 61 insertions(+), 19 deletions(-) diff --git a/scripts/Commands/InitCommand.php b/scripts/Commands/InitCommand.php index 720dcd119..26ab54c9e 100644 --- a/scripts/Commands/InitCommand.php +++ b/scripts/Commands/InitCommand.php @@ -105,10 +105,11 @@ class InitCommand extends Command */ protected function cloneBookStackViaGit(string $installDir): void { - $errors = (new ProgramRunner('git', '/usr/bin/git')) + $git = (new ProgramRunner('git', '/usr/bin/git')) ->withTimeout(240) - ->withIdleTimeout(15) - ->runCapturingStdErr([ + ->withIdleTimeout(15); + + $errors = $git->runCapturingStdErr([ 'clone', '-q', '--branch', 'release', '--single-branch', @@ -119,6 +120,12 @@ class InitCommand extends Command if ($errors) { throw new CommandError("Failed git clone with errors:\n" . $errors); } + + // Disable file permission tracking for git repo + $git->runCapturingStdErr([ + '-C', $installDir, + 'config', 'core.fileMode', 'false' + ]); } /** diff --git a/scripts/Commands/RestoreCommand.php b/scripts/Commands/RestoreCommand.php index 6c2b7deb5..a7c8ac5ff 100644 --- a/scripts/Commands/RestoreCommand.php +++ b/scripts/Commands/RestoreCommand.php @@ -48,7 +48,6 @@ class RestoreCommand extends Command $zipPath = realpath($input->getArgument('backup-zip')); $zip = new BackupZip($zipPath); - // TODO - Fix folders not being picked up here: $contents = $zip->getContentsOverview(); $output->writeln("\nContents found in the backup ZIP:"); @@ -79,9 +78,10 @@ class RestoreCommand extends Command } $zip->extractInto($extractDir); + $envChanges = []; if ($contents['env']['exists']) { $output->writeln("Restoring and merging .env file..."); - $this->restoreEnv($extractDir, $appDir); + $envChanges = $this->restoreEnv($extractDir, $appDir, $output, $interactions); } $folderLocations = ['themes', 'public/uploads', 'storage/uploads']; @@ -92,18 +92,22 @@ class RestoreCommand extends Command } } + $artisan = (new ArtisanRunner($appDir)); if ($contents['db']['exists']) { $output->writeln("Restoring database from SQL dump..."); $this->restoreDatabase($appDir, $extractDir); $output->writeln("Running database migrations..."); - $artisan = (new ArtisanRunner($appDir)); $artisan->run(['migrate', '--force']); } - // TODO - Handle change of URL? - // TODO - Update system URL (via BookStack artisan command) if - // there's been a change from old backup env + if ($envChanges && $envChanges['old_url'] !== $envChanges['new_url']) { + $output->writeln("App URL change made, Updating database with URL change..."); + $artisan->run([ + 'bookstack:update-url', + $envChanges['old_url'], $envChanges['new_url'], + ]); + } $output->writeln("Clearing app caches..."); $artisan->run(['cache:clear']); @@ -118,7 +122,7 @@ class RestoreCommand extends Command return Command::SUCCESS; } - protected function restoreEnv(string $extractDir, string $appDir) + protected function restoreEnv(string $extractDir, string $appDir, OutputInterface $output, InteractiveConsole $interactions): array { $oldEnv = EnvironmentLoader::load($extractDir); $currentEnv = EnvironmentLoader::load($appDir); @@ -149,7 +153,23 @@ class RestoreCommand extends Command copy($appEnvPath, $appEnvPath . '.backup'); } + $oldUrl = $oldEnv['APP_URL'] ?? ''; + $newUrl = $currentEnv['APP_URL'] ?? ''; + $returnData = [ + 'old_url' => $oldUrl, + 'new_url' => $oldUrl, + ]; + + if ($oldUrl !== $newUrl) { + $output->writeln("Found different APP_URL values:"); + $changedUrl = $interactions->choice('Which would you like to use?', array_filter([$oldUrl, $newUrl])); + $envContents = preg_replace('/^APP_URL=.*?$/', 'APP_URL="' . $changedUrl . '"', $envContents); + $returnData['new_url'] = $changedUrl; + } + file_put_contents($appDir . DIRECTORY_SEPARATOR . '.env', $envContents); + + return $returnData; } protected function restoreFolder(string $folderSubPath, string $appDir, string $extractDir): void diff --git a/scripts/Services/BackupZip.php b/scripts/Services/BackupZip.php index 798e24477..cb3ca280a 100644 --- a/scripts/Services/BackupZip.php +++ b/scripts/Services/BackupZip.php @@ -30,15 +30,15 @@ class BackupZip ], 'themes' => [ 'desc' => 'Themes Folder', - 'exists' => $this->zip->locateName('/themes/') !== false, + 'exists' => $this->hasFolder('themes/'), ], 'public/uploads' => [ 'desc' => 'Public File Uploads', - 'exists' => $this->zip->locateName('/public/uploads/') !== false, + 'exists' => $this->hasFolder('public/uploads/'), ], 'storage/uploads' => [ 'desc' => 'Private File Uploads', - 'exists' => $this->zip->locateName('/storage/uploads/') !== false, + 'exists' => $this->hasFolder('storage/uploads/'), ], 'db' => [ 'desc' => 'Database Dump', @@ -54,4 +54,15 @@ class BackupZip throw new \Exception("Failed extraction of ZIP into [{$directoryPath}]."); } } + + protected function hasFolder($folderPath): bool + { + for ($i = 0; $i < $this->zip->numFiles; $i++) { + $filePath = $this->zip->getNameIndex($i); + if (str_starts_with($filePath, $folderPath)) { + return true; + } + } + return false; + } } diff --git a/scripts/Services/InteractiveConsole.php b/scripts/Services/InteractiveConsole.php index 8d8f92626..0fdabe052 100644 --- a/scripts/Services/InteractiveConsole.php +++ b/scripts/Services/InteractiveConsole.php @@ -2,6 +2,7 @@ namespace Cli\Services; +use Symfony\Component\Console\Question\ChoiceQuestion; use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -9,14 +10,11 @@ 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 @@ -24,4 +22,10 @@ class InteractiveConsole $question = new ConfirmationQuestion($text . " (y/n)\n", false); return $this->helper->ask($this->input, $this->output, $question); } -} \ No newline at end of file + + public function choice(string $question, array $answers) + { + $question = new ChoiceQuestion($question, $answers, $answers[0]); + return $this->helper->ask($this->input, $this->output, $question); + } +} diff --git a/scripts/Services/MySqlRunner.php b/scripts/Services/MySqlRunner.php index 1b1091c77..40b1d98fd 100644 --- a/scripts/Services/MySqlRunner.php +++ b/scripts/Services/MySqlRunner.php @@ -39,7 +39,7 @@ class MySqlRunner '-u', $this->user, '-p' . $this->password, $this->database, - '-e' . "'show tables;'" + '-e', "show tables;" ]); return !$output;