🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
126 lines
5.0 KiB
PHP
Executable File
126 lines
5.0 KiB
PHP
Executable File
<?php
|
|
|
|
namespace App\RSpade\Commands\Migrate;
|
|
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Symfony\Component\Process\Process;
|
|
use Symfony\Component\Process\Exception\ProcessFailedException;
|
|
|
|
class Migrate_Rollback_Command extends Command
|
|
{
|
|
use PrivilegedCommandTrait;
|
|
protected $signature = 'migrate:rollback';
|
|
protected $description = 'Rollback the database to the snapshot taken at migrate:begin';
|
|
|
|
protected $mysql_data_dir = '/var/lib/mysql';
|
|
protected $backup_dir = '/var/lib/mysql_backup';
|
|
protected $flag_file = '/var/www/html/.migrating';
|
|
|
|
public function handle()
|
|
{
|
|
// Check if in migration mode
|
|
if (!file_exists($this->flag_file)) {
|
|
$this->error('[WARNING] No migration session in progress!');
|
|
$this->info('Run "php artisan migrate:begin" to start a migration session.');
|
|
return 1;
|
|
}
|
|
|
|
// Check if backup exists
|
|
if (!is_dir($this->backup_dir)) {
|
|
$this->error('[ERROR] Backup directory not found!');
|
|
$this->info('The backup may have been corrupted or deleted.');
|
|
$this->warn('You may need to manually restore from a different backup.');
|
|
|
|
// Remove flag file since backup is gone
|
|
unlink($this->flag_file);
|
|
return 1;
|
|
}
|
|
|
|
$this->warn('[WARNING] Restoring database to the snapshot taken at migrate:begin.');
|
|
$this->info(' Starting database rollback...');
|
|
|
|
try {
|
|
// Step 1: Stop MySQL using supervisorctl
|
|
$this->info('[1] Stopping MySQL server...');
|
|
$this->shell_exec_privileged('supervisorctl stop mysql 2>&1');
|
|
|
|
// Wait a moment for process to die
|
|
sleep(3);
|
|
|
|
// Step 2: Clear current MySQL data
|
|
$this->info('[2] Clearing current database data...');
|
|
// Use shell_exec to clear directory contents instead of removing directory
|
|
$this->shell_exec_privileged("rm -rf {$this->mysql_data_dir}/* 2>/dev/null");
|
|
$this->shell_exec_privileged("rm -rf {$this->mysql_data_dir}/.* 2>/dev/null");
|
|
|
|
// Step 3: Restore backup
|
|
$this->info('[3] Restoring database snapshot...');
|
|
$this->shell_exec_privileged("cp -r {$this->backup_dir}/* {$this->mysql_data_dir}/");
|
|
$this->shell_exec_privileged("cp -r {$this->backup_dir}/.[^.]* {$this->mysql_data_dir}/ 2>/dev/null");
|
|
|
|
// Step 4: Fix permissions (MySQL needs to own the files)
|
|
$this->info('[4] Setting correct permissions...');
|
|
$this->run_privileged_command(['chown', '-R', 'mysql:mysql', $this->mysql_data_dir]);
|
|
|
|
// Step 5: Start MySQL using supervisorctl
|
|
$this->info('[5] Starting MySQL server...');
|
|
$this->shell_exec_privileged('supervisorctl start mysql 2>&1');
|
|
|
|
// Step 6: Wait for MySQL to be ready
|
|
$this->info('[6] Waiting for MySQL to be ready...');
|
|
$this->wait_for_mysql_ready();
|
|
|
|
// Step 7: Keep backup and flag - stay in migration mode
|
|
$this->info('[7] Rollback complete...');
|
|
|
|
// Success
|
|
$this->info('');
|
|
$this->info('[OK] Database successfully rolled back to snapshot!');
|
|
$this->info('');
|
|
$this->line('The database has been restored to the snapshot state.');
|
|
$this->warn('[WARNING] You are still in migration mode with the same snapshot.');
|
|
$this->info('');
|
|
$this->line('Your options:');
|
|
$this->line(' • Fix your migration files and run "php artisan migrate" to try again');
|
|
$this->line(' • Run "php artisan migrate:commit" to exit migration mode');
|
|
$this->info('');
|
|
$this->line('The snapshot remains available for additional rollbacks if needed.');
|
|
|
|
return 0;
|
|
|
|
} catch (\Exception $e) {
|
|
$this->error('[ERROR] Rollback failed: ' . $e->getMessage());
|
|
$this->error('Manual intervention may be required!');
|
|
|
|
// Try to restart MySQL
|
|
$this->info('Attempting to restart MySQL...');
|
|
$this->shell_exec_privileged('supervisorctl start mysql 2>&1');
|
|
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Wait for MySQL to be ready for connections
|
|
*/
|
|
protected function wait_for_mysql_ready(): void
|
|
{
|
|
$max_attempts = 120;
|
|
$attempt = 0;
|
|
|
|
while ($attempt < $max_attempts) {
|
|
// Use mysql client to test connection (similar to user's approach)
|
|
$result = shell_exec("echo \"SELECT 'test';\" | mysql -uroot 2>/dev/null | grep test");
|
|
|
|
if ($result !== null && str_contains($result, 'test')) {
|
|
return;
|
|
}
|
|
|
|
sleep(1);
|
|
$attempt++;
|
|
}
|
|
|
|
throw new \Exception('MySQL did not start within 120 seconds');
|
|
}
|
|
} |