flag_file)) { $flag_data = json_decode(file_get_contents($this->flag_file), true); $started_at = $flag_data['started_at'] ?? 'unknown time'; $this->info('✅ Migration session already active!'); $this->info(''); $this->line('📝 Session started at: ' . $started_at); $this->line('📦 Snapshot available at: ' . $this->backup_dir); $this->info(''); $this->line('You are currently in migration mode with an active snapshot.'); $this->line('There is no need to create another snapshot.'); $this->info(''); $this->line('Your options:'); $this->info(''); $this->line('1. 🚀 Continue creating/running migrations:'); $this->line(' php artisan migrate'); $this->info(''); $this->line('2. ✅ Keep all changes and exit migration mode:'); $this->line(' php artisan migrate:commit'); $this->line(' (After commit, run migrate:begin again for new atomic migrations)'); $this->info(''); $this->line('3. ⏪ Discard all changes since snapshot and exit migration mode:'); $this->line(' php artisan migrate:rollback'); $this->line(' (Database will be restored to state at ' . $started_at . ')'); $this->info(''); $this->warn('⚠️ The web UI remains disabled while in migration mode.'); return 0; // Return success since we're providing helpful information } // Check if running in production if (app()->environment('production')) { $this->error('⚠️ This command is not available in production!'); $this->info('Snapshot-based migrations are only for development environments.'); $this->info('In production, run migrations directly with: php artisan migrate'); return 1; } // Check if running in Docker environment if (!file_exists('/.dockerenv')) { $this->error('⚠️ This command requires Docker environment!'); $this->info('Snapshot-based migrations are only available in Docker development environments.'); return 1; } $this->info('🔄 Starting migration snapshot process...'); 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: 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]); } // 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]); // Step 4: Start MySQL again using supervisorctl $this->info('4️⃣ Starting MySQL server...'); shell_exec('supervisorctl start mysql 2>&1'); // Step 5: Wait for MySQL to be ready $this->info('5️⃣ Waiting for MySQL to be ready...'); $this->wait_for_mysql_ready(); // Step 6: Create migration flag file $this->info('6️⃣ Creating migration flag...'); file_put_contents($this->flag_file, json_encode([ 'started_at' => now()->toIso8601String(), 'started_by' => get_current_user(), 'backup_dir' => $this->backup_dir, ], JSON_PRETTY_PRINT)); // Success message $this->info(''); $this->info('✅ Database snapshot created successfully!'); $this->info(''); $this->line('📋 Migration session is now active. You can:'); $this->line(' • Run migrations: php artisan migrate'); $this->line(' • Commit changes: php artisan migrate:commit'); $this->line(' • Rollback changes: php artisan migrate:rollback'); $this->info(''); // Migration best practices for LLMs $this->warn('⚠️ MIGRATION GUIDELINES FOR LLMs:'); $this->line(''); $this->line(' 🔹 Use RAW MySQL queries, not Laravel schema builder:'); $this->line(' ✅ DB::statement("ALTER TABLE users ADD COLUMN age INT")'); $this->line(' ❌ Schema::table("users", function($table) { $table->integer("age"); })'); $this->line(''); $this->line(' 🔹 ALL tables MUST have BIGINT ID primary key:'); $this->line(' ✅ id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY'); $this->line(' ❌ No exceptions - every table needs this exact ID column (SIGNED for easier migrations)'); $this->line(''); $this->line(' 🔹 Integer column types:'); $this->line(' ✅ BIGINT for all integers (IDs, counts, foreign keys, etc.)'); $this->line(' ✅ TINYINT(1) for boolean values (true/false only)'); $this->line(' ❌ NEVER use unsigned - always use signed integers'); $this->line(' ❌ NEVER use INT - always use BIGINT for consistency'); $this->line(''); $this->line(' 🔹 Migrations must be SELF-CONTAINED:'); $this->line(' • Use direct table names, not Model references'); $this->line(' • Use raw DB queries, not ORM/QueryBuilder'); $this->line(' • Never import Models or Services'); $this->line(''); $this->line(' 🔹 Examples of GOOD migration code:'); $this->line(' DB::statement("CREATE TABLE posts ('); $this->line(' id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,'); $this->line(' title VARCHAR(255)'); $this->line(' )")'); $this->line(' DB::statement("UPDATE users SET status = \'active\' WHERE created_at < NOW()")'); $this->line(''); $this->line(' 🔹 Target: MySQL only (no need for database abstraction)'); $this->info(''); $this->warn('⚠️ The web UI is disabled during migration mode.'); return 0; } catch (\Exception $e) { $this->error('❌ Failed to create snapshot: ' . $e->getMessage()); // Try to restart MySQL if it's stopped $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'); } }