setName('update'); $this->setDescription('Update an existing BookStack instance.'); $this->addOption('app-directory', null, InputOption::VALUE_OPTIONAL, 'BookStack install directory to update', ''); } /** * @throws CommandError */ protected function execute(InputInterface $input, OutputInterface $output): int { $appDir = AppLocator::require($input->getOption('app-directory')); $output->writeln("Checking system requirements..."); RequirementsValidator::validate(); $output->writeln("Checking local Git repository is active..."); $this->ensureGitRepoExists($appDir); $output->writeln("Checking composer exists..."); $composerLocator = new ComposerLocator($appDir); $composer = $composerLocator->getProgram(); if (!$composer->isFound()) { $output->writeln("Composer does not exist, downloading a local copy..."); $composerLocator->download(); } $cliPath = Phar::running(false); $cliPreUpdateHash = $cliPath ? hash_file('sha256', $cliPath) : ''; $output->writeln("Fetching latest code via Git..."); $this->updateCodeUsingGit($appDir); $cliPostUpdateHash = $cliPath ? hash_file('sha256', $cliPath) : ''; if ($cliPostUpdateHash !== $cliPreUpdateHash) { $output->writeln("System CLI file changed during update!\nRe-run the update command to complete the update process."); return Command::FAILURE; } $output->writeln("Installing PHP dependencies via composer..."); $this->installComposerDependencies($composer, $appDir); $output->writeln("Running database migrations..."); $artisan = (new ArtisanRunner($appDir)); $artisan->run(['migrate', '--force']); $output->writeln("Clearing app caches..."); $artisan->run(['cache:clear']); $artisan->run(['config:clear']); $artisan->run(['view:clear']); $output->writeln("Your BookStack instance at [{$appDir}] has been updated!"); return Command::SUCCESS; } /** * @throws CommandError */ protected function updateCodeUsingGit(string $appDir): void { $errors = (new ProgramRunner('git', '/usr/bin/git')) ->withTimeout(240) ->withIdleTimeout(15) ->runCapturingStdErr([ '-C', $appDir, 'pull', '-q', 'origin', 'release', ]); if ($errors) { throw new CommandError("Failed git pull with errors:\n" . $errors); } } /** * @throws CommandError */ protected function installComposerDependencies(ProgramRunner $composer, string $appDir): void { $errors = $composer->runCapturingStdErr([ 'install', '--no-dev', '-n', '-q', '--no-progress', '-d', $appDir, ]); if ($errors) { throw new CommandError("Failed composer install with errors:\n" . $errors); } } protected function ensureGitRepoExists(string $appDir): void { $expectedPath = Paths::join($appDir, '.git'); if (!is_dir($expectedPath)) { $message = "Could not find a local git repository, it does not look like this instance is managed via common means.\n"; $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"; $message .= "This typically involves pulling and using an updated docker container image."; throw new CommandError($message); } } }