Files
rspade_system/app/RSpade/Commands/Migrate/Migrate_Rollback_Command.php
2025-12-26 02:44:39 +00:00

127 lines
5.1 KiB
PHP

<?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('mkdir -p /var/run/mysqld');
$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 -urspade -prspadepass 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');
}
}