mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-10-01 01:36:00 -04:00
Added update command
Extracted some common parts to their own service files
This commit is contained in:
parent
0be1bf7499
commit
3b6de8872a
@ -2,8 +2,10 @@
|
||||
|
||||
namespace Cli\Commands;
|
||||
|
||||
use Cli\Services\ComposerLocator;
|
||||
use Cli\Services\EnvironmentLoader;
|
||||
use Cli\Services\ProgramRunner;
|
||||
use Cli\Services\RequirementsValidator;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
@ -24,7 +26,7 @@ class InitCommand extends Command
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$output->writeln("<info>Checking system requirements...</info>");
|
||||
$this->ensureRequirementsMet();
|
||||
RequirementsValidator::validate();
|
||||
|
||||
$suggestedOutPath = $input->getArgument('target-directory');
|
||||
|
||||
@ -36,12 +38,11 @@ class InitCommand extends Command
|
||||
$this->cloneBookStackViaGit($installDir);
|
||||
|
||||
$output->writeln("<info>Checking composer exists...</info>");
|
||||
$composer = $this->getComposerProgram($installDir);
|
||||
try {
|
||||
$composer->ensureFound();
|
||||
} catch (\Exception $exception) {
|
||||
$composerLocator = new ComposerLocator($installDir);
|
||||
$composer = $composerLocator->getProgram();
|
||||
if (!$composer->isFound()) {
|
||||
$output->writeln("<info>Composer does not exist, downloading a local copy...</info>");
|
||||
$this->downloadComposerToInstall($installDir);
|
||||
$composerLocator->download();
|
||||
}
|
||||
|
||||
$output->writeln("<info>Installing application dependencies using composer...</info>");
|
||||
@ -65,74 +66,6 @@ class InitCommand extends Command
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the required PHP extensions are installed for this command.
|
||||
* @throws CommandError
|
||||
*/
|
||||
protected function ensureRequirementsMet(): void
|
||||
{
|
||||
$errors = [];
|
||||
|
||||
if (version_compare(PHP_VERSION, '8.0.2') < 0) {
|
||||
$errors[] = "PHP >= 8.0.2 is required to install BookStack.";
|
||||
}
|
||||
|
||||
$requiredExtensions = ['bcmath', 'curl', 'gd', 'iconv', 'libxml', 'mbstring', 'mysqlnd', 'xml'];
|
||||
foreach ($requiredExtensions as $extension) {
|
||||
if (!extension_loaded($extension)) {
|
||||
$errors[] = "The \"{$extension}\" PHP extension is required by not active.";
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
(new ProgramRunner('git', '/usr/bin/git'))->ensureFound();
|
||||
(new ProgramRunner('php', '/usr/bin/php'))->ensureFound();
|
||||
} catch (\Exception $exception) {
|
||||
$errors[] = $exception->getMessage();
|
||||
}
|
||||
|
||||
if (count($errors) > 0) {
|
||||
throw new CommandError("Requirements failed with following errors:\n" . implode("\n", $errors));
|
||||
}
|
||||
}
|
||||
|
||||
protected function downloadComposerToInstall(string $installDir): void
|
||||
{
|
||||
$setupPath = $installDir . DIRECTORY_SEPARATOR . 'composer-setup.php';
|
||||
$signature = file_get_contents('https://composer.github.io/installer.sig');
|
||||
copy('https://getcomposer.org/installer', $setupPath);
|
||||
$checksum = hash_file('sha384', $setupPath);
|
||||
|
||||
if ($signature !== $checksum) {
|
||||
unlink($setupPath);
|
||||
throw new CommandError("Could not install composer, checksum validation failed.");
|
||||
}
|
||||
|
||||
$status = (new ProgramRunner('php', '/usr/bin/php'))
|
||||
->runWithoutOutputCallbacks([
|
||||
$setupPath, '--quiet',
|
||||
"--install-dir={$installDir}",
|
||||
"--filename=composer",
|
||||
]);
|
||||
|
||||
unlink($setupPath);
|
||||
|
||||
if ($status !== 0) {
|
||||
throw new CommandError("Could not install composer, composer-setup script run failed.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the composer program.
|
||||
*/
|
||||
protected function getComposerProgram(string $installDir): ProgramRunner
|
||||
{
|
||||
return (new ProgramRunner('composer', '/usr/local/bin/composer'))
|
||||
->withTimeout(300)
|
||||
->withIdleTimeout(15)
|
||||
->withAdditionalPathLocation($installDir);
|
||||
}
|
||||
|
||||
protected function generateAppKey(string $installDir): void
|
||||
{
|
||||
$errors = (new ProgramRunner('php', '/usr/bin/php'))
|
||||
|
112
scripts/Commands/UpdateCommand.php
Normal file
112
scripts/Commands/UpdateCommand.php
Normal file
@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
namespace Cli\Commands;
|
||||
|
||||
use Cli\Services\ComposerLocator;
|
||||
use Cli\Services\EnvironmentLoader;
|
||||
use Cli\Services\ProgramRunner;
|
||||
use Cli\Services\RequirementsValidator;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class UpdateCommand extends Command
|
||||
{
|
||||
|
||||
public function __construct(
|
||||
protected string $appDir
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setName('update');
|
||||
$this->setDescription('Update an existing BookStack instance.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws CommandError
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$output->writeln("<info>Checking system requirements...</info>");
|
||||
RequirementsValidator::validate();
|
||||
|
||||
$output->writeln("<info>Checking composer exists...</info>");
|
||||
$composerLocator = new ComposerLocator($this->appDir);
|
||||
$composer = $composerLocator->getProgram();
|
||||
if (!$composer->isFound()) {
|
||||
$output->writeln("<info>Composer does not exist, downloading a local copy...</info>");
|
||||
$composerLocator->download();
|
||||
}
|
||||
|
||||
$output->writeln("<info>Fetching latest code via Git...</info>");
|
||||
$this->updateCodeUsingGit();
|
||||
|
||||
$output->writeln("<info>Installing PHP dependencies via composer...</info>");
|
||||
$this->installComposerDependencies($composer);
|
||||
|
||||
$output->writeln("<info>Running database migrations...</info>");
|
||||
$this->runArtisanCommand(['migrate', '--force']);
|
||||
|
||||
$output->writeln("<info>Clearing app caches...</info>");
|
||||
$this->runArtisanCommand(['cache:clear']);
|
||||
$this->runArtisanCommand(['config:clear']);
|
||||
$this->runArtisanCommand(['view:clear']);
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws CommandError
|
||||
*/
|
||||
protected function updateCodeUsingGit(): void
|
||||
{
|
||||
$errors = (new ProgramRunner('git', '/usr/bin/git'))
|
||||
->withTimeout(240)
|
||||
->withIdleTimeout(15)
|
||||
->runCapturingStdErr([
|
||||
'-C', $this->appDir,
|
||||
'pull', '-q', 'origin', 'release',
|
||||
]);
|
||||
|
||||
if ($errors) {
|
||||
throw new CommandError("Failed git pull with errors:\n" . $errors);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws CommandError
|
||||
*/
|
||||
protected function installComposerDependencies(ProgramRunner $composer): void
|
||||
{
|
||||
$errors = $composer->runCapturingStdErr([
|
||||
'install',
|
||||
'--no-dev', '-n', '-q', '--no-progress',
|
||||
'-d', $this->appDir,
|
||||
]);
|
||||
|
||||
if ($errors) {
|
||||
throw new CommandError("Failed composer install with errors:\n" . $errors);
|
||||
}
|
||||
}
|
||||
|
||||
protected function runArtisanCommand(array $commandArgs): void
|
||||
{
|
||||
$errors = (new ProgramRunner('php', '/usr/bin/php'))
|
||||
->withTimeout(60)
|
||||
->withIdleTimeout(5)
|
||||
->withEnvironment(EnvironmentLoader::load($this->appDir))
|
||||
->runCapturingAllOutput([
|
||||
$this->appDir . DIRECTORY_SEPARATOR . 'artisan',
|
||||
'-n', '-q',
|
||||
...$commandArgs
|
||||
]);
|
||||
|
||||
if ($errors) {
|
||||
$cmdString = implode(' ', $commandArgs);
|
||||
throw new CommandError("Failed 'php artisan {$cmdString}' with errors:\n" . $errors);
|
||||
}
|
||||
}
|
||||
}
|
50
scripts/Services/ComposerLocator.php
Normal file
50
scripts/Services/ComposerLocator.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Cli\Services;
|
||||
|
||||
use Exception;
|
||||
|
||||
class ComposerLocator
|
||||
{
|
||||
public function __construct(
|
||||
protected string $appDir
|
||||
) {
|
||||
}
|
||||
|
||||
public function getProgram(): ProgramRunner
|
||||
{
|
||||
return (new ProgramRunner('composer', '/usr/local/bin/composer'))
|
||||
->withTimeout(300)
|
||||
->withIdleTimeout(15)
|
||||
->withAdditionalPathLocation($this->appDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function download(): void
|
||||
{
|
||||
$setupPath = $this->appDir . DIRECTORY_SEPARATOR . 'composer-setup.php';
|
||||
$signature = file_get_contents('https://composer.github.io/installer.sig');
|
||||
copy('https://getcomposer.org/installer', $setupPath);
|
||||
$checksum = hash_file('sha384', $setupPath);
|
||||
|
||||
if ($signature !== $checksum) {
|
||||
unlink($setupPath);
|
||||
throw new Exception("Could not install composer, checksum validation failed.");
|
||||
}
|
||||
|
||||
$status = (new ProgramRunner('php', '/usr/bin/php'))
|
||||
->runWithoutOutputCallbacks([
|
||||
$setupPath, '--quiet',
|
||||
"--install-dir={$this->appDir}",
|
||||
"--filename=composer",
|
||||
]);
|
||||
|
||||
unlink($setupPath);
|
||||
|
||||
if ($status !== 0) {
|
||||
throw new Exception("Could not install composer, composer-setup script run failed.");
|
||||
}
|
||||
}
|
||||
}
|
@ -88,6 +88,16 @@ class ProgramRunner
|
||||
$this->resolveProgramPath();
|
||||
}
|
||||
|
||||
public function isFound(): bool
|
||||
{
|
||||
try {
|
||||
$this->ensureFound();
|
||||
return true;
|
||||
} catch (\Exception $exception) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected function startProcess(array $args): Process
|
||||
{
|
||||
$programPath = $this->resolveProgramPath();
|
||||
|
39
scripts/Services/RequirementsValidator.php
Normal file
39
scripts/Services/RequirementsValidator.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Cli\Services;
|
||||
|
||||
use Exception;
|
||||
|
||||
class RequirementsValidator
|
||||
{
|
||||
/**
|
||||
* Ensure the required PHP extensions are installed for this command.
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function validate(): void
|
||||
{
|
||||
$errors = [];
|
||||
|
||||
if (version_compare(PHP_VERSION, '8.0.2') < 0) {
|
||||
$errors[] = "PHP >= 8.0.2 is required to install BookStack.";
|
||||
}
|
||||
|
||||
$requiredExtensions = ['bcmath', 'curl', 'gd', 'iconv', 'libxml', 'mbstring', 'mysqlnd', 'xml'];
|
||||
foreach ($requiredExtensions as $extension) {
|
||||
if (!extension_loaded($extension)) {
|
||||
$errors[] = "The \"{$extension}\" PHP extension is required by not active.";
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
(new ProgramRunner('git', '/usr/bin/git'))->ensureFound();
|
||||
(new ProgramRunner('php', '/usr/bin/php'))->ensureFound();
|
||||
} catch (Exception $exception) {
|
||||
$errors[] = $exception->getMessage();
|
||||
}
|
||||
|
||||
if (count($errors) > 0) {
|
||||
throw new Exception("Requirements failed with following errors:\n" . implode("\n", $errors));
|
||||
}
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@ require __DIR__ . '/vendor/autoload.php';
|
||||
use Symfony\Component\Console\Application;
|
||||
use Cli\Commands\BackupCommand;
|
||||
use Cli\Commands\InitCommand;
|
||||
use Cli\Commands\UpdateCommand;
|
||||
|
||||
// Get the directory of the CLI "entrypoint", adjusted to be the real
|
||||
// location where running via a phar.
|
||||
@ -17,12 +18,15 @@ $scriptDir = __DIR__;
|
||||
if (str_starts_with($scriptDir, 'phar://')) {
|
||||
$scriptDir = dirname(Phar::running(false));
|
||||
}
|
||||
// TODO - Add smarter strategy for locating install
|
||||
// (working directory or directory of running script or maybe passed option?)
|
||||
$bsDir = dirname($scriptDir);
|
||||
|
||||
// Setup our CLI
|
||||
$app = new Application('bookstack-system');
|
||||
|
||||
$app->add(new BackupCommand($bsDir));
|
||||
$app->add(new UpdateCommand($bsDir));
|
||||
$app->add(new InitCommand());
|
||||
|
||||
try {
|
||||
|
Loading…
Reference in New Issue
Block a user