]> BookStack Code Mirror - system-cli/blob - src/Commands/UpdateCommand.php
Added path check/validation for provided restore file path
[system-cli] / src / Commands / UpdateCommand.php
1 <?php declare(strict_types=1);
2
3 namespace Cli\Commands;
4
5 use Cli\Services\AppLocator;
6 use Cli\Services\ArtisanRunner;
7 use Cli\Services\ComposerLocator;
8 use Cli\Services\Paths;
9 use Cli\Services\ProgramRunner;
10 use Cli\Services\RequirementsValidator;
11 use Symfony\Component\Console\Command\Command;
12 use Symfony\Component\Console\Input\InputInterface;
13 use Symfony\Component\Console\Input\InputOption;
14 use Symfony\Component\Console\Output\OutputInterface;
15
16 class UpdateCommand extends Command
17 {
18     protected function configure(): void
19     {
20         $this->setName('update');
21         $this->setDescription('Update an existing BookStack instance.');
22         $this->addOption('app-directory', null, InputOption::VALUE_OPTIONAL, 'BookStack install directory to update', '');
23     }
24
25     /**
26      * @throws CommandError
27      */
28     protected function execute(InputInterface $input, OutputInterface $output): int
29     {
30         $appDir = AppLocator::require($input->getOption('app-directory'));
31         $output->writeln("<info>Checking system requirements...</info>");
32         RequirementsValidator::validate();
33
34         $output->writeln("<info>Checking local Git repository is active...</info>");
35         $this->ensureGitRepoExists($appDir);
36
37         $output->writeln("<info>Checking composer exists...</info>");
38         $composerLocator = new ComposerLocator($appDir);
39         $composer = $composerLocator->getProgram();
40         if (!$composer->isFound()) {
41             $output->writeln("<info>Composer does not exist, downloading a local copy...</info>");
42             $composerLocator->download();
43         }
44
45         $output->writeln("<info>Fetching latest code via Git...</info>");
46         $this->updateCodeUsingGit($appDir);
47
48         $output->writeln("<info>Installing PHP dependencies via composer...</info>");
49         $this->installComposerDependencies($composer, $appDir);
50
51         $output->writeln("<info>Running database migrations...</info>");
52         $artisan = (new ArtisanRunner($appDir));
53         $artisan->run(['migrate', '--force']);
54
55         $output->writeln("<info>Clearing app caches...</info>");
56         $artisan->run(['cache:clear']);
57         $artisan->run(['config:clear']);
58         $artisan->run(['view:clear']);
59
60         $output->writeln("<success>Your BookStack instance at [{$appDir}] has been updated!</success>");
61
62         return Command::SUCCESS;
63     }
64
65     /**
66      * @throws CommandError
67      */
68     protected function updateCodeUsingGit(string $appDir): void
69     {
70         $errors = (new ProgramRunner('git', '/usr/bin/git'))
71             ->withTimeout(240)
72             ->withIdleTimeout(15)
73             ->runCapturingStdErr([
74                 '-C', $appDir,
75                 'pull', '-q', 'origin', 'release',
76             ]);
77
78         if ($errors) {
79             throw new CommandError("Failed git pull with errors:\n" . $errors);
80         }
81     }
82
83     /**
84      * @throws CommandError
85      */
86     protected function installComposerDependencies(ProgramRunner $composer, string $appDir): void
87     {
88         $errors = $composer->runCapturingStdErr([
89             'install',
90             '--no-dev', '-n', '-q', '--no-progress',
91             '-d', $appDir,
92         ]);
93
94         if ($errors) {
95             throw new CommandError("Failed composer install with errors:\n" . $errors);
96         }
97     }
98
99     protected function ensureGitRepoExists(string $appDir): void
100     {
101         $expectedPath = Paths::join($appDir, '.git');
102         if (!is_dir($expectedPath)) {
103             $message = "Could not find a local git repository, it does not look like this instance is managed via common means.\n";
104             $message .= "If you are running BookStack via a docker container, you should update following the advised process for the docker container image in use.\n";
105             $message .= "This typically involves pulling and using an updated docker container image.";
106
107             throw new CommandError($message);
108         }
109     }
110 }