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