3 namespace Cli\Commands;
5 use Cli\Services\ProgramRunner;
6 use Minicli\Command\CommandCall;
11 * @throws CommandError
13 public function handle(CommandCall $input)
15 $this->ensureRequiredExtensionInstalled(); // TODO - Ensure bookstack install deps are met?
17 // TODO - Check composer and git exists before running
18 // TODO - Look at better way of handling env usage, on demand maybe where needed?
19 // Env loading in main `run` script if confilicting with certain bits here (app key generate, hence APP_KEY overload)
20 // See dotenv's Dotenv::createArrayBacked as way to go this.
21 // (More of a change for 'backup' command).
22 // TODO - Potentially download composer?
24 $suggestedOutPath = $input->subcommand;
25 if ($suggestedOutPath === 'default') {
26 $suggestedOutPath = '';
29 echo "Locating and checking install directory...\n";
30 $installDir = $this->getInstallDir($suggestedOutPath);
31 $this->ensureInstallDirEmpty($installDir);
33 echo "Cloning down BookStack project to install directory...\n";
34 $this->cloneBookStackViaGit($installDir);
36 echo "Installing application dependencies using composer...\n";
37 $this->installComposerDependencies($installDir);
39 echo "Creating .env file from .env.example...\n";
40 copy($installDir . DIRECTORY_SEPARATOR . '.env.example', $installDir . DIRECTORY_SEPARATOR . '.env');
43 echo "Generating app key...\n";
44 $this->generateAppKey($installDir);
47 echo "A BookStack install has been initialized at: {$installDir}\n\n";
48 echo "You will still need to:\n";
49 echo "- Update the .env file in the install with correct URL, database and email details.\n";
50 echo "- Run 'php artisan migrate' to set-up the database.\n";
51 echo "- Configure your webserver for use with BookStack.\n";
52 echo "- Ensure the required directories (storage/ bootstrap/cache public/uploads) are web-server writable.\n";
56 * Ensure the required PHP extensions are installed for this command.
57 * @throws CommandError
59 protected function ensureRequiredExtensionInstalled(): void
61 // if (!extension_loaded('zip')) {
62 // throw new CommandError('The "zip" PHP extension is required to run this command');
66 protected function generateAppKey(string $installDir): void
68 $errors = (new ProgramRunner('php', '/usr/bin/php'))
71 ->withEnvironment(['APP_KEY' => 'SomeRandomString'])
72 ->runCapturingAllOutput([
73 $installDir . DIRECTORY_SEPARATOR . 'artisan',
74 'key:generate', '--force', '-n', '-q'
78 throw new CommandError("Failed 'php artisan key:generate' with errors:\n" . $errors);
83 * Run composer install to download PHP dependencies.
84 * @throws CommandError
86 protected function installComposerDependencies(string $installDir): void
88 $errors = (new ProgramRunner('composer', '/usr/local/bin/composer'))
91 ->runCapturingStdErr([
93 '--no-dev', '-n', '-q', '--no-progress',
98 throw new CommandError("Failed composer install with errors:\n" . $errors);
103 * Clone a new instance of BookStack to the given install folder.
104 * @throws CommandError
106 protected function cloneBookStackViaGit(string $installDir): void
108 $errors = (new ProgramRunner('git', '/usr/bin/git'))
110 ->withIdleTimeout(15)
111 ->runCapturingStdErr([
113 '--branch', 'release',
115 'https://github.com/BookStackApp/BookStack.git',
120 throw new CommandError("Failed git clone with errors:\n" . $errors);
125 * Ensure that the installation directory is completely empty to avoid potential conflicts or issues.
126 * @throws CommandError
128 protected function ensureInstallDirEmpty(string $installDir): void
130 $contents = array_diff(scandir($installDir), ['..', '.']);
131 if (count($contents) > 0) {
132 throw new CommandError("Expected install directory to be empty but existing files found in [{$installDir}] target location.");
137 * Build a full path to the intended location for the BookStack install.
138 * @throws CommandError
140 protected function getInstallDir(string $suggestedDir): string
145 if (is_file($suggestedDir)) {
146 throw new CommandError("Was provided [{$suggestedDir}] as an install path but existing file provided.");
147 } else if (is_dir($suggestedDir)) {
148 $dir = realpath($suggestedDir);
149 } else if (is_dir(dirname($suggestedDir))) {
150 $created = mkdir($suggestedDir);
152 throw new CommandError("Could not create directory [{$suggestedDir}] for install.");
154 $dir = $suggestedDir;
156 throw new CommandError("Could not resolve provided [{$suggestedDir}] path to an existing folder.");