PluginProbe ʕ •ᴥ•ʔ
JetBackup – Backup, Restore & Migrate / trunk
JetBackup – Backup, Restore & Migrate vtrunk
3.1.22.3 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.8.1 1.4.9 1.5.0 1.5.1 1.5.1.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.6.0 1.6.10 1.6.11 1.6.12 1.6.13 1.6.15 1.6.5.1 1.6.8.8 1.6.9 1.6.9.1 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7.5 2.0.8.7 2.0.9.11 2.0.9.14 2.0.9.15 2.0.9.6 2.0.9.7 2.0.9.9 3.1.10.7 3.1.11.1 3.1.12.3 3.1.13.4 3.1.14.17 3.1.15.4 3.1.16.1 3.1.17.5 3.1.18.10 3.1.18.8 3.1.18.9 3.1.19.8 3.1.20.3 3.1.21.3 3.1.7.9 3.1.9.2 trunk 1.1.90 1.1.91 1.2.0 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 1.4.2
backup / src / JetBackup / Export / Vendor / CPanel.php
backup / src / JetBackup / Export / Vendor Last commit date
.htaccess 1 year ago CPanel.php 1 year ago DirectAdmin.php 1 year ago Vendor.php 1 year ago index.html 1 year ago web.config 1 year ago
CPanel.php
471 lines
1 <?php
2
3 namespace JetBackup\Export\Vendor;
4
5 use Exception;
6 use JetBackup\Archive\Archive;
7 use JetBackup\Archive\Gzip;
8 use JetBackup\DirIterator\DirIterator;
9 use JetBackup\Entities\Util;
10 use JetBackup\Exception\ArchiveException;
11 use JetBackup\Exception\DirIteratorFileVanishedException;
12 use JetBackup\Exception\ExportException;
13 use JetBackup\Exception\GzipException;
14 use JetBackup\Factory;
15 use JetBackup\JetBackup;
16 use JetBackup\Wordpress\Wordpress;
17 use SleekDB\Exceptions\InvalidArgumentException;
18 use SleekDB\Exceptions\IOException;
19
20 if (!defined( '__JETBACKUP__')) die('Direct access is not allowed');
21
22 class CPanel extends Vendor {
23
24 const CPANEL_VERSION = '11.116.0.11';
25
26 private string $_destination;
27 private string $_target;
28
29 public function _build():string {
30
31 $this->_destination = $this->getDestination() . JetBackup::SEP . 'tmp';
32 $this->_target = $this->getDestination() . JetBackup::SEP . 'cpmove-' . $this->getUsername() . Archive::ARCHIVE_EXT;
33
34 if(!file_exists($this->_destination)) mkdir($this->_destination, 0700);
35
36 /**
37 * Structure
38 * Filename: cpmove-username.tar.gz
39 *
40 * |-- apache_tls/
41 * |-- bandwidth/
42 * |-- bandwidth_db/
43 * |-- counters/
44 * |-- cp/
45 * | +-- username
46 * |-- cron/
47 * |-- customizations/
48 * |-- dnssec_keys/
49 * |-- dnszones/
50 * |-- domainkeys/
51 * | |-- private/
52 * | +-- public/
53 * |-- homedir/
54 * | +-- public_html/
55 * |-- httpfiles/
56 * |-- ips/
57 * |-- locale/
58 * |-- logs/
59 * |-- mm/
60 * |-- mma/
61 * | |-- priv/
62 * | +-- pub/
63 * |-- mms/
64 * |-- mysql/
65 * | |-- database.create
66 * | +-- database.sql
67 * |-- mysql-timestamps/
68 * | +-- mysql
69 * |-- psql/
70 * |-- resellerconfig/
71 * |-- resellerfeatures/
72 * |-- resellerpackages/
73 * |-- ssl/
74 * |-- sslcerts/
75 * |-- sslkeys/
76 * |-- suspended/
77 * |-- suspendinfo/
78 * |-- team/
79 * |-- userconfig/
80 * |-- userdata/
81 * |-- va/
82 * |-- vad/
83 * |-- vf/
84 * |-- autossl.json
85 * |-- homedir_paths
86 * |-- mysql.sql
87 * |-- mysql.sql-auth.json
88 * |-- shadow
89 * +-- version
90 */
91 $this->getTask()->func([$this, '_createSkeleton']);
92 $this->getTask()->func([$this, '_createHomedir']);
93 $this->getTask()->func([$this, '_createJetBackupPlugin']);
94 $this->getTask()->func([$this, '_createVersion']);
95 $this->getTask()->func([$this, '_createAutoSSL']);
96 $this->getTask()->func([$this, '_createUserData']);
97 $this->getTask()->func([$this, '_createShadow']);
98 $this->getTask()->func([$this, '_createHomedirPaths']);
99 $this->getTask()->func([$this, '_createMySQLTimestemp']);
100 $this->getTask()->func([$this, '_createMySQLAuth']);
101 $this->getTask()->func([$this, '_createMySQLAuthJson']);
102 $this->getTask()->func([$this, '_createMySQLCreate']);
103 $this->getTask()->func([$this, '_createMySQLDump']);
104 $this->getTask()->func([$this, '_archive']);
105 $this->getTask()->func([$this, '_compress']);
106
107 return $this->_target . '.gz';
108 }
109
110 public function _archive():void {
111
112 $archive = new Archive($this->_target, false, Archive::OPT_SPARSE, 0, $this->getDestination());
113 $archive->setLogController($this->getLogController());
114
115 $this->getTask()->scan($this->_destination, function(DirIterator $scan, $data) use ($archive) {
116
117 if (!$data->total_size) throw new ArchiveException('Invalid total tree size');
118
119 $archive->setAppend(!($data->total_size == $data->current_pos));
120
121 try {
122 $fd = $archive->getFileFD();
123 $current_file = $scan->next($fd ? $fd->tell() : 0);
124 } catch (DirIteratorFileVanishedException $e) {
125 $this->getLogController()->logMessage('[ WARNING ] File Vanished : ' . $e->getMessage());
126 return;
127 }
128
129 if($scan->getSource() == $current_file->getName()) return;
130
131 $this->getLogController()->logMessage("[". ($data->total_size - $data->current_pos)."/$data->total_size] Archiving: {$current_file->getName()}");
132
133 try {
134
135 $file = substr($current_file->getName(), strlen($this->_destination)+1);
136 $archive->appendFileChunked($current_file, $file, function() use ($data) {
137
138 $progress = $this->getTask()->getQueueItem()->getProgress();
139 $progress->setMessage("Building cPanel Backup Archive");
140 $progress->setSubMessage("");
141 $progress->setTotalSubItems($data->total_size);
142 $progress->setCurrentSubItem($data->total_size - $data->current_pos);
143 $this->getTask()->getQueueItem()->save();
144
145 $this->getTask()->checkExecutionTime(function() use ($data) {
146
147 $progress = $this->getTask()->getQueueItem()->getProgress();
148 $progress->setMessage("Waiting for next cron iteration");
149 $progress->setTotalSubItems($data->total_size);
150 $progress->setCurrentSubItem($data->total_size - $data->current_pos);
151 $this->getTask()->getQueueItem()->save();
152
153 });
154
155 return false;
156 }, Factory::getSettingsPerformance()->getReadChunkSizeBytes());
157 } catch(\Exception|ArchiveException $e) {
158 //this will throw exception if the file has been changed more than 3 times
159 $this->getLogController()->logError('[Export/cPanel] Error while trying to archive: ' . $e->getMessage());
160 }
161 });
162
163 $archive->save();
164
165 }
166
167 /**
168 * @return void
169 * @throws GzipException
170 * @throws IOException
171 * @throws InvalidArgumentException
172 */
173 public function _compress():void {
174
175 Gzip::compress(
176 $this->_target,
177 Gzip::DEFAULT_COMPRESS_CHUNK_SIZE,
178 Gzip::DEFAULT_COMPRESSION_LEVEL,
179 function($byteRead, $totalSize) {
180
181 $progress = $this->getTask()->getQueueItem()->getProgress();
182 $progress->setSubMessage('');
183 $progress->setTotalSubItems($totalSize);
184 $progress->setCurrentSubItem($byteRead);
185
186 $this->getTask()->getQueueItem()->save();
187
188 $this->getTask()->checkExecutionTime(function() {
189 $this->getTask()->getQueueItem()->getProgress()->setMessage('[ Gzip ] Waiting for next cron iteration');
190 $this->getTask()->getQueueItem()->save();
191 });
192 }
193 );
194
195 }
196
197 public function _createMySQLTimestemp():void {
198 $this->getLogController()->logMessage("Creating MySQL timestemps file");
199 file_put_contents($this->_destination . JetBackup::SEP . 'mysql-timestamps' . JetBackup::SEP . 'mysql', time());
200 }
201
202 public function _createMySQLAuthJson():void {
203 $this->getLogController()->logMessage("Creating MySQL auth json file");
204 file_put_contents($this->_destination . JetBackup::SEP . 'mysql.sql-auth.json', json_encode([
205 $this->getDatabaseUser() => [
206 'localhost' => [
207 'auth_plugin' => "mysql_native_password",
208 'pass_hash' => $this->getHashedPassword(),
209 ],
210 ],
211 $this->getUsername() => [
212 'localhost' => [
213 'pass_hash' => $this->getHashedPassword(),
214 'auth_plugin' => "mysql_native_password",
215 ],
216 ],
217 ], JSON_PRETTY_PRINT));
218 }
219
220 public function _createSkeleton():void {
221
222 $this->getLogController()->logMessage("Creating skeleton");
223
224 $skeleton = [
225 'apache_tls' => 0700,
226 'bandwidth' => 0700,
227 'bandwidth_db' => 0700,
228 'counters' => 0700,
229 'cp' => 0700,
230 'cron' => 0700,
231 'customizations' => 0700,
232 'dnssec_keys' => 0755,
233 'dnszones' => 0700,
234 'domainkeys' => 0700,
235 'domainkeys' . JetBackup::SEP . 'private' => 0700,
236 'domainkeys' . JetBackup::SEP . 'public' => 0700,
237 'homedir' => 0711,
238 'httpfiles' => 0700,
239 'ips' => 0700,
240 'locale' => 0700,
241 'logs' => 0700,
242 'mm' => 0700,
243 'mma' => 0700,
244 'mma' . JetBackup::SEP . 'priv' => 0700,
245 'mma' . JetBackup::SEP . 'pub' => 0700,
246 'mms' => 0700,
247 'mysql' => 0700,
248 'mysql-timestamps' => 0700,
249 'psql' => 0700,
250 'resellerconfig' => 0700,
251 'resellerfeatures' => 0700,
252 'resellerpackages' => 0700,
253 'ssl' => 0700,
254 'sslcerts' => 0700,
255 'sslkeys' => 0700,
256 'suspended' => 0700,
257 'suspendinfo' => 0700,
258 'team' => 0700,
259 'userconfig' => 0755,
260 'userdata' => 0700,
261 'va' => 0700,
262 'vad' => 0700,
263 'vf' => 0700,
264 ];
265
266 foreach($skeleton as $directory => $mode) mkdir($this->_destination . JetBackup::SEP . $directory, $mode);
267 }
268
269 public function _createHomedirPaths():void {
270 $this->getLogController()->logMessage("Creating homedir paths file");
271 file_put_contents($this->_destination . JetBackup::SEP . 'homedir_paths', JetBackup::SEP . "home" . JetBackup::SEP . $this->getUsername());
272 }
273
274 public function _createShadow():void {
275 $this->getLogController()->logMessage("Creating shadow file");
276 file_put_contents($this->_destination . JetBackup::SEP . 'shadow', crypt(Util::generateRandomString(), '$6$' . uniqid()));
277 }
278
279 public function _createAutoSSL():void {
280 $this->getLogController()->logMessage("Creating auto SSL configuration file");
281 file_put_contents($this->_destination . JetBackup::SEP . 'autossl.json', json_encode([ 'excluded_domains' => [] ]));
282 }
283
284 public function _createVersion():void {
285 file_put_contents($this->_destination . JetBackup::SEP . 'version', "pkgacct version: 10" . PHP_EOL . "archive version: 4");
286 }
287
288 public function _createHomedir():void {
289
290 $this->getLogController()->logMessage("Moving homedir to location");
291 $public_folder = $this->_destination . JetBackup::SEP . 'homedir' . JetBackup::SEP . 'public_html';
292 if($nested_folder = $this->getDomainSubFolder()) $public_folder .= JetBackup::SEP . $nested_folder;
293
294 chmod($this->getHomedir(), 0750);
295 rename($this->getHomedir(), $public_folder);
296 }
297
298 /**
299 * @return void
300 * @throws ExportException
301 *
302 * By default, JetBackup plugin is excluded during backup (to prevent issues during restore, not to 'cut our own branch')
303 * For an export package, we want to include JetBackup plugin as part of the package
304 *
305 */
306 public function _createJetBackupPlugin():void {
307
308 $this->getLogController()->logMessage("Adding JetBackup plugin to the package");
309 $public_folder = $this->_destination . JetBackup::SEP . 'homedir' . JetBackup::SEP . 'public_html';
310 if($nested_folder = $this->getDomainSubFolder()) $public_folder .= JetBackup::SEP . $nested_folder;
311
312 $plugin_local_path = Factory::getWPHelper()->getWordPressHomedir() .
313 Wordpress::WP_CONTENT . JetBackup::SEP .
314 Wordpress::WP_PLUGINS . JetBackup::SEP .
315 JetBackup::PLUGIN_NAME;
316
317 $public_folder .= JetBackup::SEP . Wordpress::WP_CONTENT . JetBackup::SEP .
318 Wordpress::WP_PLUGINS . JetBackup::SEP .
319 JetBackup::PLUGIN_NAME;
320
321 if (!file_exists($plugin_local_path)) throw new ExportException("Plugin local path not found [$plugin_local_path]");
322 if (!file_exists($public_folder)) mkdir($public_folder, 0755, true);
323
324 try {
325 Util::cp($plugin_local_path, $public_folder, 0755, ['config.php']);
326 } catch (Exception $e) {
327 throw new ExportException($e->getMessage());
328 }
329
330 }
331
332 public function _createMySQLDump():void {
333
334 $this->getLogController()->logMessage("Creating mysql dump file");
335
336
337 $this->getTask()->foreach($this->getDatabaseTables(), function($i, $table_path) {
338
339 $total_size = sizeof($this->getDatabaseTables());
340 $progress = $this->getTask()->getQueueItem()->getProgress();
341 $progress->setMessage("Building SQL dump");
342 $progress->setSubMessage("Adding " . basename($table_path));
343 $progress->setTotalSubItems($total_size);
344 $progress->setCurrentSubItem($i + 1); // Increment progress
345 $this->getTask()->getQueueItem()->save();
346
347 $this->getTask()->checkExecutionTime(function() use ($table_path, $total_size, $i) {
348
349 $progress = $this->getTask()->getQueueItem()->getProgress();
350 $progress->setSubMessage("Waiting for next cron iteration");
351 $progress->setTotalSubItems($total_size);
352 $progress->setCurrentSubItem($i + 1); // Maintain progress state
353 $this->getTask()->getQueueItem()->save();
354
355 });
356
357 $this->getLogController()->logDebug("[_createMySQLDump] $i/$total_size table_path: $table_path");
358 $this->getLogController()->logMessage("\t- Adding table " . basename($table_path) . " to dump file");
359
360 $dump_file = $this->_destination . JetBackup::SEP . 'mysql' . JetBackup::SEP . $this->getDatabase() . '.sql';
361 $this->getLogController()->logDebug("[_createMySQLDump] dump_file: " . $dump_file);
362 $this->getTask()->fileMerge($table_path, $dump_file, 'combine_table_' . basename($table_path));
363 }, 'combine_tables');
364
365 }
366
367 public function _createMySQLAuth():void {
368 $this->getLogController()->logMessage("Creating MySQL auth file");
369
370 $output = "-- cPanel mysql backup" . PHP_EOL;
371 $output .= "GRANT USAGE ON *.* TO '{$this->getUsername()}'@'localhost' IDENTIFIED BY PASSWORD '*{$this->getHashedPassword()}';" . PHP_EOL;
372 $output .= "GRANT ALL PRIVILEGES ON `{$this->getDatabase()}`.* TO '{$this->getUsername()}'@'localhost';" . PHP_EOL;
373 $output .= "GRANT USAGE ON *.* TO '{$this->getDatabaseUser()}'@'localhost' IDENTIFIED BY PASSWORD '*{$this->getHashedPassword()}';" . PHP_EOL;
374 $output .= "GRANT ALL PRIVILEGES ON `{$this->getDatabase()}`.* TO '{$this->getDatabaseUser()}'@'localhost';" . PHP_EOL;
375 file_put_contents($this->_destination . JetBackup::SEP . 'mysql.sql', $output);
376 }
377
378 public function _createMySQLCreate():void {
379
380 $this->getLogController()->logMessage("Creating MySQL create file");
381
382 $dump = [];
383 $dump[] = "-- MySQL dump 10.13 Distrib 8.0.36, for Linux (x86_64)";
384 $dump[] = "--";
385 $dump[] = "-- Host: localhost Database: {$this->getDatabase()}";
386 $dump[] = "-- ------------------------------------------------------";
387 $dump[] = "-- Server version 8.0.36";
388 $dump[] = "";
389 $dump[] = "/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;";
390 $dump[] = "/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;";
391 $dump[] = "/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;";
392 $dump[] = "/*!50503 SET NAMES utf8mb4 */;";
393 $dump[] = "/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;";
394 $dump[] = "/*!40103 SET TIME_ZONE='+00:00' */;";
395 $dump[] = "/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;";
396 $dump[] = "/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;";
397 $dump[] = "/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;";
398 $dump[] = "/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;";
399 $dump[] = "";
400 $dump[] = "--";
401 $dump[] = "-- Current Database: `{$this->getDatabase()}`";
402 $dump[] = "--";
403 $dump[] = "";
404 $dump[] = "CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$this->getDatabase()}` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci */ /*!80016 DEFAULT ENCRYPTION='N' */;";
405 $dump[] = "";
406 $dump[] = "USE `{$this->getDatabase()}`;";
407 $dump[] = "/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;";
408 $dump[] = "";
409 $dump[] = "/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;";
410 $dump[] = "/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;";
411 $dump[] = "/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;";
412 $dump[] = "/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;";
413 $dump[] = "/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;";
414 $dump[] = "/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;";
415 $dump[] = "/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;";
416 $dump[] = "\n";
417 $dump[] = "-- Dump completed on " . date("Y-m-d H:i:s");
418
419 file_put_contents($this->_destination . JetBackup::SEP . 'mysql' . JetBackup::SEP . $this->getDatabase() . '.create', implode(PHP_EOL, $dump));
420 }
421
422 public function _createUserData():void {
423
424 $this->getLogController()->logMessage("Creating user data configuration file");
425
426 $timestamp = time();
427 $user_data = [
428 'BACKUP' => 1,
429 'BWLIMIT' => 31457280000,
430 'CHILD_WORKLOADS' => '',
431 'CONTACTEMAIL' => $this->getEmailAddress(),
432 'CONTACTEMAIL2' => '',
433 'CREATED_IN_VERSION' => self::CPANEL_VERSION,
434 'DEMO' => 0,
435 'DISK_BLOCK_LIMIT' => 0,
436 'DNS' => $this->getDomainOnly(),
437 'FEATURELIST' => 'default',
438 'HASCGI' => 0,
439 'HASDKIM' => 0,
440 'HASSPF' => 0,
441 'LEGACY_BACKUP' => 0,
442 'LOCALE' => 'en',
443 'MAILBOX_FORMAT' => 'maildir',
444 'MAXADDON' => 0,
445 'MAXFTP' => 'unlimited',
446 'MAXLST' => 'unlimited',
447 'MAXPARK' => 0,
448 'MAXPASSENGERAPPS' => 4,
449 'MAXPOP' => 'unlimited',
450 'MAXSQL' => 'unlimited',
451 'MAXSUB' => 'unlimited',
452 'MAX_DEFER_FAIL_PERCENTAGE' => 100,
453 'MAX_EMAILACCT_QUOTA' => 'unlimited',
454 'MAX_TEAM_USERS' => 7,
455 'MTIME' => $timestamp,
456 'MXCHECK-' . $this->getDomainOnly() => 0,
457 'OWNER' => 'root',
458 'PLAN' => 'default',
459 'RS' => 'jupiter',
460 'SSL_DEFAULT_KEY_TYPE' => 'system',
461 'STARTDATE' => $timestamp,
462 'TRANSFERRED_OR_RESTORED' => 0,
463 'USER' => $this->getUsername(),
464 'UTF8MAILBOX' => 1,
465 ];
466
467 $output = [];
468 foreach ($user_data as $key => $value) $output[] = "$key=$value";
469 file_put_contents($this->_destination . JetBackup::SEP . 'cp' . JetBackup::SEP . $this->getUsername(), implode("\n", $output));
470 }
471 }