]> BookStack Code Mirror - system-cli/commitdiff
Made changes based upon freebsd/openbsd testing
authorDan Brown <redacted>
Sat, 29 Apr 2023 13:02:00 +0000 (14:02 +0100)
committerDan Brown <redacted>
Sat, 29 Apr 2023 13:02:00 +0000 (14:02 +0100)
- Added better generally output formatting with warnings and blue info.
- Addressed deprecation warning when opening empty zip from temp file.
- Updated mysqldump so warnings did not stop dump, but are shown to
  users.
- Massively increased timeouts where needed.
- Fixed a few typos.
- Logged extra mysql known issues.
- Added protocol to mysql commands since that's what's expected in
  BookStack's usage.
- Updated required extensions list to be more comprehensive, based upon
  actual need on FreeBSD (Where php defaults were minimal) and extracted
  requirements to static class vars for easier editing.

readme.md
run.php
src/Commands/BackupCommand.php
src/Commands/InitCommand.php
src/Commands/RestoreCommand.php
src/Commands/UpdateCommand.php
src/Services/ArtisanRunner.php
src/Services/ComposerLocator.php
src/Services/MySqlRunner.php
src/Services/RequirementsValidator.php

index 35112ca4bc43b889437fd6730d48b5ee2ea8411f..1af2452c44d6818594474df2e2ee32935ee9c37a 100644 (file)
--- a/readme.md
+++ b/readme.md
@@ -66,4 +66,23 @@ mysqldump may produce the following:
 > mysqldump: Couldn't execute 'FLUSH TABLES': Access denied; you need (at least one of) the RELOAD or FLUSH_TABLES privilege(s) for this operation (1227)
 
 This was due to 8.0.32 mysqldump, changing the required permissions, and this should be largely [fixed as per 8.0.33](https://bugs.mysql.com/bug.php?id=109685).
-Temporary workaround is to provide the database user RELOAD permissions: `GRANT RELOAD ON *.* TO 'bookstack'@'%';`
\ No newline at end of file
+Temporary workaround is to provide the database user RELOAD permissions. For example:
+
+```mysql
+GRANT RELOAD ON *.* TO 'bookstack'@'localhost';
+```
+#### mysql - Restore throws permission error
+
+MySQL during restore can throw the following:
+
+> ERROR 1227 (42000) at line 18 in file: '/root/bookstack/restore-temp-1682771620/db.sql': Access denied; you need (at least one of) the SUPER, SYSTEM_VARIABLES_ADMIN or SESSION_VARIABLES_ADMIN privilege(s) for this operation
+
+This is due to mysql adding replication data to the output.
+We could set `--set-gtid-purged=value` during dump but does not exist for mariadb.
+Alternatively, we could maybe filter these SET lines? But need to be targeted and efficient since files dump files may be large.
+
+#### mysql - Only TCP connections
+
+This assumes a database connection via a TCP connection is being used by BookStack.
+Socket/other type of connections could technically be used with BookStack but is not something we advise
+or document within our docs or env options listing, so we make this assumption for simplicity.
\ No newline at end of file
diff --git a/run.php b/run.php
index 4d503fede26407a544079bd31511fa57488d98ec..10ee64b337f262d142ddcb13f58c4c53d4057c2e 100644 (file)
--- a/run.php
+++ b/run.php
@@ -10,10 +10,20 @@ require __DIR__ . '/vendor/autoload.php';
 use Symfony\Component\Console\Formatter\OutputFormatterStyle;
 use Symfony\Component\Console\Output\ConsoleOutput;
 
+// Get the app with commands loaded
 $app = require __DIR__ . '/src/app.php';
 
+// Configure output formatting
+$output =  new ConsoleOutput();
+$formatter = $output->getFormatter();
+$formatter->setStyle('warn', new OutputFormatterStyle('yellow'));
+$formatter->setStyle('info', new OutputFormatterStyle('blue'));
+$formatter->setStyle('success', new OutputFormatterStyle('green'));
+$formatter->setStyle('error', new OutputFormatterStyle('red'));
+
+// Run the command and handle errors
 try {
-    $app->run();
+    $app->run(null, $output);
 } catch (Exception $error) {
     $output = (new ConsoleOutput())->getErrorOutput();
     $output->getFormatter()->setStyle('error', new OutputFormatterStyle('red'));
index 6a0871752866aabd465fd184f09c16405792a2c9..39b3cddf533e70e76a00b11698672b8fe64fec53 100644 (file)
@@ -48,7 +48,7 @@ final class BackupCommand extends Command
         $zipTempFile = tempnam(sys_get_temp_dir(), 'bsbackup');
         $dumpTempFile = '';
         $zip = new ZipArchive();
-        $zip->open($zipTempFile, ZipArchive::CREATE);
+        $zip->open($zipTempFile, ZipArchive::OVERWRITE);
 
         // Add default files (.env config file and this CLI if existing)
         $zip->addFile(Paths::join($appDir, '.env'), '.env');
@@ -59,7 +59,7 @@ final class BackupCommand extends Command
 
         if ($handleDatabase) {
             $output->writeln("<info>Dumping the database via mysqldump...</info>");
-            $dumpTempFile = $this->createDatabaseDump($appDir);
+            $dumpTempFile = $this->createDatabaseDump($appDir, $output);
             $output->writeln("<info>Adding database dump to backup archive...</info>");
             $zip->addFile($dumpTempFile, 'db.sql');
         }
@@ -85,7 +85,7 @@ final class BackupCommand extends Command
         rename($zipTempFile, $zipOutFile);
 
         // Announce end
-        $output->writeln("<info>Backup finished.</info>");
+        $output->writeln("<success>Backup finished.</success>");
         $output->writeln("Output ZIP saved to: {$zipOutFile}");
 
         return Command::SUCCESS;
@@ -172,7 +172,7 @@ final class BackupCommand extends Command
      * Create a database dump and return the path to the dumped SQL output.
      * @throws CommandError
      */
-    protected function createDatabaseDump(string $appDir): string
+    protected function createDatabaseDump(string $appDir, OutputInterface $output): string
     {
         $envOptions = EnvironmentLoader::loadMergedWithCurrentEnv($appDir);
         $mysql = MySqlRunner::fromEnvOptions($envOptions);
@@ -180,7 +180,10 @@ final class BackupCommand extends Command
 
         $dumpTempFile = tempnam(sys_get_temp_dir(), 'bsdbdump');
         try {
-            $mysql->runDumpToFile($dumpTempFile);
+            $warnings = $mysql->runDumpToFile($dumpTempFile);
+            if ($warnings) {
+                $output->writeln("<warn>Received warnings during mysqldump:\n{$warnings}</warn>");
+            }
         } catch (\Exception $exception) {
             unlink($dumpTempFile);
             throw new CommandError($exception->getMessage());
index ef7927389ebee4de69fffc1a0b5dcb1a79fdda15..a82b02d4908a12456062d4f0c678d1eb2732dd2e 100644 (file)
@@ -57,7 +57,7 @@ class InitCommand extends Command
         $this->generateAppKey($installDir);
 
         // Announce end
-        $output->writeln("<info>A BookStack install has been initialized at: {$installDir}\n</info>");
+        $output->writeln("<success>A BookStack install has been initialized at: {$installDir}\n</success>");
         $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>");
@@ -107,8 +107,8 @@ class InitCommand extends Command
     protected function cloneBookStackViaGit(string $installDir): void
     {
         $git = (new ProgramRunner('git', '/usr/bin/git'))
-            ->withTimeout(240)
-            ->withIdleTimeout(15);
+            ->withTimeout(300)
+            ->withIdleTimeout(300);
 
         $errors = $git->runCapturingStdErr([
                 'clone', '-q',
index 2f71db0f25cad76de14a7fba81cfa1b7f534c2be..4a5fad74ad635989c5b79e6f8aacdafe90c369a5 100644 (file)
@@ -37,11 +37,11 @@ class RestoreCommand extends Command
     {
         $interactions = new InteractiveConsole($this->getHelper('question'), $input, $output);
 
-        $output->writeln("<info>Warning!</info>");
-        $output->writeln("<info>- A restore operation will overwrite and remove files & content from an existing instance.</info>");
-        $output->writeln("<info>- Any existing tables within the configured database will be dropped.</info>");
-        $output->writeln("<info>- You should only restore into an instance of the same or newer BookStack version.</info>");
-        $output->writeln("<info>- This command won't handle, restore or address any server configuration.</info>");
+        $output->writeln("<warn>Warning!</warn>");
+        $output->writeln("<warn>- A restore operation will overwrite and remove files & content from an existing instance.</warn>");
+        $output->writeln("<warn>- Any existing tables within the configured database will be dropped.</warn>");
+        $output->writeln("<warn>- You should only restore into an instance of the same or newer BookStack version.</warn>");
+        $output->writeln("<warn>- This command won't handle, restore or address any server configuration.</warn>");
 
         $appDir = AppLocator::require($input->getOption('app-directory'));
         $output->writeln("<info>Checking system requirements...</info>");
@@ -66,7 +66,7 @@ class RestoreCommand extends Command
         }
 
         $output->writeln("<info>The checked elements will be restored into [{$appDir}].</info>");
-        $output->writeln("<info>Existing content will be overwritten.</info>");
+        $output->writeln("<warn>Existing content will be overwritten.</warn>");
 
         if (!$interactions->confirm("Do you want to continue?")) {
             $output->writeln("<info>Stopping restore operation.</info>");
@@ -119,7 +119,7 @@ class RestoreCommand extends Command
         $output->writeln("<info>Cleaning up extract directory...</info>");
         $this->deleteDirectoryAndContents($extractDir);
 
-        $output->writeln("<info>\nRestore operation complete!</info>");
+        $output->writeln("<success>\nRestore operation complete!</success>");
 
         return Command::SUCCESS;
     }
index c8c6138d14d5a84fdd837f0084e7d864299c52ff..5475aaa9f0cc747199359023aeafb3c5ee75d024 100644 (file)
@@ -53,7 +53,7 @@ class UpdateCommand extends Command
         $artisan->run(['config:clear']);
         $artisan->run(['view:clear']);
 
-        $output->writeln("<info>Your BookStack instance at [{$appDir}] has been updated!</info>");
+        $output->writeln("<success>Your BookStack instance at [{$appDir}] has been updated!</success>");
 
         return Command::SUCCESS;
     }
index b4513b3e39aabf1c1fb9b3c262a02d688c7c1a26..58c5d1fcffe6aa5258def0559c8c2d306524c83b 100644 (file)
@@ -14,8 +14,8 @@ class ArtisanRunner
     public function run(array $commandArgs)
     {
         $errors = (new ProgramRunner('php', '/usr/bin/php'))
-            ->withTimeout(60)
-            ->withIdleTimeout(5)
+            ->withTimeout(600)
+            ->withIdleTimeout(600)
             ->withEnvironment(EnvironmentLoader::load($this->appDir))
             ->runCapturingAllOutput([
                 $this->appDir . DIRECTORY_SEPARATOR . 'artisan',
index f29c58859849e1395fb5dafb44be86f7f39f3838..fd95175374d5c6f3aaf6824e0e7456df5ce31e78 100644 (file)
@@ -15,7 +15,7 @@ class ComposerLocator
     {
         return (new ProgramRunner('composer', '/usr/local/bin/composer'))
             ->withTimeout(300)
-            ->withIdleTimeout(60)
+            ->withIdleTimeout(300)
             ->withAdditionalPathLocation($this->appDir);
     }
 
index 3263def5ac694a58a92acef7e43e18e58389b566..5ccd5f80ed0042385c58b5bdeb844da8b719e746 100644 (file)
@@ -38,6 +38,7 @@ class MySqlRunner
                 '-h', $this->host,
                 '-P', $this->port,
                 '-u', $this->user,
+                '--protocol=TCP',
                 $this->database,
                 '-e', "show tables;"
             ]);
@@ -55,6 +56,7 @@ class MySqlRunner
                 '-h', $this->host,
                 '-P', $this->port,
                 '-u', $this->user,
+                '--protocol=TCP',
                 $this->database,
                 '-e', "source {$sqlFilePath}"
             ]);
@@ -83,10 +85,11 @@ SET FOREIGN_KEY_CHECKS = 1;
 HEREDOC;
     }
 
-    public function runDumpToFile(string $filePath): void
+    public function runDumpToFile(string $filePath): string
     {
         $file = fopen($filePath, 'w');
         $errors = "";
+        $warnings = "";
         $hasOutput = false;
 
         try {
@@ -98,14 +101,22 @@ HEREDOC;
                     '-h', $this->host,
                     '-P', $this->port,
                     '-u', $this->user,
+                    '--protocol=TCP',
                     '--single-transaction',
                     '--no-tablespaces',
                     $this->database,
                 ], function ($data) use (&$file, &$hasOutput) {
                     fwrite($file, $data);
                     $hasOutput = true;
-                }, function ($error) use (&$errors) {
-                    $errors .= $error . "\n";
+                }, function ($error) use (&$errors, &$warnings) {
+                    $lines = explode("\n", $error);
+                    foreach ($lines as $line) {
+                        if (str_starts_with(strtolower($line), 'warning: ')) {
+                            $warnings .= $line;
+                        } else {
+                            $errors .= $line . "\n";
+                        }
+                    }
                 });
         } catch (\Exception $exception) {
             fclose($file);
@@ -124,6 +135,8 @@ HEREDOC;
         if ($errors) {
             throw new Exception("Failed mysqldump with errors:\n" . $errors);
         }
+
+        return $warnings;
     }
 
     public static function fromEnvOptions(array $env): static
index b05741049b85fd2a2a85ccd92814e42decb84375..734fe6c226849c322f4272159e940d5a088149dc 100644 (file)
@@ -6,6 +6,23 @@ use Exception;
 
 class RequirementsValidator
 {
+    protected static string $phpVersion = '8.0.2';
+    protected static array $extensions = [
+        'curl',
+        'dom',
+        'fileinfo',
+        'gd',
+        'iconv',
+        'libxml',
+        'mbstring',
+        'mysqlnd',
+        'pdo_mysql',
+        'session',
+        'simplexml',
+        'tokenizer',
+        'xml',
+    ];
+
     /**
      * Ensure the required PHP extensions are installed for this command.
      * @throws Exception
@@ -14,14 +31,13 @@ class RequirementsValidator
     {
         $errors = [];
 
-        if (version_compare(PHP_VERSION, '8.0.2') < 0) {
-            $errors[] = "PHP >= 8.0.2 is required to install BookStack.";
+        if (version_compare(PHP_VERSION, static::$phpVersion) < 0) {
+            $errors[] = sprintf("PHP >= %s is required to install BookStack.", static::$phpVersion);
         }
 
-        $requiredExtensions = ['curl', 'gd', 'iconv', 'libxml', 'mbstring', 'mysqlnd', 'xml'];
-        foreach ($requiredExtensions as $extension) {
+        foreach (static::$extensions as $extension) {
             if (!extension_loaded($extension)) {
-                $errors[] = "The \"{$extension}\" PHP extension is required by not active.";
+                $errors[] = "The \"{$extension}\" PHP extension is required but not active.";
             }
         }