diff --git a/README.md b/README.md index d61f2c6..72078ae 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,32 @@ # Laravel Driver for the Database Backup Manager -This package pulls in the framework agnostic [Backup Manager](https://github.com/backup-manager/backup-manager) and provides seamless integration with **Laravel**. +This package pulls in the framework agnostic [Backup Manager](https://github.com/backup-manager/backup-manager) and provides seamless integration with **Laravel**. -Mitchell has put together a [video tour](https://www.youtube.com/watch?v=vWXy0R8OavM) of Laravel integration, to give you an idea what is possible with this package. +[Watch a video tour](https://www.youtube.com/watch?v=vWXy0R8OavM) to get an idea what is possible with this package. -> Note: This package is for Laravel integration only. For information about the base package please see [the base package repository](https://github.com/backup-manager/backup-manager). +> Note: This package is for Laravel integration only. For information about the framework-agnostic core package (or the Symfony driver) please see [the base package repository](https://github.com/backup-manager/backup-manager). ### Table of Contents - [Stability Notice](#stability-notice) - [Requirements](#requirements) - [Installation](#installation) -- [Usage](#usage) +- [Scheduling Backups](#scheduling-backups) - [Contribution Guidelines](#contribution-guidelines) - [Maintainers](#maintainers) - [License](#license) +- [Changelog](#changelog) ### Stability Notice It's stable enough, you'll need to understand filesystem permissions. -This package is actively being developed and we would like to get feedback to improve it. [Please feel free to submit feedback.](https://github.com/backup-manager/laravel/issues/new) +This package is being actively developed, and we would like to get feedback to improve it. [Please feel free to submit feedback.](https://github.com/backup-manager/laravel/issues/new) ### Requirements -- PHP 5.5 -- Laravel +- PHP 7.3+ +- Laravel 5.5+ - MySQL support requires `mysqldump` and `mysql` command-line binaries - PostgreSQL support requires `pg_dump` and `psql` command-line binaries - Gzip support requires `gzip` and `gunzip` command-line binaries @@ -43,41 +44,56 @@ composer require backup-manager/laravel Then, you'll need to select the appropriate packages for the adapters that you want to use. ```shell -# to support s3 -composer require league/flysystem-aws-s3-v2 +# to support s3 or google cs +composer require league/flysystem-aws-s3-v3 # to support dropbox -composer require league/flysystem-dropbox +composer require srmklive/flysystem-dropbox-v2 # to support rackspace composer require league/flysystem-rackspace # to support sftp composer require league/flysystem-sftp + +# to support gcs +composer require superbalist/flysystem-google-storage ``` -#### Laravel Configuration +#### Laravel 5 Configuration -To install into a Laravel project, first do the composer install then add **one of the following classes** to your config/app.php service providers list. +To install into a Laravel project, first do the composer install then add *ONE *of the following classes to your config/app.php service providers list. ```php -BackupManager\Laravel\Laravel4BackupManagerServiceProvider::class, +// FOR LARAVEL 5.5 + +BackupManager\Laravel\Laravel55ServiceProvider::class, ``` -or +Publish the storage configuration file. ```php -BackupManager\Laravel\Laravel5BackupManagerServiceProvider::class, +php artisan vendor:publish --provider="BackupManager\Laravel\Laravel55ServiceProvider" ``` -Copy the `vendor/backup-manager/laravel/config/backup-manager.php` file to `app/config/backup-manager.php` and configure it to suit your needs. +The Backup Manager will make use of Laravel's database configuration. But, it won't know about any connections that might be tied to other environments, so it can be best to just list multiple connections in the `config/database.php` file. + +We can also add extra parameters on our backup manager commands by configuring extra params on `.env` file: -If you are using Laravel 5 you can run this command to auto-publish the config file: -```php -php artisan vendor:publish --provider="BackupManager\Laravel\Laravel5BackupManagerServiceProvider" +``` +BACKUP_MANAGER_EXTRA_PARAMS="--column-statistics=0 --max-allowed-packet" ``` -The Backup Manager will make use of Laravel's database configuration. But, it won't know about any connections that might be tied to other environments, so it can be best to just list multiple connections in the `config/database.php` file. +#### Lumen Configuration + +To install into a Lumen project, first do the composer install then add the configuration file loader and *ONE* of the following service providers to your `bootstrap/app.php`. + +```php +// FOR LUMEN 5.5 AND ABOVE +$app->configure('backup-manager'); +$app->register(BackupManager\Laravel\Lumen55ServiceProvider::class); +``` + +Copy the `vendor/backup-manager/laravel/config/backup-manager.php` file to `config/backup-manager.php` and configure it to suit your needs. **IoC Resolution** @@ -103,6 +119,33 @@ There are three commands available `db:backup`, `db:restore` and `db:list`. All will prompt you with simple questions to successfully execute the command. +**Example Command for 24hour scheduled cronjob** + +``` +php artisan db:backup --database=mysql --destination=dropbox --destinationPath=project --timestamp="d-m-Y" --compression=gzip +``` + +This command will backup your database to dropbox using mysql and gzip compresion in path /backups/project/DATE.gz (ex: /backups/project/31-7-2015.gz) + +### Scheduling Backups + +It's possible to schedule backups using Laravel's scheduler. + +```PHP +/** + * Define the application's command schedule. + * + * @param \Illuminate\Console\Scheduling\Schedule $schedule + * @return void + */ + protected function schedule(Schedule $schedule) { + $environment = config('app.env'); + $schedule->command( + "db:backup --database=mysql --destination=s3 --destinationPath=/{$environment}/projectname --timestamp="Y_m_d_H_i_s" --compression=gzip" + )->twiceDaily(13,21); + } +``` + ### Contribution Guidelines We recommend using the vagrant configuration supplied with this package for development and contribution. Simply install VirtualBox, Vagrant, and Ansible then run `vagrant up` in the root folder. A virtualmachine specifically designed for development of the package will be built and launched for you. @@ -118,8 +161,18 @@ When contributing please consider the following guidelines: ### Maintainers -This package is maintained by [Shawn McCool](http://shawnmc.cool) and [Mitchell van Wijngaarden](http://kooding.nl). +This package is maintained by [Shawn McCool](http://shawnmc.cool) and [open-source heroes](https://github.com/backup-manager/laravel/pulls?q=is%3Apr+is%3Aclosed). ### License This package is licensed under the [MIT license](https://github.com/backup-manager/laravel/blob/master/LICENSE). + +### Changelog + +**2.0** + +_Released on 2020-04-30_ + +Remove support for all Laravel versions below 5.5. All older versions should use the backup-manager `^1.0`. + +Since so many dependencies in Laravel / Symfony have changed it became impossible to support newer versions in the same code-base. Release `^1.0` is stable and is always accepting new stability fixes (we haven't seen anything to fix in a long time). diff --git a/composer.json b/composer.json index d39c2ff..89f95b3 100644 --- a/composer.json +++ b/composer.json @@ -5,21 +5,16 @@ { "name": "Shawn McCool", "email": "shawn@heybigname.com", - "homepage": "http://heybigname.com/" - }, - { - "name": "Mitchell van Wijngaarden", - "email": "mitchell@kooding.nl", - "homepage": "http://heybigname.com/" + "homepage": "https://shawnmc.cool/" } ], "require": { - "backup-manager/backup-manager": "^1.0", - "php": "^5.5.0", - "symfony/process": "^2.0", - "illuminate/support": "^4.0||^5.0", - "illuminate/container": "^4.0||^5.0", - "illuminate/console": "^4.0||^5.0" + "backup-manager/backup-manager": "^3.0", + "php": "^7.3||^7.4", + "symfony/process": "^3||^4||^5", + "illuminate/support": "^5.5||^6||^7", + "illuminate/container": "^5.5||^6||^7", + "illuminate/console": "^5.5||^6||^7" }, "require-dev": { "mockery/mockery": "dev-master", @@ -33,6 +28,15 @@ "BackupManager\\Laravel\\": "src/" } }, - "license": "MIT", - "minimum-stability": "dev" + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "laravel": { + "providers": [ + "BackupManager\\Laravel\\Laravel55ServiceProvider" + ] + } + }, + "license": "MIT" } diff --git a/config/backup-manager.php b/config/backup-manager.php index f5a1167..f17036b 100644 --- a/config/backup-manager.php +++ b/config/backup-manager.php @@ -7,12 +7,20 @@ ], 's3' => [ 'type' => 'AwsS3', - 'key' => '', - 'secret' => '', - 'region' => 'us-east-1', - 'bucket' => '', + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'region' => env('AWS_DEFAULT_REGION'), + 'bucket' => env('AWS_BUCKET'), 'root' => '', ], + 'gcs' => [ + 'type' => 'Gcs', + 'project' => env('GOOGLE_CLOUD_PROJECT_ID', 'your-project-id'), + 'keyFilePath' => env('GOOGLE_CLOUD_KEY_FILE', null), + 'bucket' => env('GOOGLE_CLOUD_STORAGE_BUCKET', 'your-bucket'), + 'root' => '', + 'prefix' => env('GOOGLE_CLOUD_STORAGE_PATH_PREFIX', null), + ], 'rackspace' => [ 'type' => 'Rackspace', 'username' => '', @@ -23,7 +31,7 @@ 'root' => '', ], 'dropbox' => [ - 'type' => 'Dropbox', + 'type' => 'DropboxV2', 'token' => '', 'key' => '', 'secret' => '', @@ -51,4 +59,7 @@ 'privateKey' => '', 'root' => '', ], + + // Add additional options to dump-command (like '--max-allowed-packet') + 'command-extra-params' => env('BACKUP_MANAGER_EXTRA_PARAMS'), ]; diff --git a/src/AutoComplete.php b/src/AutoComplete.php new file mode 100644 index 0000000..d215530 --- /dev/null +++ b/src/AutoComplete.php @@ -0,0 +1,63 @@ +useSymfontDialog($dialog, $list, $default, $validation); + } catch (InvalidArgumentException $error) { + // + } + return $this->useSymfonyQuestion($dialog, $default, $validation); + } + + /** + * @param $dialog + * @param array $list + * @param null $default + * @return mixed + */ + protected function useSymfontDialog($dialog, array $list, $default = null, $validation) { + $helper = $this->getHelperSet()->get('dialog'); + + return $helper->askAndValidate( + $this->output, "{$dialog}", $validation, false, $default, $list + ); + } + + /** + * @param $dialog + * @param null $default + * @return mixed + */ + protected function useSymfonyQuestion($dialog, $default = null, $validation) { + $question = new Question($dialog . ' ', $default); + $question->setValidator($validation); + $helper = $this->getHelper('question'); + + return $helper->ask($this->input, $this->output, $question); + } +} diff --git a/src/BaseCommand.php b/src/BaseCommand.php deleted file mode 100644 index 5c26eff..0000000 --- a/src/BaseCommand.php +++ /dev/null @@ -1,44 +0,0 @@ -getHelperSet()->get('dialog'); - return $helper->askAndValidate($this->output, "{$dialog}", $validation, false, $default, $list); - } - - /** - * @param array $headers - * @param array $rows - * @internal param string $style - * @return void - */ - public function table(array $headers, array $rows, $style = 'default') { - $table = $this->getHelperSet()->get('table'); - $table->setHeaders($headers); - $table->setRows($rows); - $table->render($this->output); - } -} diff --git a/src/DbBackupCommand.php b/src/DbBackupCommand.php index f6701ee..e89ca28 100644 --- a/src/DbBackupCommand.php +++ b/src/DbBackupCommand.php @@ -1,5 +1,7 @@ timestamp = date($this->option('timestamp')); + if ($this->isMissingArguments()) { $this->displayMissingArguments(); $this->promptForMissingArgumentValues(); $this->validateArguments(); } + $destinations = [ + new Destination( + $this->option('destination'), + $this->option('destinationPath') . $this->timestamp + ) + ]; + $this->info('Dumping database and uploading...'); $this->backupProcedure->run( $this->option('database'), - $this->option('destination'), - $this->option('destinationPath'), + $destinations, $this->option('compression') ); @@ -97,7 +115,7 @@ public function fire() { $this->option('database'), $this->option('compression'), $this->option('destination'), - $root .'/'. $this->option('destinationPath') + $destinations[0]->destinationPath() )); } @@ -182,7 +200,7 @@ private function validateArguments() { $this->info(sprintf('Do you want to create a backup of %s, store it on %s at %s and compress it to %s?', $this->option('database'), $this->option('destination'), - $root . $this->option('destinationPath'), + $root . $this->option('destinationPath') . $this->timestamp, $this->option('compression') )); $this->line(''); @@ -215,6 +233,7 @@ protected function getOptions() { ['destination', null, InputOption::VALUE_OPTIONAL, 'Destination configuration name', null], ['destinationPath', null, InputOption::VALUE_OPTIONAL, 'File destination path', null], ['compression', null, InputOption::VALUE_OPTIONAL, 'Compression type', null], + ['timestamp', null, InputOption::VALUE_OPTIONAL, 'Append timestamp to filename', null], ]; } } diff --git a/src/DbListCommand.php b/src/DbListCommand.php index a6a272e..9bbcbe9 100644 --- a/src/DbListCommand.php +++ b/src/DbListCommand.php @@ -1,9 +1,15 @@ filesystems = $filesystems; @@ -67,7 +76,7 @@ public function fire() { if ($file['type'] == 'dir') continue; $rows[] = [ $file['basename'], - $file['extension'], + key_exists('extension', $file) ? $file['extension'] : null, $this->formatBytes($file['size']), date('D j Y H:i:s', $file['timestamp']) ]; @@ -111,6 +120,9 @@ private function promptForMissingArgumentValues() { } } + /** + * + */ private function askSource() { $providers = $this->filesystems->getAvailableProviders(); $formatted = implode(', ', $providers); @@ -119,6 +131,9 @@ private function askSource() { $this->input->setOption('source', $source); } + /** + * + */ private function askPath() { $root = $this->filesystems->getConfig($this->option('source'), 'root'); $path = $this->ask("From which path? {$root}"); @@ -166,6 +181,11 @@ protected function getOptions() { ]; } + /** + * @param $bytes + * @param int $precision + * @return string + */ private function formatBytes($bytes, $precision = 2) { $units = ['B', 'KB', 'MB', 'GB', 'TB']; $bytes = max($bytes, 0); diff --git a/src/DbRestoreCommand.php b/src/DbRestoreCommand.php index e2e6518..ead624e 100644 --- a/src/DbRestoreCommand.php +++ b/src/DbRestoreCommand.php @@ -1,11 +1,17 @@ filesystems->getAvailableProviders(); $formatted = implode(', ', $providers); @@ -137,6 +146,9 @@ private function askSource() { $this->input->setOption('source', $source); } + /** + * + */ private function askSourcePath() { // ask path $root = $this->filesystems->getConfig($this->option('source'), 'root'); @@ -164,7 +176,7 @@ private function askSourcePath() { if ($file['type'] == 'dir') continue; $rows[] = [ $file['basename'], - $file['extension'], + key_exists('extension', $file) ? $file['extension'] : null, $this->formatBytes($file['size']), date('D j Y H:i:s', $file['timestamp']) ]; @@ -175,6 +187,9 @@ private function askSourcePath() { $this->input->setOption('sourcePath', "{$path}/{$filename}"); } + /** + * + */ private function askDatabase() { $providers = $this->databases->getAvailableProviders(); $formatted = implode(', ', $providers); @@ -183,6 +198,9 @@ private function askDatabase() { $this->input->setOption('database', $database); } + /** + * + */ private function askCompression() { $types = ['null', 'gzip']; $formatted = implode(', ', $types); @@ -236,6 +254,11 @@ protected function getOptions() { ]; } + /** + * @param $bytes + * @param int $precision + * @return string + */ private function formatBytes($bytes, $precision = 2) { $units = ['B', 'KB', 'MB', 'GB', 'TB']; $bytes = max($bytes, 0); diff --git a/src/GetDatabaseConfig.php b/src/GetDatabaseConfig.php new file mode 100644 index 0000000..dd0eb77 --- /dev/null +++ b/src/GetDatabaseConfig.php @@ -0,0 +1,46 @@ + $connection['driver'], + 'host' => $connection['host'], + 'port' => $port, + 'user' => $connection['username'], + 'pass' => $connection['password'], + 'database' => $connection['database'], + 'ignoreTables' => $connection['driver'] === 'mysql' && isset($connection['ignoreTables']) + ? $connection['ignoreTables'] : null, + 'extraParams' => config('backup-manager.command-extra-params'), + ]; + }, $connections); + return new Config($mapped); + } +} diff --git a/src/Laravel55Compatibility.php b/src/Laravel55Compatibility.php new file mode 100644 index 0000000..6ebc2e3 --- /dev/null +++ b/src/Laravel55Compatibility.php @@ -0,0 +1,36 @@ +getHelperSet()->get('table'); + } catch (InvalidArgumentException $error) { + // + } finally { + $table = new Table($this->output); + } + + $table->setHeaders($headers); + $table->setRows($rows); + $table->render($this->output); + } + + public function handle() + { + $this->fire(); + } +} diff --git a/src/Laravel55DbBackupCommand.php b/src/Laravel55DbBackupCommand.php new file mode 100644 index 0000000..fba0c69 --- /dev/null +++ b/src/Laravel55DbBackupCommand.php @@ -0,0 +1,11 @@ +app->bind(\BackupManager\Filesystems\FilesystemProvider::class, function ($app) { $provider = new Filesystems\FilesystemProvider(new Config($app['config']['backup-manager'])); $provider->add(new Filesystems\Awss3Filesystem); + $provider->add(new Filesystems\GcsFilesystem); $provider->add(new Filesystems\DropboxFilesystem); + $provider->add(new Filesystems\DropboxV2Filesystem); $provider->add(new Filesystems\FtpFilesystem); $provider->add(new Filesystems\LocalFilesystem); $provider->add(new Filesystems\RackspaceFilesystem); @@ -94,7 +97,7 @@ private function registerCompressorProvider() { */ private function registerShellProcessor() { $this->app->bind(\BackupManager\ShellProcessing\ShellProcessor::class, function () { - return new ShellProcessor(new Process('')); + return new ShellProcessor(new Process([], null, null, null, null)); }); } @@ -105,9 +108,9 @@ private function registerShellProcessor() { */ private function registerArtisanCommands() { $this->commands([ - \BackupManager\Laravel\DbBackupCommand::class, - \BackupManager\Laravel\DbRestoreCommand::class, - \BackupManager\Laravel\DbListCommand::class, + \BackupManager\Laravel\Laravel55DbBackupCommand::class, + \BackupManager\Laravel\Laravel55DbRestoreCommand::class, + \BackupManager\Laravel\Laravel55DbListCommand::class, ]); } @@ -123,32 +126,4 @@ public function provides() { \BackupManager\ShellProcessing\ShellProcessor::class, ]; } - - private function getDatabaseConfig($connections) { - $mapped = array_map(function ($connection) { - if ( ! in_array($connection['driver'], ['mysql', 'pgsql'])) { - return; - } - - if (isset($connection['port'])) { - $port = $connection['port']; - } else { - if ($connection['driver'] == 'mysql') { - $port = '3306'; - } elseif ($connection['driver'] == 'pgsql') { - $port = '5432'; - } - } - - return [ - 'type' => $connection['driver'], - 'host' => $connection['host'], - 'port' => $port, - 'user' => $connection['username'], - 'pass' => $connection['password'], - 'database' => $connection['database'], - ]; - }, $connections); - return new Config($mapped); - } } diff --git a/src/Laravel4BackupManagerServiceProvider.php b/src/Lumen55ServiceProvider.php similarity index 68% rename from src/Laravel4BackupManagerServiceProvider.php rename to src/Lumen55ServiceProvider.php index 9de58f1..3f88700 100644 --- a/src/Laravel4BackupManagerServiceProvider.php +++ b/src/Lumen55ServiceProvider.php @@ -12,25 +12,19 @@ * Class BackupManagerServiceProvider * @package BackupManager\Laravel */ -class Laravel4BackupManagerServiceProvider extends ServiceProvider { +class Lumen55ServiceProvider extends ServiceProvider { + use GetDatabaseConfig; protected $defer = true; - /** - * Bootstrap the application events. - * - * @return void - */ - public function boot() { - $this->package('backup-manager/laravel', 'backup-manager', realpath(app_path("config"))); - } - /** * Register the service provider. * * @return void */ public function register() { + $configPath = __DIR__ . '/../config/backup-manager.php'; + $this->mergeConfigFrom($configPath, 'backup-manager'); $this->registerFilesystemProvider(); $this->registerDatabaseProvider(); $this->registerCompressorProvider(); @@ -45,9 +39,10 @@ public function register() { */ private function registerFilesystemProvider() { $this->app->bind(\BackupManager\Filesystems\FilesystemProvider::class, function ($app) { - $provider = new Filesystems\FilesystemProvider(new Config($app['config']['backup-manager::storage'])); + $provider = new Filesystems\FilesystemProvider(new Config($app['config']['backup-manager'])); $provider->add(new Filesystems\Awss3Filesystem); $provider->add(new Filesystems\DropboxFilesystem); + $provider->add(new Filesystems\DropboxV2Filesystem); $provider->add(new Filesystems\FtpFilesystem); $provider->add(new Filesystems\LocalFilesystem); $provider->add(new Filesystems\RackspaceFilesystem); @@ -102,9 +97,9 @@ private function registerShellProcessor() { */ private function registerArtisanCommands() { $this->commands([ - \BackupManager\Laravel\DbBackupCommand::class, - \BackupManager\Laravel\DbRestoreCommand::class, - \BackupManager\Laravel\DbListCommand::class + \BackupManager\Laravel\Laravel55DbBackupCommand::class, + \BackupManager\Laravel\Laravel55DbRestoreCommand::class, + \BackupManager\Laravel\Laravel55DbListCommand::class, ]); } @@ -120,32 +115,4 @@ public function provides() { \BackupManager\ShellProcessing\ShellProcessor::class, ]; } - - private function getDatabaseConfig($connections) { - $mapped = array_map(function ($connection) { - if ( ! in_array($connection['driver'], ['mysql', 'pgsql'])) { - return; - } - - if (isset($connection['port'])) { - $port = $connection['port']; - } else { - if ($connection['driver'] == 'mysql') { - $port = '3306'; - } elseif ($connection['driver'] == 'pgsql') { - $port = '5432'; - } - } - - return [ - 'type' => $connection['driver'], - 'host' => $connection['host'], - 'port' => $port, - 'user' => $connection['username'], - 'pass' => $connection['password'], - 'database' => $connection['database'], - ]; - }, $connections); - return new Config($mapped); - } }