Skip to content

Commit 00451b7

Browse files
committed
fix(backup): add missing error captures and improve error code consistency
- Fix indentation inconsistencies in backup functions - Change unsupported site type error code from 1003 to 1002 for consistency - Use consistent error codes for fatal errors (5001) and interruptions (6000) - Improve rclone backend error message to show actual backend name Add error captures for backup operations: - Site files archive creation failure (3002) - WordPress content archive creation failure (3002) - Nginx configuration archive creation failure (3002) - PHP configuration archive creation failure (3002) - Database dump failure (4002) - Database SQL compression failure (3002) Error code reference: - 1xxx: Validation errors (1001, 1002) - 2xxx: Configuration errors (2001, 2002, 2003, 2010, 2011) - 3xxx: Filesystem/archive errors (3001, 3002) - 4xxx: Network/database errors (4001, 4002) - 5xxx: PHP fatal errors (5001) - 6xxx: Process interruptions (6000)
1 parent f80580c commit 00451b7

File tree

1 file changed

+125
-43
lines changed

1 file changed

+125
-43
lines changed

src/helper/Site_Backup_Restore.php

Lines changed: 125 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -65,16 +65,16 @@ public function backup( $args, $assoc_args = [] ) {
6565
// Debug: Log the raw dash_auth value received
6666
EE::debug( 'Received --dash-auth value: ' . $dash_auth );
6767

68-
// Parse backup-id:backup-verification-token format
69-
$auth_parts = explode( ':', $dash_auth, 2 );
70-
if ( count( $auth_parts ) !== 2 || empty( $auth_parts[0] ) || empty( $auth_parts[1] ) ) {
71-
$this->capture_error(
72-
'Invalid --dash-auth format. Expected: backup-id:backup-verification-token',
73-
self::ERROR_TYPE_VALIDATION,
74-
1001
75-
);
76-
EE::error( 'Invalid --dash-auth format. Expected: backup-id:backup-verification-token' );
77-
}
68+
// Parse backup-id:backup-verification-token format
69+
$auth_parts = explode( ':', $dash_auth, 2 );
70+
if ( count( $auth_parts ) !== 2 || empty( $auth_parts[0] ) || empty( $auth_parts[1] ) ) {
71+
$this->capture_error(
72+
'Invalid --dash-auth format. Expected: backup-id:backup-verification-token',
73+
self::ERROR_TYPE_VALIDATION,
74+
1001
75+
);
76+
EE::error( 'Invalid --dash-auth format. Expected: backup-id:backup-verification-token' );
77+
}
7878

7979
// Check for ed-api-url configuration
8080
$ed_api_url = get_config_value( 'ed-api-url', '' );
@@ -117,7 +117,7 @@ public function backup( $args, $assoc_args = [] ) {
117117
$this->capture_error(
118118
sprintf( 'Backup is not supported for site type: %s', $this->site_data['site_type'] ),
119119
self::ERROR_TYPE_VALIDATION,
120-
1003
120+
1002
121121
);
122122
EE::error( 'Backup is not supported for this site type.' );
123123
}
@@ -152,7 +152,7 @@ public function backup( $args, $assoc_args = [] ) {
152152
/**
153153
* Shutdown handler to send failure callback to EasyDash if backup didn't complete.
154154
* This is called when script terminates (including via EE::error which calls exit).
155-
*
155+
*
156156
* Automatically captures fatal errors and interrupted processes if no error was
157157
* explicitly captured during backup execution.
158158
*/
@@ -165,7 +165,13 @@ public function dash_shutdown_handler() {
165165
$last_error = error_get_last();
166166

167167
// Check if this was a fatal PHP error
168-
if ( $last_error && in_array( $last_error['type'], [ E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR ] ) ) {
168+
if ( $last_error && in_array( $last_error['type'], [
169+
E_ERROR,
170+
E_PARSE,
171+
E_CORE_ERROR,
172+
E_COMPILE_ERROR,
173+
E_USER_ERROR
174+
] ) ) {
169175
$this->capture_error(
170176
sprintf(
171177
'PHP Fatal Error: %s in %s:%d',
@@ -174,14 +180,14 @@ public function dash_shutdown_handler() {
174180
$last_error['line']
175181
),
176182
self::ERROR_TYPE_FATAL,
177-
$last_error['type']
183+
5001
178184
);
179185
} else {
180186
// Script was killed, interrupted, or failed unexpectedly
181187
$this->capture_error(
182188
'Backup process was interrupted or killed unexpectedly',
183189
self::ERROR_TYPE_INTERRUPTED,
184-
0
190+
6000
185191
);
186192
}
187193
}
@@ -200,7 +206,7 @@ public function dash_shutdown_handler() {
200206
*
201207
* @param string $message Error message describing what went wrong.
202208
* @param string $type Error type category (use ERROR_TYPE_* constants).
203-
* @param int $code Error code for additional context (optional).
209+
* @param int $code Error code for additional context (optional).
204210
*/
205211
private function capture_error( $message, $type = self::ERROR_TYPE_UNKNOWN, $code = 0 ) {
206212
// Only capture the first error (root cause)
@@ -425,7 +431,17 @@ private function backup_site_dir( $backup_dir ) {
425431
$backup_file = $backup_dir . '/' . $this->site_data['site_url'] . '.zip';
426432
$backup_command = sprintf( 'cd %s && 7z a -mx=1 %s .', $site_dir, $backup_file );
427433

428-
EE::exec( $backup_command );
434+
$result = EE::exec( $backup_command );
435+
436+
// Check if archive was created successfully
437+
if ( ! $result || ! $this->fs->exists( $backup_file ) ) {
438+
$this->capture_error(
439+
'Failed to create site backup archive',
440+
self::ERROR_TYPE_FILESYSTEM,
441+
3002
442+
);
443+
EE::error( 'Failed to create backup archive. Please check disk space and file permissions.' );
444+
}
429445

430446
return $backup_file;
431447
}
@@ -454,7 +470,16 @@ private function backup_wp_content_dir( $backup_dir ) {
454470
}
455471

456472
$backup_command = sprintf( 'cd %s && 7z a -mx=1 %s wp-config.php', $site_dir . '/../', $backup_file );
457-
EE::exec( $backup_command );
473+
$result = EE::exec( $backup_command );
474+
475+
if ( ! $result ) {
476+
$this->capture_error(
477+
'Failed to create WordPress content backup archive',
478+
self::ERROR_TYPE_FILESYSTEM,
479+
3002
480+
);
481+
EE::error( 'Failed to create backup archive. Please check disk space and file permissions.' );
482+
}
458483

459484
// meta.json path
460485
$meta_file = $backup_dir . '/meta.json';
@@ -472,6 +497,16 @@ private function backup_wp_content_dir( $backup_dir ) {
472497
EE::exec( $backup_command );
473498
}
474499

500+
// Final check that backup file was created successfully
501+
if ( ! $this->fs->exists( $backup_file ) ) {
502+
$this->capture_error(
503+
'Failed to create WordPress content backup archive',
504+
self::ERROR_TYPE_FILESYSTEM,
505+
3002
506+
);
507+
EE::error( 'Failed to create backup archive. Please check disk space and file permissions.' );
508+
}
509+
475510
return $backup_file;
476511
}
477512

@@ -482,7 +517,16 @@ private function backup_nginx_conf( $backup_dir ) {
482517
$backup_file = $backup_dir . '/conf.zip';
483518
$backup_command = sprintf( 'cd %s && 7z a -mx=1 %s nginx', $conf_dir, $backup_file );
484519

485-
EE::exec( $backup_command );
520+
$result = EE::exec( $backup_command );
521+
522+
if ( ! $result ) {
523+
$this->capture_error(
524+
'Failed to create nginx configuration backup archive',
525+
self::ERROR_TYPE_FILESYSTEM,
526+
3002
527+
);
528+
EE::error( 'Failed to create nginx configuration backup archive. Please check disk space and file permissions.' );
529+
}
486530
}
487531

488532
private function backup_php_conf( $backup_dir ) {
@@ -492,7 +536,16 @@ private function backup_php_conf( $backup_dir ) {
492536
$backup_file = $backup_dir . '/conf.zip';
493537
$backup_command = sprintf( 'cd %s && 7z u -mx=1 %s php', $conf_dir, $backup_file );
494538

495-
EE::exec( $backup_command );
539+
$result = EE::exec( $backup_command );
540+
541+
if ( ! $result ) {
542+
$this->capture_error(
543+
'Failed to create PHP configuration backup archive',
544+
self::ERROR_TYPE_FILESYSTEM,
545+
3002
546+
);
547+
EE::error( 'Failed to create PHP configuration backup archive. Please check disk space and file permissions.' );
548+
}
496549
}
497550

498551
private function backup_html( $backup_dir ) {
@@ -541,10 +594,31 @@ private function backup_db( $backup_dir ) {
541594
$options = [ 'skip-tty' => true ];
542595

543596
EE::run_command( $args, $assoc_args, $options );
544-
EE::exec( sprintf( 'mv %s %s', EE_ROOT_DIR . '/sites/' . $this->site_data['site_url'] . '/app/htdocs/' . $sql_filename, $sql_file ) );
597+
598+
$sql_dump_path = EE_ROOT_DIR . '/sites/' . $this->site_data['site_url'] . '/app/htdocs/' . $sql_filename;
599+
600+
// Check if database dump was created successfully
601+
if ( ! $this->fs->exists( $sql_dump_path ) ) {
602+
$this->capture_error(
603+
sprintf( 'Database backup failed for database: %s', $db_name ),
604+
self::ERROR_TYPE_DATABASE,
605+
4002
606+
);
607+
EE::error( 'Database backup failed. Please check database credentials and connectivity.' );
608+
}
609+
610+
EE::exec( sprintf( 'mv %s %s', $sql_dump_path, $sql_file ) );
545611
$backup_command = sprintf( 'cd %s && 7z u -mx=1 %s sql', $backup_dir, $backup_file );
546612

547-
EE::exec( $backup_command );
613+
$result = EE::exec( $backup_command );
614+
if ( ! $result ) {
615+
$this->capture_error(
616+
'Failed to compress database backup into archive',
617+
self::ERROR_TYPE_FILESYSTEM,
618+
3002
619+
);
620+
EE::error( 'Failed to compress database backup. Please check disk space.' );
621+
}
548622
$this->fs->remove( $backup_dir . '/sql' );
549623
}
550624

@@ -751,16 +825,17 @@ private function pre_backup_restore_checks() {
751825
$command = 'rclone listremotes';
752826
$output = EE::launch( $command );
753827

754-
$rclone_path = get_config_value( 'rclone-path', 'easyengine:easyengine' );
755-
$rclone_path = explode( ':', $rclone_path )[0] . ':';
828+
$rclone_path = get_config_value( 'rclone-path', 'easyengine:easyengine' );
829+
$rclone_backend = explode( ':', $rclone_path )[0];
830+
$rclone_path = $rclone_backend . ':';
756831

757832
if ( strpos( $output->stdout, $rclone_path ) === false ) {
758833
$this->capture_error(
759-
'rclone backend easyengine does not exist',
834+
sprintf( 'rclone backend "%s" is not configured. Please create it using `rclone config`', $rclone_backend ),
760835
self::ERROR_TYPE_CONFIG,
761836
2002
762837
);
763-
EE::error( 'rclone backend easyengine does not exist. Please create it using `rclone config`' );
838+
EE::error( sprintf( 'rclone backend "%s" does not exist. Please create it using `rclone config`', $rclone_backend ) );
764839
}
765840

766841
$this->check_and_install( 'zip', 'zip' );
@@ -1217,7 +1292,7 @@ private function cleanup_old_backups() {
12171292
// Check if we have more backups than allowed
12181293
if ( count( $backups ) > ( $no_of_backups + 1 ) ) {
12191294
$backups_to_delete = array_slice( $backups, $no_of_backups );
1220-
1295+
12211296
EE::log( sprintf( 'Cleaning up old backups. Keeping %d most recent backups.', $no_of_backups ) );
12221297
foreach ( $backups_to_delete as $backup ) {
12231298
EE::log( 'Deleting old backup: ' . $backup );
@@ -1241,6 +1316,7 @@ private function cleanup_old_backups() {
12411316
private function rollback_failed_backup() {
12421317
if ( empty( $this->dash_new_backup_path ) ) {
12431318
EE::warning( 'Cannot rollback backup: backup path not found.' );
1319+
12441320
return;
12451321
}
12461322

@@ -1305,10 +1381,11 @@ private function restore_php_conf( $backup_dir ) {
13051381
/**
13061382
* Send success callback to EasyDash API after successful backup.
13071383
*
1308-
* @param string $ed_api_url The EasyDash API URL.
1309-
* @param string $backup_id The backup ID.
1310-
* @param string $verify_token The verification token.
1311-
* @param array $backup_metadata The backup metadata.
1384+
* @param string $ed_api_url The EasyDash API URL.
1385+
* @param string $backup_id The backup ID.
1386+
* @param string $verify_token The verification token.
1387+
* @param array $backup_metadata The backup metadata.
1388+
*
13121389
* @return bool True if API request succeeded, false otherwise.
13131390
*/
13141391
private function send_dash_success_callback( $ed_api_url, $backup_id, $verify_token, $backup_metadata ) {
@@ -1365,10 +1442,10 @@ private function send_dash_failure_callback( $ed_api_url, $backup_id, $verify_to
13651442
];
13661443

13671444
EE::debug( 'Sending failure callback with error details: ' . json_encode( [
1368-
'error_message' => $payload['error_message'],
1369-
'error_type' => $payload['error_type'],
1370-
'error_code' => $payload['error_code'],
1371-
] ) );
1445+
'error_message' => $payload['error_message'],
1446+
'error_type' => $payload['error_type'],
1447+
'error_code' => $payload['error_code'],
1448+
] ) );
13721449

13731450
$this->send_dash_request( $endpoint, $payload );
13741451
}
@@ -1377,14 +1454,15 @@ private function send_dash_failure_callback( $ed_api_url, $backup_id, $verify_to
13771454
* Send HTTP request to EasyEngine Dashboard API with retry logic for 5xx errors and connection errors.
13781455
*
13791456
* @param string $endpoint The API endpoint URL.
1380-
* @param array $payload The request payload.
1457+
* @param array $payload The request payload.
1458+
*
13811459
* @return bool True if request succeeded, false otherwise.
13821460
*/
13831461
private function send_dash_request( $endpoint, $payload ) {
1384-
$max_retries = 3;
1385-
$retry_delay = 300; // 5 minutes in seconds
1462+
$max_retries = 3;
1463+
$retry_delay = 300; // 5 minutes in seconds
13861464
$max_attempts = $max_retries + 1; // 1 initial attempt + 3 retries = 4 total
1387-
$attempt = 1;
1465+
$attempt = 1;
13881466

13891467
while ( $attempt <= $max_attempts ) {
13901468
$ch = curl_init( $endpoint );
@@ -1397,9 +1475,9 @@ private function send_dash_request( $endpoint, $payload ) {
13971475
] );
13981476
curl_setopt( $ch, CURLOPT_TIMEOUT, 30 );
13991477

1400-
$response = curl_exec( $ch );
1478+
$response = curl_exec( $ch );
14011479
$http_code = curl_getinfo( $ch, CURLINFO_HTTP_CODE );
1402-
$error = curl_error( $ch );
1480+
$error = curl_error( $ch );
14031481

14041482
curl_close( $ch );
14051483

@@ -1410,13 +1488,14 @@ private function send_dash_request( $endpoint, $payload ) {
14101488
if ( ! $error && $http_code >= 200 && $http_code < 300 ) {
14111489
EE::log( 'EasyEngine Dashboard callback sent successfully.' );
14121490
EE::debug( 'EasyEngine Dashboard response: ' . $response_text );
1491+
14131492
return true; // Success
14141493
}
14151494

14161495
// Determine if this is a retryable error
1417-
$is_5xx_error = $http_code >= 500 && $http_code < 600;
1496+
$is_5xx_error = $http_code >= 500 && $http_code < 600;
14181497
$is_connection_error = ! empty( $error ) || $http_code === 0;
1419-
$should_retry = ( $is_5xx_error || $is_connection_error ) && $attempt < $max_attempts;
1498+
$should_retry = ( $is_5xx_error || $is_connection_error ) && $attempt < $max_attempts;
14201499

14211500
if ( $should_retry ) {
14221501
// Retry on 5xx errors or connection errors
@@ -1470,6 +1549,7 @@ private function send_dash_request( $endpoint, $payload ) {
14701549
// 4xx or other HTTP error codes that shouldn't be retried
14711550
EE::warning( 'EasyEngine Dashboard callback returned HTTP ' . $http_code . '. Response: ' . $response_text );
14721551
}
1552+
14731553
return false; // Failure
14741554
}
14751555
}
@@ -1481,12 +1561,14 @@ private function send_dash_request( $endpoint, $payload ) {
14811561
* Sanitize count value for API payload.
14821562
*
14831563
* @param mixed $value The value to sanitize.
1564+
*
14841565
* @return int The sanitized integer value.
14851566
*/
14861567
private function sanitize_count( $value ) {
14871568
if ( $value === '-' || ! is_numeric( $value ) ) {
14881569
return 0;
14891570
}
1571+
14901572
return intval( $value );
14911573
}
14921574
}

0 commit comments

Comments
 (0)