Browse Source

[FEATURE] Add backup commands and library

quickform
Adam Bukowski 4 years ago
parent
commit
220a9f6409
8 changed files with 453 additions and 48 deletions
  1. +2
    -1
      composer.json
  2. +178
    -19
      composer.lock
  3. +4
    -0
      console.php
  4. +57
    -0
      console/Backup/BackupAllCommand.php
  5. +33
    -0
      console/Backup/BackupDbCommand.php
  6. +56
    -0
      console/Backup/BackupFilesCommand.php
  7. +30
    -0
      console/Backup/ListBackupsCommand.php
  8. +93
    -28
      include/backups.php

+ 2
- 1
composer.json View File

@@ -4,7 +4,8 @@
"twig/twig": "^1.24",
"symfony/console": "^2.7",
"symfony/http-foundation": "^2.7",
"phpdocumentor/reflection-docblock": "^2.0.4"
"phpdocumentor/reflection-docblock": "^2.0.4",
"ifsnop/mysqldump-php": "dev-master"
},
"scripts": {
"post-install-cmd": [


+ 178
- 19
composer.lock View File

@@ -4,10 +4,62 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "7cd00f28e2bbb91494283f0a36e59011",
"content-hash": "3d82e3f1b1f0912e248a46bf24027027",
"hash": "f12042cde6cb5c0fd6e20ca44c78c120",
"content-hash": "2b34029e9942b85f5b24fc42bfdbbd3f",
"packages": [
{
"name": "ifsnop/mysqldump-php",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/ifsnop/mysqldump-php.git",
"reference": "c225512de8a2dd9aa6bbadf603d05a4e027550c1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ifsnop/mysqldump-php/zipball/c225512de8a2dd9aa6bbadf603d05a4e027550c1",
"reference": "c225512de8a2dd9aa6bbadf603d05a4e027550c1",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"require-dev": {
"phpunit/phpunit": "3.7.*",
"squizlabs/php_codesniffer": "1.*"
},
"type": "library",
"autoload": {
"psr-4": {
"Ifsnop\\": "src/Ifsnop/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Diego Torres",
"homepage": "https://github.com/ifsnop",
"role": "Developer"
}
],
"description": "This is a php version of linux's mysqldump in terminal \"$ mysqldump -u username -p...\"",
"homepage": "https://github.com/ifsnop/mysqldump-php",
"keywords": [
"backup",
"database",
"dump",
"export",
"mysql",
"mysqldump",
"pdo",
"sqlite"
],
"time": "2016-10-13 21:44:06"
},
{
"name": "ircmaxell/password-compat",
"version": "v1.0.4",
"source": {
@@ -145,21 +197,69 @@
"time": "2015-09-11 15:10:35"
},
{
"name": "psr/log",
"version": "1.0.2",
"source": {
"type": "git",
"url": "https://github.com/php-fig/log.git",
"reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
"reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Log\\": "Psr/Log/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common interface for logging libraries",
"homepage": "https://github.com/php-fig/log",
"keywords": [
"log",
"psr",
"psr-3"
],
"time": "2016-10-10 12:19:37"
},
{
"name": "symfony/console",
"version": "v2.8.11",
"version": "v2.8.12",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "3d3e4fa5f0614c8e45220e5de80332322e33bd90"
"reference": "d7a5a88178f94dcc29531ea4028ea614e35452d4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/3d3e4fa5f0614c8e45220e5de80332322e33bd90",
"reference": "3d3e4fa5f0614c8e45220e5de80332322e33bd90",
"url": "https://api.github.com/repos/symfony/console/zipball/d7a5a88178f94dcc29531ea4028ea614e35452d4",
"reference": "d7a5a88178f94dcc29531ea4028ea614e35452d4",
"shasum": ""
},
"require": {
"php": ">=5.3.9",
"symfony/debug": "~2.7,>=2.7.2|~3.0.0",
"symfony/polyfill-mbstring": "~1.0"
},
"require-dev": {
@@ -202,20 +302,77 @@
],
"description": "Symfony Console Component",
"homepage": "https://symfony.com",
"time": "2016-09-06 10:55:00"
"time": "2016-09-28 00:10:16"
},
{
"name": "symfony/debug",
"version": "v3.0.9",
"source": {
"type": "git",
"url": "https://github.com/symfony/debug.git",
"reference": "697c527acd9ea1b2d3efac34d9806bf255278b0a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/debug/zipball/697c527acd9ea1b2d3efac34d9806bf255278b0a",
"reference": "697c527acd9ea1b2d3efac34d9806bf255278b0a",
"shasum": ""
},
"require": {
"php": ">=5.5.9",
"psr/log": "~1.0"
},
"conflict": {
"symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2"
},
"require-dev": {
"symfony/class-loader": "~2.8|~3.0",
"symfony/http-kernel": "~2.8|~3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.0-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\Debug\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Debug Component",
"homepage": "https://symfony.com",
"time": "2016-07-30 07:22:48"
},
{
"name": "symfony/http-foundation",
"version": "v2.8.11",
"version": "v2.8.12",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-foundation.git",
"reference": "1d4ab8de2215e44e57fddc1e6b5d122546769e7d"
"reference": "91f87d27e9fe99435278c337375b0dce292fe0e2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/1d4ab8de2215e44e57fddc1e6b5d122546769e7d",
"reference": "1d4ab8de2215e44e57fddc1e6b5d122546769e7d",
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/91f87d27e9fe99435278c337375b0dce292fe0e2",
"reference": "91f87d27e9fe99435278c337375b0dce292fe0e2",
"shasum": ""
},
"require": {
@@ -257,7 +414,7 @@
],
"description": "Symfony HttpFoundation Component",
"homepage": "https://symfony.com",
"time": "2016-09-06 10:55:00"
"time": "2016-09-21 19:04:07"
},
{
"name": "symfony/polyfill-mbstring",
@@ -434,16 +591,16 @@
},
{
"name": "twig/twig",
"version": "v1.24.2",
"version": "v1.26.1",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
"reference": "33093f6e310e6976baeac7b14f3a6ec02f2d79b7"
"reference": "a09d8ee17ac1cfea29ed60c83960ad685c6a898d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/33093f6e310e6976baeac7b14f3a6ec02f2d79b7",
"reference": "33093f6e310e6976baeac7b14f3a6ec02f2d79b7",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/a09d8ee17ac1cfea29ed60c83960ad685c6a898d",
"reference": "a09d8ee17ac1cfea29ed60c83960ad685c6a898d",
"shasum": ""
},
"require": {
@@ -456,7 +613,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.24-dev"
"dev-master": "1.26-dev"
}
},
"autoload": {
@@ -491,13 +648,15 @@
"keywords": [
"templating"
],
"time": "2016-09-01 17:50:53"
"time": "2016-10-05 18:57:41"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"stability-flags": {
"ifsnop/mysqldump-php": 20
},
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],


+ 4
- 0
console.php View File

@@ -22,4 +22,8 @@ $application->add(new \Epesi\Console\Maintenance\MaintenanceOnCommand());
$application->add(new \Epesi\Console\Maintenance\MaintenanceOffCommand());
$application->add(new \Epesi\Console\SearchClearCommand());
$application->add(new \Epesi\Console\SearchIndexCommand());
$application->add(new \Epesi\Console\Backup\BackupDbCommand());
$application->add(new \Epesi\Console\Backup\BackupFilesCommand());
$application->add(new \Epesi\Console\Backup\BackupAllCommand());
$application->add(new \Epesi\Console\Backup\ListBackupsCommand());
$application->run();

+ 57
- 0
console/Backup/BackupAllCommand.php View File

@@ -0,0 +1,57 @@
<?php

namespace Epesi\Console\Backup;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

class BackupAllCommand extends Command
{
protected function configure()
{
$this
->setName('backup:all')
->setDescription('Backup EPESI files with database snapshot')
->addArgument(
'file',
InputArgument::OPTIONAL,
'file or directory to backup'
)
->addOption(
'output', 'o',
InputOption::VALUE_REQUIRED,
'output file'
)
->addOption(
'force', 'f',
InputOption::VALUE_NONE,
'force overwrite backup file'
)
;
}

protected function execute(InputInterface $input, OutputInterface $output)
{
$file = $input->getArgument('file');
$file_description = 'Only ' . $file;
if (!$file) {
$file = '.';
$file_description = 'All files';
}
$output_file = $input->getOption('output');
$overwrite = $input->getOption('force') ? true : false;
$st = new SymfonyStyle($input, $output);

require_once 'include/backups.php';
$util = \BackupUtil::default_instance();
$description = "EPESI ver " . EPESI_VERSION . " rev " . EPESI_REVISION . ' - ' . $file_description . ' with database snapshot';
$backup = $util->create_backup($file, $description, $output_file, $overwrite);
$util->addDbBackup($backup);
$st->writeln('Created backup:');
$st->writeln(sprintf("<fg=yellow>[%s]</> <fg=green>%s</> (File: %s)", $backup->get_date('Y-m-d H:i:s'), $backup->get_description(), $backup->get_file()));
}
}

+ 33
- 0
console/Backup/BackupDbCommand.php View File

@@ -0,0 +1,33 @@
<?php

namespace Epesi\Console\Backup;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

class BackupDbCommand extends Command
{
protected function configure()
{
$this
->setName('backup:db')
->setDescription('Backup database')
->addArgument(
'file',
InputArgument::REQUIRED,
'backup filename'
);
}

protected function execute(InputInterface $input, OutputInterface $output)
{
$st = new SymfonyStyle($input, $output);
$file = $input->getArgument('file');
require_once 'include/backups.php';
\BackupUtil::backup_db($file);
$st->writeln('done');
}
}

+ 56
- 0
console/Backup/BackupFilesCommand.php View File

@@ -0,0 +1,56 @@
<?php

namespace Epesi\Console\Backup;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

class BackupFilesCommand extends Command
{
protected function configure()
{
$this
->setName('backup:files')
->setDescription('Backup EPESI files')
->addArgument(
'file',
InputArgument::OPTIONAL,
'file or directory to backup'
)
->addOption(
'output', 'o',
InputOption::VALUE_REQUIRED,
'output file'
)
->addOption(
'force', 'f',
InputOption::VALUE_NONE,
'force overwrite backup file'
)
;
}

protected function execute(InputInterface $input, OutputInterface $output)
{
$file = $input->getArgument('file');
$file_description = 'Only ' . $file;
if (!$file) {
$file = '.';
$file_description = 'All files';
}
$output_file = $input->getOption('output');
$overwrite = $input->getOption('force') ? true : false;
$st = new SymfonyStyle($input, $output);

require_once 'include/backups.php';
$util = \BackupUtil::default_instance();
$description = "EPESI ver " . EPESI_VERSION . " rev " . EPESI_REVISION . ' - ' . $file_description;
$backup = $util->create_backup($file, $description, $output_file, $overwrite);
$st->writeln('Created backup:');
$st->writeln(sprintf("<fg=yellow>[%s]</> <fg=green>%s</> (File: %s)", $backup->get_date('Y-m-d H:i:s'), $backup->get_description(), $backup->get_file()));
}
}

+ 30
- 0
console/Backup/ListBackupsCommand.php View File

@@ -0,0 +1,30 @@
<?php

namespace Epesi\Console\Backup;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Ifsnop\Mysqldump\Mysqldump;

class ListBackupsCommand extends Command
{
protected function configure()
{
$this
->setName('backup:list')
->setDescription('List backups');
}

protected function execute(InputInterface $input, OutputInterface $output)
{
$st = new SymfonyStyle($input, $output);
require_once 'include/backups.php';
$util = \BackupUtil::default_instance();
foreach ($util->list_backups() as $backup) {
$st->writeln(sprintf("<fg=yellow>[%s]</> <fg=green>%s</> (File: %s)", $backup->get_date('Y-m-d H:i:s'), $backup->get_description(), $backup->get_file()));
}
}
}

+ 93
- 28
include/backups.php View File

@@ -1,5 +1,7 @@
<?php

use Ifsnop\Mysqldump\Mysqldump;

class BackupUtil {

private $_backup_dir;
@@ -13,6 +15,25 @@ class BackupUtil {
$this->_backup_store = new BackupStore($backup_dir);
}

public static function backup_db($file = null)
{
if (\DB::is_postgresql()) {
throw new Exception('PostgreSQL is not supported yet');
}
$compress = Mysqldump::NONE;
$extension = strtolower(substr($file, -3));
if ($extension == '.gz') $compress = Mysqldump::GZIP;
if ($extension == '.bz') $compress = Mysqldump::BZIP2;
$options = array(
'compress' => $compress,
'add-drop-table' => true,
'no-data' => array('recordbrowser_search_index', 'session', 'session_client', 'history'),
);
$dump = new Mysqldump('mysql:host=' . DATABASE_HOST . ';dbname=' . DATABASE_NAME, DATABASE_USER, DATABASE_PASSWORD, $options);
$dump->start($file);
return $file;
}

function list_backups() {
$files = $this->_backup_store->list_backup_files();
$ret = array();
@@ -23,21 +44,37 @@ class BackupUtil {
}

function create_backup_of_epesi() {

$description = "EPESI ver " . EPESI_VERSION . " rev " . EPESI_REVISION;
return $this->_create_backup('.', $description);
$backup = $this->create_backup('.', $description);
$this->addDbBackup($backup);
return $backup;
}

function addDbBackup(Backup $backup)
{
$db_backup = 'db_backup_' . time() . '.sql.gz';
self::backup_db($db_backup);
if (file_exists($db_backup)) {
$backup->get_archive()->addSingleFile($db_backup, 'db_backup.sql.gz');
unlink($db_backup);
}
}

function create_backup($files, $description = '') {
return $this->_create_backup($files, $description);
function create_backup($files, $description = '', $output_file = null, $overwrite = false) {
if (!$output_file) {
$output_file = $this->_backup_store->new_backup_file();
}
$bkp = new Backup($output_file, $overwrite);
return $this->_create_backup($files, $description, $bkp);
}

private function _create_backup($files, $description) {
$file = $this->_backup_store->new_backup_file();
$bkp = new Backup($file);
private function _create_backup($files, $description, Backup $backup) {
$this->_chdir_to_epesi();
$success = $bkp->create($files, $description, $this->_backup_dir);
$exclude = array($this->_backup_dir, DATA_DIR . '/cache');
$success = $backup->create($files, $description, $exclude);
$this->_chdir_back();
return $success ? $bkp : false;
return $success ? $backup : false;
}

private function _chdir_to_epesi() {
@@ -54,14 +91,14 @@ class BackupUtil {
}

static function default_instance() {
return new BackupUtil(getcwd(), 'data/backups');
return new BackupUtil(getcwd(), DATA_DIR . '/backups');
}

}

class BackupStore {

private $_extension = 'bkp';
private $_extension = 'bkp.zip';
private $_dir;

public function __construct($dir) {
@@ -92,8 +129,9 @@ class BackupStore {
$ret = array();
$dir = new DirectoryIterator($this->_dir);
foreach ($dir as $file) {
if ($file->getExtension() == $this->_extension)
if (preg_match('/.*' . preg_quote($this->_extension) . '$/', $file->getFilename())) {
$ret[] = $file->getPathname();
}
}
return $ret;
}
@@ -105,21 +143,21 @@ class Backup {
private static $__properties_in_metadata = array('date', 'description');
private $date;
private $description;
private $files;
private $_overwrite;
private $file;
private $overwrite;

/** @var BackupArchive */
private $_archive;
private $archive;

public function __construct($backup_file, $overwrite = false) {
$this->_file = $backup_file;
$this->_overwrite = $overwrite;
$this->_archive = new BackupArchive($backup_file);
$this->file = $backup_file;
$this->overwrite = $overwrite;
$this->archive = new BackupArchive($backup_file);
$this->_read_metadata();
}

private function _read_metadata() {
$data = $this->_archive->get_metadata();
$data = $this->archive->get_metadata();
$this->_set_properties($data);
}

@@ -142,41 +180,61 @@ class Backup {

public function create($files, $description, $exclude_files = array()) {
set_time_limit(0);
if ($this->_overwrite == false && $this->_archive->exists())
return false;
if ($this->overwrite == false && $this->archive->exists()) {
throw new Exception('Backup archive exists - overwrite is forbidden.');
}

$success = $this->_archive->create($files, $exclude_files);
$success = $this->archive->create($files, $exclude_files);
if ($success) {
$this->date = time();
$this->description = $description;
$this->_archive->set_metadata($this->_get_properties());
$this->archive->set_metadata($this->_get_properties());
}
return $success;
}

public function restore_to($destination) {
set_time_limit(0);
return $this->_archive->extractTo($destination);
return $this->archive->extractTo($destination);
}
public function restore() {
return $this->restore_to('.');
}

/**
* @param string|null $format date function format string
*
* @return int|string unix timestamp or formatted date
*/
public function get_date($format = null) {
if (is_string($format))
return date($format, $this->date);
return $this->date;
}

/**
* Backup description
*
* @return string
*/
public function get_description() {
return $this->description;
}

public function get_files() {
if (!$this->files)
$this->files = $this->_archive->list_files();
return $this->files;
/**
* @return BackupArchive
*/
public function get_archive() {
return $this->archive;
}

/**
* @return string File path to backup
*/
public function get_file()
{
return $this->file;
}

}
@@ -262,6 +320,13 @@ class BackupArchive extends ZipArchive {
return $this->addFile($path);
}

public function addSingleFile($file, $dest)
{
$this->_open();
$this->addFile($file, $dest);
$this->close();
}

public function get_metadata() {
if (!file_exists($this->_file))
return null;
@@ -298,7 +363,7 @@ class BackupArchive extends ZipArchive {

private function _is_excluded($file) {
foreach ($this->_exclude as $ex) {
if (strpos($file, $ex) === 0)
if (preg_match("#$ex#", $file))
return true;
}
return false;


Loading…
Cancel
Save