Add sudo support for migrate commands when not running as root
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,7 @@ use Symfony\Component\Process\Exception\ProcessFailedException;
|
||||
|
||||
class Migrate_Begin_Command extends Command
|
||||
{
|
||||
use PrivilegedCommandTrait;
|
||||
protected $signature = 'migrate:begin';
|
||||
protected $description = 'Begin a migration session by creating a MySQL snapshot';
|
||||
|
||||
@@ -68,7 +69,7 @@ class Migrate_Begin_Command extends Command
|
||||
try {
|
||||
// Step 1: Stop MySQL using supervisorctl
|
||||
$this->info('[1] Stopping MySQL server...');
|
||||
shell_exec('supervisorctl stop mysql 2>&1');
|
||||
$this->shell_exec_privileged('supervisorctl stop mysql 2>&1');
|
||||
|
||||
// Wait a moment for process to die
|
||||
sleep(3);
|
||||
@@ -76,16 +77,16 @@ class Migrate_Begin_Command extends Command
|
||||
// Step 2: Remove old backup if exists
|
||||
if (is_dir($this->backup_dir)) {
|
||||
$this->info('[2] Removing old backup...');
|
||||
$this->run_command(['rm', '-rf', $this->backup_dir]);
|
||||
$this->run_privileged_command(['rm', '-rf', $this->backup_dir]);
|
||||
}
|
||||
|
||||
// Step 3: Copy MySQL data directory
|
||||
$this->info('[3] Creating snapshot of MySQL data...');
|
||||
$this->run_command(['cp', '-r', $this->mysql_data_dir, $this->backup_dir]);
|
||||
$this->run_privileged_command(['cp', '-r', $this->mysql_data_dir, $this->backup_dir]);
|
||||
|
||||
// Step 4: Start MySQL again using supervisorctl
|
||||
$this->info('[4] Starting MySQL server...');
|
||||
shell_exec('supervisorctl start mysql 2>&1');
|
||||
$this->shell_exec_privileged('supervisorctl start mysql 2>&1');
|
||||
|
||||
// Step 5: Wait for MySQL to be ready
|
||||
$this->info('[5] Waiting for MySQL to be ready...');
|
||||
@@ -149,28 +150,12 @@ class Migrate_Begin_Command extends Command
|
||||
|
||||
// Try to restart MySQL if it's stopped
|
||||
$this->info('Attempting to restart MySQL...');
|
||||
shell_exec('supervisorctl start mysql 2>&1');
|
||||
$this->shell_exec_privileged('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
|
||||
*/
|
||||
|
||||
@@ -10,6 +10,7 @@ use App\RSpade\Core\Database\MigrationPaths;
|
||||
|
||||
class Migrate_Commit_Command extends Command
|
||||
{
|
||||
use PrivilegedCommandTrait;
|
||||
protected $signature = 'migrate:commit';
|
||||
protected $description = 'Commit the migration changes and end the migration session';
|
||||
|
||||
@@ -88,7 +89,7 @@ class Migrate_Commit_Command extends Command
|
||||
// Step 1: Remove backup directory
|
||||
if (is_dir($this->backup_dir)) {
|
||||
$this->info('[1] Removing backup snapshot...');
|
||||
$this->run_command(['rm', '-rf', $this->backup_dir]);
|
||||
$this->run_privileged_command(['rm', '-rf', $this->backup_dir]);
|
||||
}
|
||||
|
||||
// Step 2: Remove migration flag
|
||||
@@ -168,19 +169,4 @@ class Migrate_Commit_Command extends Command
|
||||
return $unran;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a shell command
|
||||
*/
|
||||
protected function run_command(array $command): string
|
||||
{
|
||||
$process = new Process($command);
|
||||
$process->setTimeout(60);
|
||||
$process->run();
|
||||
|
||||
if (!$process->isSuccessful()) {
|
||||
throw new \Exception($process->getErrorOutput());
|
||||
}
|
||||
|
||||
return $process->getOutput();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ 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';
|
||||
|
||||
@@ -42,7 +43,7 @@ class Migrate_Rollback_Command extends Command
|
||||
try {
|
||||
// Step 1: Stop MySQL using supervisorctl
|
||||
$this->info('[1] Stopping MySQL server...');
|
||||
shell_exec('supervisorctl stop mysql 2>&1');
|
||||
$this->shell_exec_privileged('supervisorctl stop mysql 2>&1');
|
||||
|
||||
// Wait a moment for process to die
|
||||
sleep(3);
|
||||
@@ -50,21 +51,21 @@ class Migrate_Rollback_Command extends Command
|
||||
// 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");
|
||||
$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...');
|
||||
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");
|
||||
$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_command(['chown', '-R', 'mysql:mysql', $this->mysql_data_dir]);
|
||||
$this->run_privileged_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');
|
||||
$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...');
|
||||
@@ -94,28 +95,12 @@ class Migrate_Rollback_Command extends Command
|
||||
|
||||
// Try to restart MySQL
|
||||
$this->info('Attempting to restart MySQL...');
|
||||
shell_exec('supervisorctl start mysql 2>&1');
|
||||
$this->shell_exec_privileged('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
|
||||
*/
|
||||
|
||||
60
app/RSpade/Commands/Migrate/PrivilegedCommandTrait.php
Executable file
60
app/RSpade/Commands/Migrate/PrivilegedCommandTrait.php
Executable file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace App\RSpade\Commands\Migrate;
|
||||
|
||||
use Symfony\Component\Process\Process;
|
||||
use Symfony\Component\Process\Exception\ProcessFailedException;
|
||||
|
||||
/**
|
||||
* Trait for commands that need to run privileged operations (supervisorctl, file ops on /var/lib/mysql, etc.)
|
||||
*
|
||||
* Automatically prepends sudo when not running as root.
|
||||
*/
|
||||
trait PrivilegedCommandTrait
|
||||
{
|
||||
/**
|
||||
* Check if we're running as root
|
||||
*/
|
||||
protected function is_root(): bool
|
||||
{
|
||||
$whoami = trim(shell_exec('whoami') ?? '');
|
||||
return $whoami === 'root';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sudo prefix if needed
|
||||
*/
|
||||
protected function sudo_prefix(): string
|
||||
{
|
||||
return $this->is_root() ? '' : 'sudo ';
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a shell command string with sudo if needed
|
||||
*/
|
||||
protected function shell_exec_privileged(string $command): ?string
|
||||
{
|
||||
$full_command = $this->sudo_prefix() . $command;
|
||||
return shell_exec($full_command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a Process command array with sudo if needed
|
||||
*/
|
||||
protected function run_privileged_command(array $command, bool $throw_on_error = true): string
|
||||
{
|
||||
if (!$this->is_root()) {
|
||||
array_unshift($command, 'sudo');
|
||||
}
|
||||
|
||||
$process = new Process($command);
|
||||
$process->setTimeout(60);
|
||||
$process->run();
|
||||
|
||||
if ($throw_on_error && !$process->isSuccessful()) {
|
||||
throw new ProcessFailedException($process);
|
||||
}
|
||||
|
||||
return $process->getOutput();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user