use Cli\Services\EnvironmentLoader;
use Cli\Services\InteractiveConsole;
use Cli\Services\MySqlRunner;
+use Cli\Services\Paths;
use Cli\Services\ProgramRunner;
use Cli\Services\RequirementsValidator;
use Exception;
}
$output->writeln("<info>Extracting ZIP into temporary directory...</info>");
- $extractDir = $appDir . DIRECTORY_SEPARATOR . 'restore-temp-' . time();
+ $extractDir = Paths::join($appDir, 'restore-temp-' . time());
if (!mkdir($extractDir)) {
throw new CommandError("Could not create temporary extraction directory at [{$extractDir}].");
}
{
$oldEnv = EnvironmentLoader::load($extractDir);
$currentEnv = EnvironmentLoader::load($appDir);
- $envContents = file_get_contents($extractDir . DIRECTORY_SEPARATOR . '.env');
- $appEnvPath = $appDir . DIRECTORY_SEPARATOR . '.env';
+ $envContents = file_get_contents(Paths::join($extractDir, '.env'));
+ $appEnvPath = Paths::real(Paths::join($appDir, '.env'));
$mysqlCurrent = MySqlRunner::fromEnvOptions($currentEnv);
$mysqlOld = MySqlRunner::fromEnvOptions($oldEnv);
$returnData['new_url'] = $changedUrl;
}
- file_put_contents($appDir . DIRECTORY_SEPARATOR . '.env', $envContents);
+ file_put_contents($appEnvPath, $envContents);
return $returnData;
}
protected function restoreFolder(string $folderSubPath, string $appDir, string $extractDir): void
{
- $fullAppFolderPath = $appDir . DIRECTORY_SEPARATOR . $folderSubPath;
+ $fullAppFolderPath = Paths::real(Paths::join($appDir, $folderSubPath));
$this->deleteDirectoryAndContents($fullAppFolderPath);
- rename($extractDir . DIRECTORY_SEPARATOR . $folderSubPath, $fullAppFolderPath);
+ rename(Paths::join($extractDir, $folderSubPath), $fullAppFolderPath);
}
protected function deleteDirectoryAndContents(string $dir): void
protected function restoreDatabase(string $appDir, string $extractDir): void
{
- $dbDump = $extractDir . DIRECTORY_SEPARATOR . 'db.sql';
+ $dbDump = Paths::join($extractDir, 'db.sql');
$currentEnv = EnvironmentLoader::load($appDir);
$mysql = MySqlRunner::fromEnvOptions($currentEnv);
class Paths
{
+ /**
+ * Get the full real path, resolving symbolic links, to
+ * the existing file/directory of the given path.
+ * @throws \Exception
+ */
+ public static function real(string $path): string
+ {
+ $real = realpath($path);
+ if ($real === false) {
+ throw new \Exception("Path {$path} could not be resolved to a location on the filesystem");
+ }
+
+ return $real;
+ }
+
/**
* Join together the given path components.
* Does no resolving or cleaning.
$result = $this->runCommand('restore', [
'backup-zip' => $zipFile,
- ], [
- 'yes', '1'
- ]);
+ ], ['yes', '1']);
- $result->dumpError();
$result->assertSuccessfulExit();
$result->assertStdoutContains('✔ .env Config File');
$result->assertStdoutContains('✔ Themes Folder');
$mysql->query("DROP TABLE zz_testing;");
}
+ public function test_restore_with_symlinked_content_folders()
+ {
+ $zipFile = $this->buildZip(function (\ZipArchive $zip) {
+ $zip->addFromString('.env', "APP_KEY=abc123\nAPP_URL=https://example.com");
+ $zip->addFromString('public/uploads/test.txt', 'hello-public-uploads');
+ $zip->addFromString('storage/uploads/test.txt', 'hello-storage-uploads');
+ $zip->addFromString('themes/test.txt', 'hello-themes');
+ });
+
+ exec('cp -r /var/www/bookstack /var/www/bookstack-symlink-restore');
+ chdir('/var/www/bookstack-symlink-restore');
+ mkdir('/symlinks');
+
+ $symlinkPaths = ['public/uploads', 'storage/uploads', '.env', 'themes'];
+ foreach ($symlinkPaths as $path) {
+ $targetFile = str_replace('/', '-', $path);
+ $code = 0;
+ $output = null;
+ exec("mv /var/www/bookstack-symlink-restore/{$path} /symlinks/{$targetFile}", $output, $code);
+ exec("ln -s /symlinks/{$targetFile} /var/www/bookstack-symlink-restore/{$path}", $output, $code);
+ if ($code !== 0) {
+ $this->fail("Error when setting up symlinks");
+ }
+ }
+
+ $result = $this->runCommand('restore', [
+ 'backup-zip' => $zipFile,
+ ], ['yes', '1']);
+
+ $result->assertSuccessfulExit();
+
+ $this->assertStringEqualsFile('/var/www/bookstack-symlink-restore/public/uploads/test.txt', 'hello-public-uploads');
+ $this->assertStringEqualsFile('/var/www/bookstack-symlink-restore/storage/uploads/test.txt', 'hello-storage-uploads');
+ $this->assertStringEqualsFile('/var/www/bookstack-symlink-restore/themes/test.txt', 'hello-themes');
+
+ exec('rm -rf /var/www/bookstack-symlink-restore');
+ }
+
protected function buildZip(callable $builder): string
{
$zipFile = tempnam(sys_get_temp_dir(), 'cli-test');