]> BookStack Code Mirror - system-cli/blob - scripts/Commands/RestoreCommand.php
Progressed restore command a little
[system-cli] / scripts / Commands / RestoreCommand.php
1 <?php
2
3 namespace Cli\Commands;
4
5 use Cli\Services\AppLocator;
6 use Cli\Services\ArtisanRunner;
7 use Cli\Services\BackupZip;
8 use Cli\Services\EnvironmentLoader;
9 use Cli\Services\InteractiveConsole;
10 use Cli\Services\RequirementsValidator;
11 use Symfony\Component\Console\Command\Command;
12 use Symfony\Component\Console\Input\InputArgument;
13 use Symfony\Component\Console\Input\InputInterface;
14 use Symfony\Component\Console\Input\InputOption;
15 use Symfony\Component\Console\Output\OutputInterface;
16
17 class RestoreCommand extends Command
18 {
19     protected function configure(): void
20     {
21         $this->setName('restore');
22         $this->addArgument('backup-zip', InputArgument::REQUIRED, 'Path to the ZIP file containing your backup.');
23         $this->setDescription('Restore data and files from a backup ZIP file.');
24         $this->addOption('app-directory', null, InputOption::VALUE_OPTIONAL, 'BookStack install directory to restore into', '');
25     }
26
27     /**
28      * @throws CommandError
29      */
30     protected function execute(InputInterface $input, OutputInterface $output): int
31     {
32         $interactions = new InteractiveConsole($this->getHelper('question'), $input, $output);
33
34         $output->writeln("<info>Warning!</info>");
35         $output->writeln("<info>- A restore operation will overwrite and remove files & content from an existing instance.</info>");
36         $output->writeln("<info>- Any existing tables within the configured database will be dropped.</info>");
37         $output->writeln("<info>- You should only restore into an instance of the same or newer BookStack version.</info>");
38         $output->writeln("<info>- This command won't handle, restore or address any server configuration.</info>");
39
40         $appDir = AppLocator::require($input->getOption('app-directory'));
41         $output->writeln("<info>Checking system requirements...</info>");
42         RequirementsValidator::validate();
43
44         $zipPath = realpath($input->getArgument('backup-zip'));
45         $zip = new BackupZip($zipPath);
46         $contents = $zip->getContentsOverview();
47
48         $output->writeln("\n<info>Contents found in the backup ZIP:</info>");
49         $hasContent = false;
50         foreach ($contents as $info) {
51             $output->writeln(($info['exists'] ? '✔ ' : '❌ ') . $info['desc']);
52             if ($info['exists']) {
53                 $hasContent = true;
54             }
55         }
56
57         if (!$hasContent) {
58             throw new CommandError("Provided ZIP backup [{$zipPath}] does not have any expected restore-able content.");
59         }
60
61         $output->writeln("<info>The checked elements will be restored into [{$appDir}].</info>");
62         $output->writeln("<info>Existing content may be overwritten.</info>");
63         $output->writeln("<info>Do you want to continue?</info>");
64
65         if (!$interactions->confirm("Do you want to continue?")) {
66             $output->writeln("<info>Stopping restore operation.</info>");
67             return Command::SUCCESS;
68         }
69
70         $output->writeln("<info>Extracting ZIP into temporary directory...</info>");
71         $extractDir = $appDir . DIRECTORY_SEPARATOR . 'restore-temp-' . time();
72         if (!mkdir($extractDir)) {
73             throw new CommandError("Could not create temporary extraction directory at [{$extractDir}].");
74         }
75         $zip->extractInto($extractDir);
76
77         // TODO - Cleanup temp extract dir
78
79         // TODO - Environment handling
80         //  - Restore of old .env
81         //  - Prompt for correct DB details (Test before serving?)
82         //  - Prompt for correct URL (Allow entry of new?)
83
84         // TODO - Restore folders from backup
85
86         // TODO - Restore database from backup
87
88         $output->writeln("<info>Running database migrations...</info>");
89         $artisan = (new ArtisanRunner($appDir));
90         $artisan->run(['migrate', '--force']);
91
92         // TODO - Update system URL (via BookStack artisan command) if
93         //   there's been a change from old backup env
94
95         $output->writeln("<info>Clearing app caches...</info>");
96         $artisan->run(['cache:clear']);
97         $artisan->run(['config:clear']);
98         $artisan->run(['view:clear']);
99
100         return Command::SUCCESS;
101     }
102
103     protected function restoreEnv(string $extractDir, string $appDir, InteractiveConsole $interactions)
104     {
105         $extractEnv = EnvironmentLoader::load($extractDir);
106         $appEnv = EnvironmentLoader::load($appDir); // TODO - Probably pass in since we'll need the APP_URL later on.
107
108         // TODO - Create mysql runner to take variables to a programrunner instance.
109         //  Then test each, backup existing env, then replace env with old then overwrite
110         //  db options if the new app env options are the valid ones.
111     }
112 }