namespace Cli\Commands;
+use Cli\Services\ComposerLocator;
use Cli\Services\EnvironmentLoader;
use Cli\Services\ProgramRunner;
-use Minicli\Command\CommandCall;
+use Cli\Services\RequirementsValidator;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
-class InitCommand
+class InitCommand extends Command
{
+ protected function configure(): void
+ {
+ $this->setName('init');
+ $this->setDescription('Initialise a new BookStack install. Does not configure the webserver or database.');
+ $this->addArgument('target-directory', InputArgument::OPTIONAL, 'The directory to create the BookStack install within. Must be empty.', '');
+ }
+
/**
* @throws CommandError
*/
- public function handle(CommandCall $input)
+ protected function execute(InputInterface $input, OutputInterface $output): int
{
- echo "Checking system requirements...\n";
- $this->ensureRequirementsMet();
+ $output->writeln("<info>Checking system requirements...</info>");
+ RequirementsValidator::validate();
- $suggestedOutPath = $input->subcommand;
- if ($suggestedOutPath === 'default') {
- $suggestedOutPath = '';
- }
+ $suggestedOutPath = $input->getArgument('target-directory');
- echo "Locating and checking install directory...\n";
+ $output->writeln("<info>Locating and checking install directory...</info>");
$installDir = $this->getInstallDir($suggestedOutPath);
$this->ensureInstallDirEmptyAndWritable($installDir);
- echo "Cloning down BookStack project to install directory...\n";
+ $output->writeln("<info>Cloning down BookStack project to install directory...</info>");
$this->cloneBookStackViaGit($installDir);
- echo "Checking composer exists...\n";
- $composer = $this->getComposerProgram($installDir);
- try {
- $composer->ensureFound();
- } catch (\Exception $exception) {
- echo "Composer does not exist, downloading a local copy...\n";
- $this->downloadComposerToInstall($installDir);
+ $output->writeln("<info>Checking composer exists...</info>");
+ $composerLocator = new ComposerLocator($installDir);
+ $composer = $composerLocator->getProgram();
+ if (!$composer->isFound()) {
+ $output->writeln("<info>Composer does not exist, downloading a local copy...</info>");
+ $composerLocator->download();
}
- echo "Installing application dependencies using composer...\n";
+ $output->writeln("<info>Installing application dependencies using composer...</info>");
$this->installComposerDependencies($composer, $installDir);
- echo "Creating .env file from .env.example...\n";
+ $output->writeln("<info>Creating .env file from .env.example...</info>");
copy($installDir . DIRECTORY_SEPARATOR . '.env.example', $installDir . DIRECTORY_SEPARATOR . '.env');
sleep(1);
- echo "Generating app key...\n";
+ $output->writeln("<info>Generating app key...</info>");
$this->generateAppKey($installDir);
// Announce end
- echo "A BookStack install has been initialized at: {$installDir}\n\n";
- echo "You will still need to:\n";
- echo "- Update the .env file in the install with correct URL, database and email details.\n";
- echo "- Run 'php artisan migrate' to set-up the database.\n";
- echo "- Configure your webserver for use with BookStack.\n";
- echo "- Ensure the required directories (storage/ bootstrap/cache public/uploads) are web-server writable.\n";
- }
-
- /**
- * 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);
+ $output->writeln("<info>A BookStack install has been initialized at: {$installDir}\n</info>");
+ $output->writeln("<info>You will still need to:</info>");
+ $output->writeln("<info>- Update the .env file in the install with correct URL, database and email details.</info>");
+ $output->writeln("<info>- Run 'php artisan migrate' to set-up the database.</info>");
+ $output->writeln("<info>- Configure your webserver for use with BookStack.</info>");
+ $output->writeln("<info>- Ensure the required directories (storage/ bootstrap/cache public/uploads) are web-server writable.</info>");
+
+ return Command::SUCCESS;
}
protected function generateAppKey(string $installDir): void
*/
protected function cloneBookStackViaGit(string $installDir): void
{
- $errors = (new ProgramRunner('git', '/usr/bin/git'))
+ $git = (new ProgramRunner('git', '/usr/bin/git'))
->withTimeout(240)
- ->withIdleTimeout(15)
- ->runCapturingStdErr([
+ ->withIdleTimeout(15);
+
+ $errors = $git->runCapturingStdErr([
'clone', '-q',
'--branch', 'release',
'--single-branch',
if ($errors) {
throw new CommandError("Failed git clone with errors:\n" . $errors);
}
+
+ // Disable file permission tracking for git repo
+ $git->runCapturingStdErr([
+ '-C', $installDir,
+ 'config', 'core.fileMode', 'false'
+ ]);
}
/**