_destination = $this->getDestination() . JetBackup::SEP . 'tmp'; $this->_target = $this->getDestination() . JetBackup::SEP . 'cpmove-' . $this->getUsername() . Archive::ARCHIVE_EXT; if(!file_exists($this->_destination)) mkdir($this->_destination, 0700); /** * Structure * Filename: cpmove-username.tar.gz * * |-- apache_tls/ * |-- bandwidth/ * |-- bandwidth_db/ * |-- counters/ * |-- cp/ * | +-- username * |-- cron/ * |-- customizations/ * |-- dnssec_keys/ * |-- dnszones/ * |-- domainkeys/ * | |-- private/ * | +-- public/ * |-- homedir/ * | +-- public_html/ * |-- httpfiles/ * |-- ips/ * |-- locale/ * |-- logs/ * |-- mm/ * |-- mma/ * | |-- priv/ * | +-- pub/ * |-- mms/ * |-- mysql/ * | |-- database.create * | +-- database.sql * |-- mysql-timestamps/ * | +-- mysql * |-- psql/ * |-- resellerconfig/ * |-- resellerfeatures/ * |-- resellerpackages/ * |-- ssl/ * |-- sslcerts/ * |-- sslkeys/ * |-- suspended/ * |-- suspendinfo/ * |-- team/ * |-- userconfig/ * |-- userdata/ * |-- va/ * |-- vad/ * |-- vf/ * |-- autossl.json * |-- homedir_paths * |-- mysql.sql * |-- mysql.sql-auth.json * |-- shadow * +-- version */ $this->getTask()->func([$this, '_createSkeleton']); $this->getTask()->func([$this, '_createHomedir']); $this->getTask()->func([$this, '_createJetBackupPlugin']); $this->getTask()->func([$this, '_createVersion']); $this->getTask()->func([$this, '_createAutoSSL']); $this->getTask()->func([$this, '_createUserData']); $this->getTask()->func([$this, '_createShadow']); $this->getTask()->func([$this, '_createHomedirPaths']); $this->getTask()->func([$this, '_createMySQLTimestemp']); $this->getTask()->func([$this, '_createMySQLAuth']); $this->getTask()->func([$this, '_createMySQLAuthJson']); $this->getTask()->func([$this, '_createMySQLCreate']); $this->getTask()->func([$this, '_createMySQLDump']); $this->getTask()->func([$this, '_archive']); $this->getTask()->func([$this, '_compress']); return $this->_target . '.gz'; } public function _archive():void { $archive = new Archive($this->_target, false, Archive::OPT_SPARSE, 0, $this->getDestination()); $archive->setLogController($this->getLogController()); $this->getTask()->scan($this->_destination, function(DirIterator $scan, $data) use ($archive) { if (!$data->total_size) throw new ArchiveException('Invalid total tree size'); $archive->setAppend(!($data->total_size == $data->current_pos)); try { $fd = $archive->getFileFD(); $current_file = $scan->next($fd ? $fd->tell() : 0); } catch (DirIteratorFileVanishedException $e) { $this->getLogController()->logMessage('[ WARNING ] File Vanished : ' . $e->getMessage()); return; } if($scan->getSource() == $current_file->getName()) return; $this->getLogController()->logMessage("[". ($data->total_size - $data->current_pos)."/$data->total_size] Archiving: {$current_file->getName()}"); try { $file = substr($current_file->getName(), strlen($this->_destination)+1); $archive->appendFileChunked($current_file, $file, function() use ($data) { $progress = $this->getTask()->getQueueItem()->getProgress(); $progress->setMessage("Building cPanel Backup Archive"); $progress->setSubMessage(""); $progress->setTotalSubItems($data->total_size); $progress->setCurrentSubItem($data->total_size - $data->current_pos); $this->getTask()->getQueueItem()->save(); $this->getTask()->checkExecutionTime(function() use ($data) { $progress = $this->getTask()->getQueueItem()->getProgress(); $progress->setMessage("Waiting for next cron iteration"); $progress->setTotalSubItems($data->total_size); $progress->setCurrentSubItem($data->total_size - $data->current_pos); $this->getTask()->getQueueItem()->save(); }); return false; }, Factory::getSettingsPerformance()->getReadChunkSizeBytes()); } catch(\Exception|ArchiveException $e) { //this will throw exception if the file has been changed more than 3 times $this->getLogController()->logError('[Export/cPanel] Error while trying to archive: ' . $e->getMessage()); } }); $archive->save(); } /** * @return void * @throws GzipException * @throws IOException * @throws InvalidArgumentException */ public function _compress():void { Gzip::compress( $this->_target, Gzip::DEFAULT_COMPRESS_CHUNK_SIZE, Gzip::DEFAULT_COMPRESSION_LEVEL, function($byteRead, $totalSize) { $progress = $this->getTask()->getQueueItem()->getProgress(); $progress->setSubMessage(''); $progress->setTotalSubItems($totalSize); $progress->setCurrentSubItem($byteRead); $this->getTask()->getQueueItem()->save(); $this->getTask()->checkExecutionTime(function() { $this->getTask()->getQueueItem()->getProgress()->setMessage('[ Gzip ] Waiting for next cron iteration'); $this->getTask()->getQueueItem()->save(); }); } ); } public function _createMySQLTimestemp():void { $this->getLogController()->logMessage("Creating MySQL timestemps file"); file_put_contents($this->_destination . JetBackup::SEP . 'mysql-timestamps' . JetBackup::SEP . 'mysql', time()); } public function _createMySQLAuthJson():void { $this->getLogController()->logMessage("Creating MySQL auth json file"); file_put_contents($this->_destination . JetBackup::SEP . 'mysql.sql-auth.json', json_encode([ $this->getDatabaseUser() => [ 'localhost' => [ 'auth_plugin' => "mysql_native_password", 'pass_hash' => $this->getHashedPassword(), ], ], $this->getUsername() => [ 'localhost' => [ 'pass_hash' => $this->getHashedPassword(), 'auth_plugin' => "mysql_native_password", ], ], ], JSON_PRETTY_PRINT)); } public function _createSkeleton():void { $this->getLogController()->logMessage("Creating skeleton"); $skeleton = [ 'apache_tls' => 0700, 'bandwidth' => 0700, 'bandwidth_db' => 0700, 'counters' => 0700, 'cp' => 0700, 'cron' => 0700, 'customizations' => 0700, 'dnssec_keys' => 0755, 'dnszones' => 0700, 'domainkeys' => 0700, 'domainkeys' . JetBackup::SEP . 'private' => 0700, 'domainkeys' . JetBackup::SEP . 'public' => 0700, 'homedir' => 0711, 'httpfiles' => 0700, 'ips' => 0700, 'locale' => 0700, 'logs' => 0700, 'mm' => 0700, 'mma' => 0700, 'mma' . JetBackup::SEP . 'priv' => 0700, 'mma' . JetBackup::SEP . 'pub' => 0700, 'mms' => 0700, 'mysql' => 0700, 'mysql-timestamps' => 0700, 'psql' => 0700, 'resellerconfig' => 0700, 'resellerfeatures' => 0700, 'resellerpackages' => 0700, 'ssl' => 0700, 'sslcerts' => 0700, 'sslkeys' => 0700, 'suspended' => 0700, 'suspendinfo' => 0700, 'team' => 0700, 'userconfig' => 0755, 'userdata' => 0700, 'va' => 0700, 'vad' => 0700, 'vf' => 0700, ]; foreach($skeleton as $directory => $mode) mkdir($this->_destination . JetBackup::SEP . $directory, $mode); } public function _createHomedirPaths():void { $this->getLogController()->logMessage("Creating homedir paths file"); file_put_contents($this->_destination . JetBackup::SEP . 'homedir_paths', JetBackup::SEP . "home" . JetBackup::SEP . $this->getUsername()); } public function _createShadow():void { $this->getLogController()->logMessage("Creating shadow file"); file_put_contents($this->_destination . JetBackup::SEP . 'shadow', crypt(Util::generateRandomString(), '$6$' . uniqid())); } public function _createAutoSSL():void { $this->getLogController()->logMessage("Creating auto SSL configuration file"); file_put_contents($this->_destination . JetBackup::SEP . 'autossl.json', json_encode([ 'excluded_domains' => [] ])); } public function _createVersion():void { file_put_contents($this->_destination . JetBackup::SEP . 'version', "pkgacct version: 10" . PHP_EOL . "archive version: 4"); } public function _createHomedir():void { $this->getLogController()->logMessage("Moving homedir to location"); $public_folder = $this->_destination . JetBackup::SEP . 'homedir' . JetBackup::SEP . 'public_html'; if($nested_folder = $this->getDomainSubFolder()) $public_folder .= JetBackup::SEP . $nested_folder; chmod($this->getHomedir(), 0750); rename($this->getHomedir(), $public_folder); } /** * @return void * @throws ExportException * * By default, JetBackup plugin is excluded during backup (to prevent issues during restore, not to 'cut our own branch') * For an export package, we want to include JetBackup plugin as part of the package * */ public function _createJetBackupPlugin():void { $this->getLogController()->logMessage("Adding JetBackup plugin to the package"); $public_folder = $this->_destination . JetBackup::SEP . 'homedir' . JetBackup::SEP . 'public_html'; if($nested_folder = $this->getDomainSubFolder()) $public_folder .= JetBackup::SEP . $nested_folder; $plugin_local_path = Factory::getWPHelper()->getWordPressHomedir() . Wordpress::WP_CONTENT . JetBackup::SEP . Wordpress::WP_PLUGINS . JetBackup::SEP . JetBackup::PLUGIN_NAME; $public_folder .= JetBackup::SEP . Wordpress::WP_CONTENT . JetBackup::SEP . Wordpress::WP_PLUGINS . JetBackup::SEP . JetBackup::PLUGIN_NAME; if (!file_exists($plugin_local_path)) throw new ExportException("Plugin local path not found [$plugin_local_path]"); if (!file_exists($public_folder)) mkdir($public_folder, 0755, true); try { Util::cp($plugin_local_path, $public_folder, 0755, ['config.php']); } catch (Exception $e) { throw new ExportException($e->getMessage()); } } public function _createMySQLDump():void { $this->getLogController()->logMessage("Creating mysql dump file"); $this->getTask()->foreach($this->getDatabaseTables(), function($i, $table_path) { $total_size = sizeof($this->getDatabaseTables()); $progress = $this->getTask()->getQueueItem()->getProgress(); $progress->setMessage("Building SQL dump"); $progress->setSubMessage("Adding " . basename($table_path)); $progress->setTotalSubItems($total_size); $progress->setCurrentSubItem($i + 1); // Increment progress $this->getTask()->getQueueItem()->save(); $this->getTask()->checkExecutionTime(function() use ($table_path, $total_size, $i) { $progress = $this->getTask()->getQueueItem()->getProgress(); $progress->setSubMessage("Waiting for next cron iteration"); $progress->setTotalSubItems($total_size); $progress->setCurrentSubItem($i + 1); // Maintain progress state $this->getTask()->getQueueItem()->save(); }); $this->getLogController()->logDebug("[_createMySQLDump] $i/$total_size table_path: $table_path"); $this->getLogController()->logMessage("\t- Adding table " . basename($table_path) . " to dump file"); $dump_file = $this->_destination . JetBackup::SEP . 'mysql' . JetBackup::SEP . $this->getDatabase() . '.sql'; $this->getLogController()->logDebug("[_createMySQLDump] dump_file: " . $dump_file); $this->getTask()->fileMerge($table_path, $dump_file, 'combine_table_' . basename($table_path)); }, 'combine_tables'); } public function _createMySQLAuth():void { $this->getLogController()->logMessage("Creating MySQL auth file"); $output = "-- cPanel mysql backup" . PHP_EOL; $output .= "GRANT USAGE ON *.* TO '{$this->getUsername()}'@'localhost' IDENTIFIED BY PASSWORD '*{$this->getHashedPassword()}';" . PHP_EOL; $output .= "GRANT ALL PRIVILEGES ON `{$this->getDatabase()}`.* TO '{$this->getUsername()}'@'localhost';" . PHP_EOL; $output .= "GRANT USAGE ON *.* TO '{$this->getDatabaseUser()}'@'localhost' IDENTIFIED BY PASSWORD '*{$this->getHashedPassword()}';" . PHP_EOL; $output .= "GRANT ALL PRIVILEGES ON `{$this->getDatabase()}`.* TO '{$this->getDatabaseUser()}'@'localhost';" . PHP_EOL; file_put_contents($this->_destination . JetBackup::SEP . 'mysql.sql', $output); } public function _createMySQLCreate():void { $this->getLogController()->logMessage("Creating MySQL create file"); $dump = []; $dump[] = "-- MySQL dump 10.13 Distrib 8.0.36, for Linux (x86_64)"; $dump[] = "--"; $dump[] = "-- Host: localhost Database: {$this->getDatabase()}"; $dump[] = "-- ------------------------------------------------------"; $dump[] = "-- Server version 8.0.36"; $dump[] = ""; $dump[] = "/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;"; $dump[] = "/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;"; $dump[] = "/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;"; $dump[] = "/*!50503 SET NAMES utf8mb4 */;"; $dump[] = "/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;"; $dump[] = "/*!40103 SET TIME_ZONE='+00:00' */;"; $dump[] = "/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;"; $dump[] = "/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;"; $dump[] = "/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;"; $dump[] = "/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;"; $dump[] = ""; $dump[] = "--"; $dump[] = "-- Current Database: `{$this->getDatabase()}`"; $dump[] = "--"; $dump[] = ""; $dump[] = "CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$this->getDatabase()}` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci */ /*!80016 DEFAULT ENCRYPTION='N' */;"; $dump[] = ""; $dump[] = "USE `{$this->getDatabase()}`;"; $dump[] = "/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;"; $dump[] = ""; $dump[] = "/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;"; $dump[] = "/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;"; $dump[] = "/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;"; $dump[] = "/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;"; $dump[] = "/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;"; $dump[] = "/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;"; $dump[] = "/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;"; $dump[] = "\n"; $dump[] = "-- Dump completed on " . date("Y-m-d H:i:s"); file_put_contents($this->_destination . JetBackup::SEP . 'mysql' . JetBackup::SEP . $this->getDatabase() . '.create', implode(PHP_EOL, $dump)); } public function _createUserData():void { $this->getLogController()->logMessage("Creating user data configuration file"); $timestamp = time(); $user_data = [ 'BACKUP' => 1, 'BWLIMIT' => 31457280000, 'CHILD_WORKLOADS' => '', 'CONTACTEMAIL' => $this->getEmailAddress(), 'CONTACTEMAIL2' => '', 'CREATED_IN_VERSION' => self::CPANEL_VERSION, 'DEMO' => 0, 'DISK_BLOCK_LIMIT' => 0, 'DNS' => $this->getDomainOnly(), 'FEATURELIST' => 'default', 'HASCGI' => 0, 'HASDKIM' => 0, 'HASSPF' => 0, 'LEGACY_BACKUP' => 0, 'LOCALE' => 'en', 'MAILBOX_FORMAT' => 'maildir', 'MAXADDON' => 0, 'MAXFTP' => 'unlimited', 'MAXLST' => 'unlimited', 'MAXPARK' => 0, 'MAXPASSENGERAPPS' => 4, 'MAXPOP' => 'unlimited', 'MAXSQL' => 'unlimited', 'MAXSUB' => 'unlimited', 'MAX_DEFER_FAIL_PERCENTAGE' => 100, 'MAX_EMAILACCT_QUOTA' => 'unlimited', 'MAX_TEAM_USERS' => 7, 'MTIME' => $timestamp, 'MXCHECK-' . $this->getDomainOnly() => 0, 'OWNER' => 'root', 'PLAN' => 'default', 'RS' => 'jupiter', 'SSL_DEFAULT_KEY_TYPE' => 'system', 'STARTDATE' => $timestamp, 'TRANSFERRED_OR_RESTORED' => 0, 'USER' => $this->getUsername(), 'UTF8MAILBOX' => 1, ]; $output = []; foreach ($user_data as $key => $value) $output[] = "$key=$value"; file_put_contents($this->_destination . JetBackup::SEP . 'cp' . JetBackup::SEP . $this->getUsername(), implode("\n", $output)); } }