]> BookStack Code Mirror - system-cli/commitdiff
Added "init" command to admin-cli
authorDan Brown <redacted>
Sat, 4 Mar 2023 02:40:29 +0000 (02:40 +0000)
committerDan Brown <redacted>
Thu, 9 Mar 2023 15:28:12 +0000 (15:28 +0000)
Got to basic working state, some todos in there.

scripts/Commands/BackupCommand.php
scripts/Commands/InitCommand.php [new file with mode: 0644]
scripts/run

index f2c47a27f73ce1b6fd26322d1befeb4ca2e7758d..a607f342a0c55e2e80c6ffecc66bd92176c7b2d2 100644 (file)
@@ -66,7 +66,7 @@ final class BackupCommand
         $zip->close();
         rename($zipTempFile, $zipOutFile);
 
-        // Announce end and display errors
+        // Announce end
         echo "Backup finished.\nOutput ZIP saved to: {$zipOutFile}\n";
     }
 
diff --git a/scripts/Commands/InitCommand.php b/scripts/Commands/InitCommand.php
new file mode 100644 (file)
index 0000000..1c943a2
--- /dev/null
@@ -0,0 +1,205 @@
+<?php
+
+namespace Cli\Commands;
+
+use Minicli\Command\CommandCall;
+use Symfony\Component\Process\ExecutableFinder;
+use Symfony\Component\Process\Process;
+
+class InitCommand
+{
+    /**
+     * @throws CommandError
+     */
+    public function handle(CommandCall $input)
+    {
+        $this->ensureRequiredExtensionInstalled(); // TODO - Ensure bookstack install deps are met?
+
+        // TODO - Dedupe the command stuff going on.
+        // TODO - Check composer and git exists before running
+        // TODO - Look at better way of handling env usage, on demand maybe where needed?
+        //   Env loading in main `run` script if confilicting with certain bits here (app key generate, hence APP_KEY overload)
+        //   See dotenv's Dotenv::createArrayBacked as way to go this.
+        //   (More of a change for 'backup' command).
+        // TODO - Potentially download composer?
+
+        $suggestedOutPath = $input->subcommand;
+        if ($suggestedOutPath === 'default') {
+            $suggestedOutPath = '';
+        }
+
+        echo "Locating and checking install directory...\n";
+        $installDir = $this->getInstallDir($suggestedOutPath);
+        $this->ensureInstallDirEmpty($installDir);
+
+        echo "Cloning down BookStack project to install directory...\n";
+        $this->cloneBookStackViaGit($installDir);
+
+        echo "Installing application dependencies using composer...\n";
+        $this->installComposerDependencies($installDir);
+
+        echo "Creating .env file from .env.example...\n";
+        copy($installDir . DIRECTORY_SEPARATOR . '.env.example', $installDir . DIRECTORY_SEPARATOR . '.env');
+        sleep(1);
+
+        echo "Generating app key...\n";
+        $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 ensureRequiredExtensionInstalled(): void
+    {
+//        if (!extension_loaded('zip')) {
+//            throw new CommandError('The "zip" PHP extension is required to run this command');
+//        }
+    }
+
+    protected function generateAppKey(string $installDir): void
+    {
+        // Find reference to php
+        $executableFinder = new ExecutableFinder();
+        $phpPath = $executableFinder->find('php', '/usr/bin/php');
+        if (!is_file($phpPath)) {
+            throw new CommandError('Could not locate "php" program.');
+        }
+
+        $process = new Process([
+            $phpPath,
+            $installDir . DIRECTORY_SEPARATOR . 'artisan',
+            'key:generate', '--force', '-n', '-q'
+        ], null, ['APP_KEY' => 'SomeRandomString']);
+        $process->setTimeout(240);
+        $process->setIdleTimeout(5);
+        $process->start();
+
+        $errors = '';
+        foreach ($process as $type => $data) {
+            // Errors are on stdout for artisan
+            $errors .= $data . "\n";
+        }
+
+        if ($errors) {
+            throw new CommandError("Failed 'php artisan key:generate' with errors:\n" . $errors);
+        }
+    }
+
+    /**
+     * Run composer install to download PHP dependencies.
+     * @throws CommandError
+     */
+    protected function installComposerDependencies(string $installDir): void
+    {
+        // Find reference to composer
+        $executableFinder = new ExecutableFinder();
+        $composerPath = $executableFinder->find('composer', '/usr/local/bin/composer');
+        if (!is_file($composerPath)) {
+            throw new CommandError('Could not locate "composer" program.');
+        }
+
+        $process = new Process([
+            $composerPath, 'install',
+            '--no-dev', '-n', '-q', '--no-progress',
+            '-d', $installDir
+        ]);
+        $process->setTimeout(240);
+        $process->setIdleTimeout(15);
+        $process->start();
+
+        $errors = '';
+        foreach ($process as $type => $data) {
+            if ($process::ERR === $type) {
+                $errors .= $data . "\n";
+            }
+        }
+
+        if ($errors) {
+            throw new CommandError("Failed composer install with errors:\n" . $errors);
+        }
+    }
+
+    /**
+     * Clone a new instance of BookStack to the given install folder.
+     * @throws CommandError
+     */
+    protected function cloneBookStackViaGit(string $installDir): void
+    {
+        // Find reference to git
+        $executableFinder = new ExecutableFinder();
+        $gitPath = $executableFinder->find('git', '/usr/bin/bit');
+        if (!is_file($gitPath)) {
+            throw new CommandError('Could not locate "git" program.');
+        }
+
+        $process = new Process([
+            $gitPath, 'clone', '-q',
+            '--branch', 'release',
+            '--single-branch',
+            'https://github.com/BookStackApp/BookStack.git',
+            $installDir
+        ]);
+        $process->setTimeout(240);
+        $process->setIdleTimeout(15);
+        $process->start();
+
+        $errors = '';
+        foreach ($process as $type => $data) {
+            if ($process::ERR === $type) {
+                $errors .= $data . "\n";
+            }
+        }
+
+        if ($errors) {
+            throw new CommandError("Failed git clone with errors:\n" . $errors);
+        }
+    }
+
+    /**
+     * Ensure that the installation directory is completely empty to avoid potential conflicts or issues.
+     * @throws CommandError
+     */
+    protected function ensureInstallDirEmpty(string $installDir): void
+    {
+        $contents = array_diff(scandir($installDir), ['..', '.']);
+        if (count($contents) > 0) {
+            throw new CommandError("Expected install directory to be empty but existing files found in [{$installDir}] target location.");
+        }
+    }
+
+    /**
+     * Build a full path to the intended location for the BookStack install.
+     * @throws CommandError
+     */
+    protected function getInstallDir(string $suggestedDir): string
+    {
+        $dir = getcwd();
+
+        if ($suggestedDir) {
+            if (is_file($suggestedDir)) {
+                throw new CommandError("Was provided [{$suggestedDir}] as an install path but existing file provided.");
+            } else if (is_dir($suggestedDir)) {
+                $dir = realpath($suggestedDir);
+            } else if (is_dir(dirname($suggestedDir))) {
+                $created = mkdir($suggestedDir);
+                if (!$created) {
+                    throw new CommandError("Could not create directory [{$suggestedDir}] for install.");
+                }
+                $dir = $suggestedDir;
+            } else {
+                throw new CommandError("Could not resolve provided [{$suggestedDir}] path to an existing folder.");
+            }
+        }
+
+        return $dir;
+    }
+}
index ff38c81515318bfdee8a5f5e7d63221f57ba07eb..6f1d20567d6a557fe674313a50c775a104e423a8 100644 (file)
@@ -9,6 +9,7 @@ require __DIR__ . '/vendor/autoload.php';
 
 use Cli\Commands\BackupCommand;
 use Cli\Commands\CommandError;
+use Cli\Commands\InitCommand;
 use Minicli\App;
 
 // Get the directory of the CLI "entrypoint", adjusted to be the real
@@ -28,6 +29,7 @@ $app = new App();
 $app->setSignature('./run');
 
 $app->registerCommand('backup', [new BackupCommand($bsDir), 'handle']);
+$app->registerCommand('init', [new InitCommand(), 'handle']);
 
 try {
     $app->runCommand($argv);