]> BookStack Code Mirror - system-cli/blob - scripts/Commands/BackupCommand.php
2b303854a57cdc8e4282e6a55f69c921b905b224
[system-cli] / scripts / Commands / BackupCommand.php
1 <?php
2
3 namespace Cli\Commands;
4
5 use Minicli\Command\CommandCall;
6 use RecursiveDirectoryIterator;
7 use Symfony\Component\Process\ExecutableFinder;
8 use Symfony\Component\Process\Process;
9 use ZipArchive;
10
11 final class BackupCommand
12 {
13     public function __construct(
14         protected string $appDir
15     ) {
16     }
17
18     public function handle(CommandCall $input)
19     {
20         // TODO - Customizable output file
21         // TODO - Database only command
22         // TODO - Validate DB vars
23         // TODO - Error handle each stage
24         // TODO - Validate zip (and any other extensions required) are active.
25
26         $zipOutFile = getcwd() . DIRECTORY_SEPARATOR . 'backup.zip';
27
28         $dbHost = ($_SERVER['DB_HOST'] ?? '');
29         $dbUser = ($_SERVER['DB_USERNAME'] ?? '');
30         $dbPass = ($_SERVER['DB_PASSWORD'] ?? '');
31         $dbDatabase = ($_SERVER['DB_DATABASE'] ?? '');
32
33         // Create a mysqldump for the BookStack database
34         $executableFinder = new ExecutableFinder();
35         $mysqldumpPath = $executableFinder->find('mysqldump');
36
37         $process = new Process([
38             $mysqldumpPath,
39             '-h', $dbHost,
40             '-u', $dbUser,
41             '-p' . $dbPass,
42             '--single-transaction',
43             '--no-tablespaces',
44             $dbDatabase,
45         ]);
46         $process->start();
47
48         $errors = "";
49         $dumpTempFile = tempnam(sys_get_temp_dir(), 'bsbackup');
50         $dumpTempFileResource = fopen($dumpTempFile, 'w');
51         foreach ($process as $type => $data) {
52             if ($process::OUT === $type) {
53                 fwrite($dumpTempFileResource, $data);
54             } else { // $process::ERR === $type
55                 $errors .= $data . "\n";
56             }
57         }
58         fclose($dumpTempFileResource);
59
60
61         // Create a new ZIP file
62         $zipTempFile = tempnam(sys_get_temp_dir(), 'bsbackup');
63         $zip = new ZipArchive();
64         $sep = DIRECTORY_SEPARATOR;
65         $zip->open($zipTempFile, ZipArchive::CREATE);
66         $zip->addFile($this->appDir . $sep . '.env', '.env');
67         $zip->addFile($dumpTempFile, 'db.sql');
68
69         $fileDirs = [
70             $this->appDir . $sep . 'public' . $sep . 'uploads' => 'public/uploads',
71             $this->appDir . $sep . 'storage' . $sep . 'uploads' => 'storage/uploads',
72         ];
73
74         foreach ($fileDirs as $fullFileDir => $relativeFileDir) {
75             $dirIter = new RecursiveDirectoryIterator($fullFileDir);
76             $fileIter = new \RecursiveIteratorIterator($dirIter);
77             /** @var \SplFileInfo $file */
78             foreach ($fileIter as $file) {
79                 if (!$file->isDir()) {
80                     $zip->addFile($file->getPathname(), $relativeFileDir . '/' . $fileIter->getSubPathname());
81                 }
82             }
83         }
84
85         // Close off our zip and move it to the required location
86         $zip->close();
87         rename($zipTempFile, $zipOutFile);
88
89         // Delete our temporary DB dump file
90         unlink($dumpTempFile);
91
92         // Announce end and display errors
93         echo "Finished";
94         if ($errors) {
95             echo " with the following errors:\n" . $errors;
96         }
97     }
98 }