Files
rspade_system/app/RSpade/Commands/Migrate/Migrate_Rollback_Command.php
root f6fac6c4bc Fix bin/publish: copy docs.dist from project root
Fix bin/publish: use correct .env path for rspade_system
Fix bin/publish script: prevent grep exit code 1 from terminating script

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 02:08:33 +00:00

141 lines
5.4 KiB
PHP
Executable File
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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
{
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('⚠️ 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('❌ 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('⚠️ 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...');
shell_exec('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
shell_exec("rm -rf {$this->mysql_data_dir}/* 2>/dev/null");
shell_exec("rm -rf {$this->mysql_data_dir}/.* 2>/dev/null");
// Step 3: Restore backup
$this->info('3⃣ Restoring database snapshot...');
shell_exec("cp -r {$this->backup_dir}/* {$this->mysql_data_dir}/");
shell_exec("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_command(['chown', '-R', 'mysql:mysql', $this->mysql_data_dir]);
// Step 5: Start MySQL using supervisorctl
$this->info('5⃣ Starting MySQL server...');
shell_exec('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('✅ Database successfully rolled back to snapshot!');
$this->info('');
$this->line('The database has been restored to the snapshot state.');
$this->warn('⚠️ 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('❌ Rollback failed: ' . $e->getMessage());
$this->error('Manual intervention may be required!');
// Try to restart MySQL
$this->info('Attempting to restart MySQL...');
shell_exec('supervisorctl start mysql 2>&1');
return 1;
}
}
/**
* Run a shell command and check for errors
*/
protected function run_command(array $command, bool $throw_on_error = true): string
{
$process = new Process($command);
$process->setTimeout(60);
$process->run();
if ($throw_on_error && !$process->isSuccessful()) {
throw new ProcessFailedException($process);
}
return $process->getOutput();
}
/**
* Wait for MySQL to be ready for connections
*/
protected function wait_for_mysql_ready(): void
{
$max_attempts = 30;
$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 30 seconds');
}
}