]> BookStack Code Mirror - system-cli/blob - scripts/Commands/InitCommand.php
Updated env loading to be contained/controlled for usage
[system-cli] / scripts / Commands / InitCommand.php
1 <?php
2
3 namespace Cli\Commands;
4
5 use Cli\Services\EnvironmentLoader;
6 use Cli\Services\ProgramRunner;
7 use Minicli\Command\CommandCall;
8
9 class InitCommand
10 {
11     /**
12      * @throws CommandError
13      */
14     public function handle(CommandCall $input)
15     {
16         $this->ensureRequiredExtensionInstalled(); // TODO - Ensure bookstack install deps are met?
17
18         // TODO - Check composer and git exists before running
19         // TODO - Potentially download composer?
20
21         $suggestedOutPath = $input->subcommand;
22         if ($suggestedOutPath === 'default') {
23             $suggestedOutPath = '';
24         }
25
26         echo "Locating and checking install directory...\n";
27         $installDir = $this->getInstallDir($suggestedOutPath);
28         $this->ensureInstallDirEmpty($installDir);
29
30         echo "Cloning down BookStack project to install directory...\n";
31         $this->cloneBookStackViaGit($installDir);
32
33         echo "Installing application dependencies using composer...\n";
34         $this->installComposerDependencies($installDir);
35
36         echo "Creating .env file from .env.example...\n";
37         copy($installDir . DIRECTORY_SEPARATOR . '.env.example', $installDir . DIRECTORY_SEPARATOR . '.env');
38         sleep(1);
39
40         echo "Generating app key...\n";
41         $this->generateAppKey($installDir);
42
43         // Announce end
44         echo "A BookStack install has been initialized at: {$installDir}\n\n";
45         echo "You will still need to:\n";
46         echo "- Update the .env file in the install with correct URL, database and email details.\n";
47         echo "- Run 'php artisan migrate' to set-up the database.\n";
48         echo "- Configure your webserver for use with BookStack.\n";
49         echo "- Ensure the required directories (storage/ bootstrap/cache public/uploads) are web-server writable.\n";
50     }
51
52     /**
53      * Ensure the required PHP extensions are installed for this command.
54      * @throws CommandError
55      */
56     protected function ensureRequiredExtensionInstalled(): void
57     {
58 //        if (!extension_loaded('zip')) {
59 //            throw new CommandError('The "zip" PHP extension is required to run this command');
60 //        }
61     }
62
63     protected function generateAppKey(string $installDir): void
64     {
65         $errors = (new ProgramRunner('php', '/usr/bin/php'))
66             ->withTimeout(60)
67             ->withIdleTimeout(5)
68             ->withEnvironment(EnvironmentLoader::load($installDir))
69             ->runCapturingAllOutput([
70                 $installDir . DIRECTORY_SEPARATOR . 'artisan',
71                 'key:generate', '--force', '-n', '-q'
72             ]);
73
74         if ($errors) {
75             throw new CommandError("Failed 'php artisan key:generate' with errors:\n" . $errors);
76         }
77     }
78
79     /**
80      * Run composer install to download PHP dependencies.
81      * @throws CommandError
82      */
83     protected function installComposerDependencies(string $installDir): void
84     {
85         $errors = (new ProgramRunner('composer', '/usr/local/bin/composer'))
86             ->withTimeout(300)
87             ->withIdleTimeout(15)
88             ->runCapturingStdErr([
89                 'install',
90                 '--no-dev', '-n', '-q', '--no-progress',
91                 '-d', $installDir
92             ]);
93
94         if ($errors) {
95             throw new CommandError("Failed composer install with errors:\n" . $errors);
96         }
97     }
98
99     /**
100      * Clone a new instance of BookStack to the given install folder.
101      * @throws CommandError
102      */
103     protected function cloneBookStackViaGit(string $installDir): void
104     {
105         $errors = (new ProgramRunner('git', '/usr/bin/git'))
106             ->withTimeout(240)
107             ->withIdleTimeout(15)
108             ->runCapturingStdErr([
109                 'clone', '-q',
110                 '--branch', 'release',
111                 '--single-branch',
112                 'https://github.com/BookStackApp/BookStack.git',
113                 $installDir
114             ]);
115
116         if ($errors) {
117             throw new CommandError("Failed git clone with errors:\n" . $errors);
118         }
119     }
120
121     /**
122      * Ensure that the installation directory is completely empty to avoid potential conflicts or issues.
123      * @throws CommandError
124      */
125     protected function ensureInstallDirEmpty(string $installDir): void
126     {
127         $contents = array_diff(scandir($installDir), ['..', '.']);
128         if (count($contents) > 0) {
129             throw new CommandError("Expected install directory to be empty but existing files found in [{$installDir}] target location.");
130         }
131     }
132
133     /**
134      * Build a full path to the intended location for the BookStack install.
135      * @throws CommandError
136      */
137     protected function getInstallDir(string $suggestedDir): string
138     {
139         $dir = getcwd();
140
141         if ($suggestedDir) {
142             if (is_file($suggestedDir)) {
143                 throw new CommandError("Was provided [{$suggestedDir}] as an install path but existing file provided.");
144             } else if (is_dir($suggestedDir)) {
145                 $dir = realpath($suggestedDir);
146             } else if (is_dir(dirname($suggestedDir))) {
147                 $created = mkdir($suggestedDir);
148                 if (!$created) {
149                     throw new CommandError("Could not create directory [{$suggestedDir}] for install.");
150                 }
151                 $dir = $suggestedDir;
152             } else {
153                 throw new CommandError("Could not resolve provided [{$suggestedDir}] path to an existing folder.");
154             }
155         }
156
157         return $dir;
158     }
159 }