]> BookStack Code Mirror - system-cli/blob - scripts/Commands/InitCommand.php
1041678ee9536ef18c68167c89f94cae11c4f04b
[system-cli] / scripts / Commands / InitCommand.php
1 <?php
2
3 namespace Cli\Commands;
4
5 use Cli\Services\ProgramRunner;
6 use Minicli\Command\CommandCall;
7
8 class InitCommand
9 {
10     /**
11      * @throws CommandError
12      */
13     public function handle(CommandCall $input)
14     {
15         $this->ensureRequiredExtensionInstalled(); // TODO - Ensure bookstack install deps are met?
16
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?
23
24         $suggestedOutPath = $input->subcommand;
25         if ($suggestedOutPath === 'default') {
26             $suggestedOutPath = '';
27         }
28
29         echo "Locating and checking install directory...\n";
30         $installDir = $this->getInstallDir($suggestedOutPath);
31         $this->ensureInstallDirEmpty($installDir);
32
33         echo "Cloning down BookStack project to install directory...\n";
34         $this->cloneBookStackViaGit($installDir);
35
36         echo "Installing application dependencies using composer...\n";
37         $this->installComposerDependencies($installDir);
38
39         echo "Creating .env file from .env.example...\n";
40         copy($installDir . DIRECTORY_SEPARATOR . '.env.example', $installDir . DIRECTORY_SEPARATOR . '.env');
41         sleep(1);
42
43         echo "Generating app key...\n";
44         $this->generateAppKey($installDir);
45
46         // Announce end
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";
53     }
54
55     /**
56      * Ensure the required PHP extensions are installed for this command.
57      * @throws CommandError
58      */
59     protected function ensureRequiredExtensionInstalled(): void
60     {
61 //        if (!extension_loaded('zip')) {
62 //            throw new CommandError('The "zip" PHP extension is required to run this command');
63 //        }
64     }
65
66     protected function generateAppKey(string $installDir): void
67     {
68         $errors = (new ProgramRunner('php', '/usr/bin/php'))
69             ->withTimeout(60)
70             ->withIdleTimeout(5)
71             ->withEnvironment(['APP_KEY' => 'SomeRandomString'])
72             ->runCapturingAllOutput([
73                 $installDir . DIRECTORY_SEPARATOR . 'artisan',
74                 'key:generate', '--force', '-n', '-q'
75             ]);
76
77         if ($errors) {
78             throw new CommandError("Failed 'php artisan key:generate' with errors:\n" . $errors);
79         }
80     }
81
82     /**
83      * Run composer install to download PHP dependencies.
84      * @throws CommandError
85      */
86     protected function installComposerDependencies(string $installDir): void
87     {
88         $errors = (new ProgramRunner('composer', '/usr/local/bin/composer'))
89             ->withTimeout(300)
90             ->withIdleTimeout(15)
91             ->runCapturingStdErr([
92                 'install',
93                 '--no-dev', '-n', '-q', '--no-progress',
94                 '-d', $installDir
95             ]);
96
97         if ($errors) {
98             throw new CommandError("Failed composer install with errors:\n" . $errors);
99         }
100     }
101
102     /**
103      * Clone a new instance of BookStack to the given install folder.
104      * @throws CommandError
105      */
106     protected function cloneBookStackViaGit(string $installDir): void
107     {
108         $errors = (new ProgramRunner('git', '/usr/bin/git'))
109             ->withTimeout(240)
110             ->withIdleTimeout(15)
111             ->runCapturingStdErr([
112                 'clone', '-q',
113                 '--branch', 'release',
114                 '--single-branch',
115                 'https://github.com/BookStackApp/BookStack.git',
116                 $installDir
117             ]);
118
119         if ($errors) {
120             throw new CommandError("Failed git clone with errors:\n" . $errors);
121         }
122     }
123
124     /**
125      * Ensure that the installation directory is completely empty to avoid potential conflicts or issues.
126      * @throws CommandError
127      */
128     protected function ensureInstallDirEmpty(string $installDir): void
129     {
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.");
133         }
134     }
135
136     /**
137      * Build a full path to the intended location for the BookStack install.
138      * @throws CommandError
139      */
140     protected function getInstallDir(string $suggestedDir): string
141     {
142         $dir = getcwd();
143
144         if ($suggestedDir) {
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);
151                 if (!$created) {
152                     throw new CommandError("Could not create directory [{$suggestedDir}] for install.");
153                 }
154                 $dir = $suggestedDir;
155             } else {
156                 throw new CommandError("Could not resolve provided [{$suggestedDir}] path to an existing folder.");
157             }
158         }
159
160         return $dir;
161     }
162 }