From 3d5f45f00141e9055bf9071fdc071cd3ad8696af Mon Sep 17 00:00:00 2001 From: flydev Date: Fri, 27 Oct 2023 23:20:23 +0200 Subject: [PATCH 1/7] chore(workflows): contrib update --- .github/workflows/releases.yml | 14 ++++++++------ README.md | 14 ++++++++++++++ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml index 02cf148..4d3b924 100644 --- a/.github/workflows/releases.yml +++ b/.github/workflows/releases.yml @@ -9,12 +9,6 @@ jobs: runs-on: ubuntu-latest steps: - - uses: akhilmhdh/contributors-readme-action@v2.3.6 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - commit_message: "docs(contributor): readme updated" - - uses: actions/checkout@v3 - name: conventional Changelog Action id: changelog @@ -31,3 +25,11 @@ jobs: tag_name: ${{ steps.changelog.outputs.tag }} release_name: ${{ steps.changelog.outputs.tag }} body: ${{ steps.changelog.outputs.clean_changelog }} + + - name: Contribute List + uses: akhilmhdh/contributors-readme-action@v2.3.6 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + commit_message: "docs(contributor): contrib updated" + \ No newline at end of file diff --git a/README.md b/README.md index b52eb5b..012fafb 100755 --- a/README.md +++ b/README.md @@ -1,5 +1,18 @@ +
+ +![Current stable version)](https://img.shields.io/github/package-json/v/flydev-fr/duplicator) +![GitHub Repo stars](https://img.shields.io/github/stars/flydev-fr/duplicator) +[![process.com](https://img.shields.io/badge/%F0%9F%8C%80_processwire-lightblue)](https://processwire.com) +[![Get support](https://img.shields.io/badge/%F0%9F%9B%9F_support_thread-gray)](https://processwire.com/talk/topic/15345-duplicator-backup-and-move-sites/?page=2#comment-139376) + +
+ +*** + ![Duplicator](https://s3.us-west-2.amazonaws.com/processwire-forums/monthly_2017_11/Duplicator-logo-v2-Dlong.thumb.png.8898ec8cda79046779b10d8527fc0def.png) +*** + ### WIP - The readme is being re-written. @@ -135,6 +148,7 @@ Timeout usually happen if you are on a low-end budget host with limitations. The * 1and1 Shared hosting (resolved by using Filters) * Dreamhost hosting (resolved using the `Archive Flush` option); Need to be tested again. +## Contributors From ad53225f29ad10bf3a9d67ed506b41c7564cf0e3 Mon Sep 17 00:00:00 2001 From: flydev Date: Fri, 27 Oct 2023 23:17:20 +0200 Subject: [PATCH 2/7] fix: If zip binary is not found on the system, fallback to wireZipFile note: considering to make it configurable. --- Duplicator.module | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Duplicator.module b/Duplicator.module index 5ea3417..a118786 100644 --- a/Duplicator.module +++ b/Duplicator.module @@ -468,7 +468,9 @@ class Duplicator extends WireData implements Module, ConfigurableModule $fileinfo['structure'] = $zipinfo['zipfile']; if (!file_exists($fileinfo['structure'])) { - DUP_Logs::log("- an error occured while building the ProcessWire structure: " . $fileinfo['zipfile'] . " root: " . $root . " " . print_r($options, true)); + DUP_Logs::log("- an error occured while building the ProcessWire structure: "); + DUP_Logs::log(json_encode($fileinfo['zipfile'], JSON_PRETTY_PRINT)); + DUP_Logs::log(" root: " . $root . "\n" . json_encode($options, JSON_PRETTY_PRINT )); return false; } @@ -1447,6 +1449,13 @@ class Duplicator extends WireData implements Module, ConfigurableModule } else { $note = __("\n⚠️ `mysqldump` not found on **PATH**. **Native Mode** backup is **not available**, "); } + if ($zipExec !== null) { + $note .= __("\n☑️ `zip` binary [**OK**]."); + } else { + $note .= __("\n⚠️ `zip` binary not found on **PATH**. **wireZipFile** will be used instead."); + } + + $f->name = 'backup_mode'; $f->label = __("Mode"); $f->description = __("Choose the prefered backup mode. Select **Native Mode** for **large database**."); @@ -1469,7 +1478,7 @@ class Duplicator extends WireData implements Module, ConfigurableModule $f->columnWidth = 50; $f->notes = '`security settings`: **CHMOD** generated script on creation. Default: `0700` *(chmod a+rwx,g-rwx,o-rwx)*'; $fs->append($f); - + $field = $modules->get('InputfieldCheckbox'); $field->attr('name', 'archiveFlush'); $field->label = 'Archive Flush: Attempt Network Keep Alive'; From 24a7df8280c4a14b597cd3b2fbb5db27aba24166 Mon Sep 17 00:00:00 2001 From: flydev Date: Fri, 27 Oct 2023 23:19:32 +0200 Subject: [PATCH 3/7] refactor: Added helpers to dup_util - `getOS()` return current operating system name - `getStub()` return content of a stub file --- Classes/DupUtil.php | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/Classes/DupUtil.php b/Classes/DupUtil.php index 2aa90a1..ad7b8e0 100755 --- a/Classes/DupUtil.php +++ b/Classes/DupUtil.php @@ -238,6 +238,18 @@ public static function isWinOS() return (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'); } + // MAC (DARWIN) is threat as UNIX: not tested since M1 Silicon + public static function getOS() + { + $os = strtoupper(substr(PHP_OS, 0, 3)); + if ($os === 'WIN') { + return 'WINDOWS'; + } + else { + return 'UNIX'; + } + } + static public function safePath($path) { return str_replace("\\", "/", $path); @@ -297,12 +309,26 @@ static function timer($name = 'default', $unset_timer = TRUE) return $timers; } -} + + static public function getStub($OS = null) { + if ($OS) { + $os = strtolower($OS); + } else { + $os = strtolower(self::getOS()); + } + $filename = __DIR__ . '/../scripts/mysqldump.' . $os . '.sh'; + // assert script file exists based $OS + if (!file_exists($filename)) { + return false; + } + return $filename; + } + +} // end class class DUP_DataFilter extends \RecursiveFilterIterator { - protected $excluded; protected $excludedList = array(); @@ -327,4 +353,4 @@ public function getChildren() ini_set('max_execution_time', 300); return new self($this->getInnerIterator()->getChildren(), $this->excluded); } -} +} // end class From 221f6470b52bef9f00aff1d60090871b445eb653 Mon Sep 17 00:00:00 2001 From: flydev Date: Sat, 28 Oct 2023 12:31:44 +0200 Subject: [PATCH 4/7] refactor: Defined constants to set current backup mode --- Classes/BackupDatabase.php | 46 +++++++++++++++++++++++++---------- Duplicator.module | 49 +++++++++++++++++++++++++++++++------- 2 files changed, 74 insertions(+), 21 deletions(-) diff --git a/Classes/BackupDatabase.php b/Classes/BackupDatabase.php index 6bc9905..5d3ca53 100644 --- a/Classes/BackupDatabase.php +++ b/Classes/BackupDatabase.php @@ -23,14 +23,8 @@ public function __construct(array $options = array()) 'cachePath' => wire('config')->paths->cache, 'chmodPermission' => '0700' ); - + $this->options = array_merge($this->options, $options); - - if (DUP_Util::isWinOS()) { - $this->OS = 'WINDOWS'; - } else { - $this->OS = 'UNIX'; - } } public function setDatabase($database) @@ -95,8 +89,17 @@ public function getZip() $output = array(); if ($this->OS === 'UNIX') { - exec('zip ' . $zipfile . ' ' . $sqlfile, $output, $return); - unlink($sqlfile); + if ($this->options['zipbinary']) { + // use zip binary as it's available + exec('zip ' . $zipfile . ' ' . $sqlfile, $output, $return); + } + else { + // use processwire zip function + $output = wireZipFile($zipfile, $sqlfile); + if (!count($output['errors'])) { + $return = 0; + } + } unlink($cachePath . 'duplicator.sh'); } else { exec(str_replace('\\', '/', wire('config')->paths->Duplicator) . 'Bin/7za.exe a ' . $zipfile . ' ' . str_replace('\\', '/', $sqlfile), $output, $return); @@ -200,9 +203,28 @@ protected function UnixNative() // delete `duplicator.sh` script on error unlink($cachePath . 'duplicator.sh'); - - $ex = json_encode($output); - throw new WireException("Error while running UnixNative Backup\n, err {$return}: {$ex}\n\n"); + + $meaning = ''; + if ($return == 1) { + $meaning = 'general error in command line'; + } + else if ($return == 2) { + $meaning = 'misuse of shell builtins (according to Bash documentation), verify that "mysqldump" or "zip" is available on env path'; + } + else if ($return == 126) { + $meaning = 'command invoked cannot execute'; + } + else if ($return == 127) { + $meaning = 'error while running the script, maybe the script is not well formatted (carriage return)'; + } + else if ($return == 126) { + $meaning = 'command invoked cannot execute'; + } + else { + $meaning = 'unknown error'; + } + + throw new WireException("!! Error while running UnixNative Backup\n, err {$return}: $meaning\n\n"); } return $cachePath . $this->options['backup']['filename']; diff --git a/Duplicator.module b/Duplicator.module index a118786..0bd8e36 100644 --- a/Duplicator.module +++ b/Duplicator.module @@ -128,6 +128,9 @@ class Duplicator extends WireData implements Module, ConfigurableModule const DUP_DB_MAX_TIME = 5000; const DUP_PHP_EXECUTION_TIME = 300; const DUP_ZIP_FLUSH_TRIGGER = 1000; + const DUP_MODE_PWAPI = 0; + const DUP_MODE_NATIVE = 1; + const DEFAULT_BACKUP_MODE = self::DUP_MODE_PWAPI; protected static $_data; @@ -167,7 +170,7 @@ class Duplicator extends WireData implements Module, ConfigurableModule 'maxPackages' => self::DUP_MAX_PACKAGES, 'removeBackups' => 1, // Advanced options - 'backup_mode' => 'MODE_PWAPI', + 'backup_mode' => self::DEFAULT_BACKUP_MODE, 'chmodPermission' => '0700', 'archiveFlush' => 0, 'zip_flush_mbytes' => 0, @@ -394,6 +397,19 @@ class Duplicator extends WireData implements Module, ConfigurableModule $this->warning("Please note that packages files in {$path} remain. You may remove them manually."); } + public function ___upgrade($fromVersion, $toVersion) { + // Show a warning if previous installed version was configured using native_mode about this previous setting + if ($this->modules->isInstalled('Duplicator') && $this->modules->get('Duplicator')->backup_mode == 'MODE_NATIVE') { + $this->modules->saveConfig(self::class, 'backup_mode', self::DUP_MODE_NATIVE); + if ($this->backup_mode == self::DUP_MODE_NATIVE) { + $this->message("The **previous version** of Duplicator was configured using `native_mode`. Please note that the value of this setting has changed. The change **is already applied** to your current configuration.", Notice::allowMarkdown); + } + else { + $this->warning("The **previous version** of Duplicator was configured using `native_mode`. Please note that the value of this setting has changed. You need to set the backup mode to `Native mode` and **save the settings**.", Notice::allowMarkdown); + } + } + } + public static function getPath() { return self::$_data['path']; @@ -494,7 +510,7 @@ class Duplicator extends WireData implements Module, ConfigurableModule ); $options = array_merge($options, $defaultOptions); $dbBackup = new BackupDatabase($options); - $zipobj = $dbBackup->setDatabase($this->database)->setMode($this->backup_mode); + $zipobj = $dbBackup->setDatabase($this->database)->setMode((int)$this->backup_mode); $zipfile = $zipobj->getZip(); if (file_exists($zipfile)) { $fileinfo['zipfile'] = $zipfile; @@ -1433,7 +1449,7 @@ class Duplicator extends WireData implements Module, ConfigurableModule $f = wire('modules')->get("InputfieldSelect"); $note = ""; - $options = array('MODE_PWAPI' => 'Web mode'); + $options = array(self::DUP_MODE_PWAPI => 'Web mode'); // check if mysqldump tool is available on env path $shellExec = null; if (DUP_Util::isEnabled('shell_exec')) { @@ -1443,8 +1459,8 @@ class Duplicator extends WireData implements Module, ConfigurableModule $shellExec = shell_exec("which mysqldump"); } } - if ($shellExec !== null) { - $options = array_merge($options, array('MODE_NATIVE' => 'Native mode (mysqldump)')); + if ($mysqldumpExec !== null) { + $options = array_merge($options, array(self::DUP_MODE_NATIVE => 'Native mode (mysqldump)')); $note = __("\n☑️ `mysqldump` tool [**OK**]."); } else { $note = __("\n⚠️ `mysqldump` not found on **PATH**. **Native Mode** backup is **not available**, "); @@ -1461,11 +1477,9 @@ class Duplicator extends WireData implements Module, ConfigurableModule $f->description = __("Choose the prefered backup mode. Select **Native Mode** for **large database**."); $f->notes = !self::isExtensionLoaded('zip') ? __("⚠️ `php_zip` is **required**, extension **not found**") : __("☑️ `php_zip` extension [**OK**]"); $f->notes .= $note; + $f->addOptions($options); - if ($data['backup_mode']) { - $f->attr('value', $data['backup_mode']); - $f->attr('selected', $data['backup_mode']); - } + $f->value = (int)$data['backup_mode']; $f->columnWidth = 50; $fs->append($f); @@ -1477,6 +1491,23 @@ class Duplicator extends WireData implements Module, ConfigurableModule $f->icon = 'shield'; $f->columnWidth = 50; $f->notes = '`security settings`: **CHMOD** generated script on creation. Default: `0700` *(chmod a+rwx,g-rwx,o-rwx)*'; + $f->showIf = "backup_mode=".self::DUP_MODE_NATIVE; + $fs->append($f); + + $f = wire('modules')->get("InputfieldTextarea"); + $f->name = 'shellScript'; + $f->label = __("Custom Shell script"); + $f->description = __("Shell script used to backup database in Native Mode."); + $f->notes = __("This script is used to backup database in Native Mode. You can edit this script to fit your needs."); + // load from stub + $f->value = $data['shellScript'] ? $data['shellScript'] : ''; + $f->icon = 'terminal'; + $f->columnWidth = 100; + $f->rows = 18; + $f->collapsed = $data['backup_mode'] == self::DUP_MODE_NATIVE ? ($data['shellScript'] == '' ? true : false) : false; + $f->showIf = "backup_mode=".self::DUP_MODE_NATIVE; + $stub = DUP_Util::getStub(); + $f->placeholder = $stub ? file_get_contents($stub) : 'Paste or write your custom dump shell script here...'; $fs->append($f); $field = $modules->get('InputfieldCheckbox'); From eb176820fccf524470aae4ee1cbf141667efc09d Mon Sep 17 00:00:00 2001 From: flydev Date: Sat, 28 Oct 2023 12:33:42 +0200 Subject: [PATCH 5/7] refactor: Remove ProcessDuplicator on module uninstall --- Duplicator.module | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Duplicator.module b/Duplicator.module index 0bd8e36..34813e4 100644 --- a/Duplicator.module +++ b/Duplicator.module @@ -18,7 +18,7 @@ require_once __DIR__ . DIRECTORY_SEPARATOR . 'Classes/DupLogs.php'; require_once __DIR__ . DIRECTORY_SEPARATOR . 'Classes/BackupDatabase.php'; class DupZipLog -{ //ATO: Utility class to implement some logging into a file per ZIP +{ //ATO: Utility class to implement some logging into a file per ZIP public $fLog = null; public $startTime = 0; public $logLevel = 0; @@ -26,7 +26,7 @@ class DupZipLog public $excludeNames = []; public $excludeRegEx = []; public $excludeExtension = []; - + public function log($str) { if ($this->fLog) { @@ -393,6 +393,11 @@ class Duplicator extends WireData implements Module, ConfigurableModule */ public function ___uninstall() { + // uninstall ProcessDuplicator if installed + if ($this->modules->isInstalled('ProcessDuplicator')) { + $this->modules->uninstall('ProcessDuplicator'); + } + $path = str_replace($this->config->paths->root, '/', $this->path); $this->warning("Please note that packages files in {$path} remain. You may remove them manually."); } From 1497031f76f9ff97eaf0d166b61ca3221c803b31 Mon Sep 17 00:00:00 2001 From: flydev Date: Sat, 28 Oct 2023 13:11:54 +0200 Subject: [PATCH 6/7] refactor(dbdump): Remove carriage return or script will fail on unix --- Classes/BackupDatabase.php | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/Classes/BackupDatabase.php b/Classes/BackupDatabase.php index 5d3ca53..850ce03 100644 --- a/Classes/BackupDatabase.php +++ b/Classes/BackupDatabase.php @@ -163,25 +163,9 @@ protected function fromNativeTools() protected function UnixNative() { $cachePath = $this->options['cachePath']; - $data = ' - # (1) set up all the mysqldump variables - FILE=' . $this->options['backup']['filename'] . ' - DBSERVER=127.0.0.1 - DATABASE=' . wire('config')->dbName . ' - USER=' . wire('config')->dbUser . ' - PASS=' . wire('config')->dbPass . ' - - # (2) in case you run this more than once a day, remove the previous version of the file - unalias rm 2> /dev/null - rm ${FILE} 2> /dev/null - rm ${FILE}.zip 2> /dev/null - - # (3) do the mysql database backup (dump) - # for a database server on a separate host: - # mysqldump --opt --protocol=TCP --user=${USER} --password=${PASS} --host=${DBSERVER} ${DATABASE} > ' . $cachePath . '${FILE} - # use this command for a database server on localhost. add other options if need be. - mysqldump --routines --triggers --single-transaction --log-error=mysqldump_error.log --user=${USER} --password=${PASS} --databases ${DATABASE} > ' . $cachePath . '${FILE} - '; + $data = $this->options['shellScript']; + // remove carriage return or script will fail + $data = str_replace("\r", "", $data); $return = null; $output = array(); From 30c514340a2e5d67c5f934888a528f179fad20a0 Mon Sep 17 00:00:00 2001 From: flydev Date: Fri, 27 Oct 2023 23:11:55 +0200 Subject: [PATCH 7/7] feat: Set custom dump shell script from configuration Feature asked by @adrianbj for adding custom script job in order to dump remote database. --- Classes/BackupDatabase.php | 101 ++++++++++++++++++++++++++++------- Duplicator.module | 19 +++++-- scripts/mysqldump.unix.sh | 26 +++++++++ scripts/mysqldump.windows.sh | 27 ++++++++++ 4 files changed, 151 insertions(+), 22 deletions(-) create mode 100644 scripts/mysqldump.unix.sh create mode 100644 scripts/mysqldump.windows.sh diff --git a/Classes/BackupDatabase.php b/Classes/BackupDatabase.php index 850ce03..e96d53d 100644 --- a/Classes/BackupDatabase.php +++ b/Classes/BackupDatabase.php @@ -4,6 +4,9 @@ class BackupDatabase { + const DUP_MODE_PWAPI = 0; + const DUP_MODE_NATIVE = 1; + protected $database; protected $options; protected $mode; @@ -13,6 +16,8 @@ class BackupDatabase public function __construct(array $options = array()) { + $this->OS = DUP_Util::getOS(); + $this->options = array( 'path' => '', 'backup' => array( @@ -21,7 +26,9 @@ public function __construct(array $options = array()) 'maxSeconds' => 120, ), 'cachePath' => wire('config')->paths->cache, - 'chmodPermission' => '0700' + 'chmodPermission' => '0700', + 'shellScript' => '', + 'zipbinary' => false, ); $this->options = array_merge($this->options, $options); @@ -41,6 +48,33 @@ public function setMode($mode) return $this; } + // set default shell script from stub file for Windows OS (batch file) or Unix OS (shell script) + public function setDefaultShellScript($customScriptData = false) + { + $stub = ''; + if ($customScriptData) { + $stub = $customScriptData; + } else { + $filename = DUP_Util::getStub($this->OS); + if (!$filename) return false; + $stub = file_get_contents($filename); + } + + if ($stub) { + $stub = str_replace('%%FILE%%', $this->options['backup']['filename'], $stub); + $stub = str_replace('%%SERVER%%', wire('config')->dbHost, $stub); + $stub = str_replace('%%DATABASE%%', wire('config')->dbName, $stub); + $stub = str_replace('%%USER%%', wire('config')->dbUser, $stub); + $stub = str_replace('%%PASS%%', wire('config')->dbPass, $stub); + $stub = str_replace('%%CACHEPATH%%', $this->options['cachePath'], $stub); + } + + // set shell script + $this->options['shellScript'] = $stub; + + return $stub; + } + /* * do backup using different mode * return a SQL file @@ -51,12 +85,12 @@ public function backup() { DUP_Logs::log("Backup Database"); switch ($this->mode) { - case 'MODE_PWAPI': + case self::DUP_MODE_PWAPI: DUP_Logs::log("- Backup using standard mode"); return $this->fromProcessWire(); break; - case 'MODE_NATIVE': + case self::DUP_MODE_NATIVE: DUP_Logs::log("- Backup using native tools"); return $this->fromNativeTools(); break; @@ -76,7 +110,7 @@ public function getZip() $sqlfile = $this->backup(); $this->size = filesize($sqlfile); $zipfile = $this->options['path'] . $this->options['backup']['filename'] . '.zip'; - if ($this->mode === 'MODE_PWAPI') { + if ($this->mode === self::DUP_MODE_PWAPI) { $result = wireZipFile($zipfile, $sqlfile); DUP_Util::deleteFile($sqlfile); if (count($result['errors'])) { @@ -84,7 +118,7 @@ public function getZip() DUP_Logs::log("ZIP add failed: $error"); } } - } else if ($this->mode === 'MODE_NATIVE') { + } else if ($this->mode === self::DUP_MODE_NATIVE) { $return = null; $output = array(); @@ -99,11 +133,11 @@ public function getZip() if (!count($output['errors'])) { $return = 0; } - } + } + unlink($cachePath . 'duplicator.sh'); } else { exec(str_replace('\\', '/', wire('config')->paths->Duplicator) . 'Bin/7za.exe a ' . $zipfile . ' ' . str_replace('\\', '/', $sqlfile), $output, $return); - unlink($sqlfile); unlink(str_replace('\\', '/', $cachePath) . 'duplicator.bat'); } @@ -116,6 +150,10 @@ public function getZip() } } + if (file_exists($sqlfile)) { + unlink($sqlfile); + } + if (file_exists($zipfile)) { return $zipfile; } @@ -152,6 +190,7 @@ protected function fromNativeTools() case 'WINDOWS': return $this->WindowsNative(); + default: // not supported platform return false; @@ -162,11 +201,33 @@ protected function fromNativeTools() protected function UnixNative() { - $cachePath = $this->options['cachePath']; - $data = $this->options['shellScript']; - // remove carriage return or script will fail + if ($this->options['shellScript'] == '') { + $data = $this->setDefaultShellScript(); + DUP_Logs::log("- using default shell script"); + } + else { + $data = $this->setDefaultShellScript($this->options['shellScript']); + DUP_Logs::log("- using custom shell script"); + } + + if (!$data) { + throw new WireException("!! Error while running UnixNative Backup\n, err: shell script not found\n\n"); + } + + // remove carriage return or script will fail on unix $data = str_replace("\r", "", $data); + $zipExec = shell_exec("which zip"); + // check if zip tool is available on env path + if ($zipExec !== null) { + $this->options['zipbinary'] = true; + DUP_Logs::log("- zip binary is available"); + } else { + DUP_Logs::log("- zip binary not available, fallback using wireZipFile function"); + } + + $cachePath = $this->options['cachePath']; + $return = null; $output = array(); file_put_contents($cachePath . 'duplicator.sh', $data); @@ -216,16 +277,20 @@ protected function UnixNative() protected function WindowsNative() { + // exit if shell script is empty + if ($this->options['shellScript'] === '') { + return false; + } + + $data = $this->setDefaultShellScript($this->options['shellScript']); + if (!$data) { + throw new WireException("!! Error while running UnixNative Backup\n, err: shell script not found\n\n"); + } + $return = null; $output = array(); $cachePath = $this->options['cachePath']; - - $data = '@echo off - set MYSQLDATABASE=' . wire('config')->dbName . ' - set MYSQLUSER=' . wire('config')->dbUser . ' - set MYSQLPASS=' . wire('config')->dbPass . ' - "mysqldump.exe" -u%MYSQLUSER% -p%MYSQLPASS% --single-transaction --skip-lock-tables --routines --triggers %MYSQLDATABASE% > ' . $cachePath . $this->options['backup']['filename']; - + file_put_contents($cachePath . 'duplicator.bat', $data); chdir($cachePath); @@ -244,4 +309,4 @@ protected function WindowsNative() return $cachePath . $this->options['backup']['filename']; } -} \ No newline at end of file +} diff --git a/Duplicator.module b/Duplicator.module index 34813e4..f4a59a3 100644 --- a/Duplicator.module +++ b/Duplicator.module @@ -172,6 +172,7 @@ class Duplicator extends WireData implements Module, ConfigurableModule // Advanced options 'backup_mode' => self::DEFAULT_BACKUP_MODE, 'chmodPermission' => '0700', + 'shellScript' => '', 'archiveFlush' => 0, 'zip_flush_mbytes' => 0, // CRON mode @@ -511,8 +512,10 @@ class Duplicator extends WireData implements Module, ConfigurableModule 'description' => '', //$this->backupFileinfo(), 'maxSeconds' => self::DUP_WIRETEMP_MAX_TIME, ), - 'chmodPermission' => $this->chmodPermission + 'chmodPermission' => $this->chmodPermission, + 'shellScript' => $this->shellScript, ); + $options = array_merge($options, $defaultOptions); $dbBackup = new BackupDatabase($options); $zipobj = $dbBackup->setDatabase($this->database)->setMode((int)$this->backup_mode); @@ -1456,12 +1459,20 @@ class Duplicator extends WireData implements Module, ConfigurableModule $note = ""; $options = array(self::DUP_MODE_PWAPI => 'Web mode'); // check if mysqldump tool is available on env path - $shellExec = null; + $mysqldumpExec = null; + $zipExec = null; + // check if shell_exec is enabled if (DUP_Util::isEnabled('shell_exec')) { + // exec mysqldump to check if it's available. if (DUP_Util::isWinOS()) { - $shellExec = shell_exec("where mysqldump"); + $mysqldumpExec = shell_exec("where mysqldump"); } else { - $shellExec = shell_exec("which mysqldump"); + $mysqldumpExec = shell_exec("which mysqldump"); + + } + // check if zip binary is available + if (!DUP_Util::isWinOS()) { + $zipExec = shell_exec("which zip"); } } if ($mysqldumpExec !== null) { diff --git a/scripts/mysqldump.unix.sh b/scripts/mysqldump.unix.sh new file mode 100644 index 0000000..839c3b2 --- /dev/null +++ b/scripts/mysqldump.unix.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +# (1) Set up all the mysqldump variables +FILE=%%FILE%% +DBSERVER=%%SERVER%% +DATABASE=%%DATABASE%% +USER=%%USER%% +PASS=%%PASS%% +CACHEPATH=%%CACHEPATH%% + +# Fix trailing slash in cache path +CACHEPATH="${CACHEPATH%/}/" + +OUTPUT="${CACHEPATH}${FILE}" + +# (2) In case you run this more than once a day, remove the previous version of the file +# unalias rm 2> /dev/null +# rm ${FILE} 2> /dev/null +# rm ${FILE}.zip 2> /dev/null + +# (3) Do the mysql database backup (dump) +# - to log errors of the dump process to a file, add --log-error=mysqldump_dup_error.log +# (a) Use this command for a remote database. Add other options if need be. +# mysqldump --opt --protocol=TCP --user=${USER} --password=${PASS} --host=${DBSERVER} --port=${PORT} ${DATABASE} ${DATABASE} > ${OUTPUT} +# (b) Use this command for a database server on localhost. Add other options if need be. +mysqldump --routines --triggers --single-transaction --user=${USER} --password=${PASS} --databases ${DATABASE} > ${OUTPUT} diff --git a/scripts/mysqldump.windows.sh b/scripts/mysqldump.windows.sh new file mode 100644 index 0000000..57442dd --- /dev/null +++ b/scripts/mysqldump.windows.sh @@ -0,0 +1,27 @@ +@echo off + +REM (1) Set up all the mysqldump variables +set DBSERVER=%%SERVER%% +set DATABASE=%%DATABASE%% +set USER=%%USER%% +set PASS=%%PASS%% +set FILE=%%FILE%% +set CACHEPATH=%%CACHEPATH%% + +REM Fix trailing slash in cache path +set lastchar=%CACHEPATH%:~-1% +if %lastchar% == \ ( + set OUTPUT=%CACHEPATH%%FILE% +) +else ( + set OUTPUT=%CACHEPATH%\%FILE% +) + +REM (3) Do the MySQL database backup (dump) +REM - to log errors of the dump process to a file, add --log-error=mysqldump_dup_error.log +REM For a database server on a separate host: +REM (a) Use this command for a database on remote server. Add other options if need be. +REM > mysqldump --opt --protocol=TCP --user=%USER% --password=%PASS% --host=%DBSERVER% %DATABASE% > %OUTPUT% +REM +REM (b) Use this command for a database server on localhost. Add other options if need be. +mysqldump.exe -u%USER% -p%PASS% --single-transaction --skip-lock-tables --routines --triggers %DATABASE% > %OUTPUT%