flag_file)) { $flag_data = json_decode(file_get_contents($this->flag_file), true); $started_at = $flag_data['started_at'] ?? 'unknown time'; $this->info('[OK] 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. [OK] 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('[WARNING] 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('[WARNING] 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('[WARNING] 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...'); $this->shell_exec_privileged('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_privileged_command(['rm', '-rf', $this->backup_dir]); } // Step 3: Copy MySQL data directory $this->info('[3] Creating snapshot of MySQL data...'); $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...'); $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...'); $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('[OK] 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('[WARNING] MIGRATION GUIDELINES FOR LLMs:'); $this->line(''); $this->line(' * Use RAW MySQL queries, not Laravel schema builder:'); $this->line(' [OK] DB::statement("ALTER TABLE users ADD COLUMN age INT")'); $this->line(' [ERROR] Schema::table("users", function($table) { $table->integer("age"); })'); $this->line(''); $this->line(' * ALL tables MUST have BIGINT ID primary key:'); $this->line(' [OK] id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY'); $this->line(' [ERROR] No exceptions - every table needs this exact ID column (SIGNED for easier migrations)'); $this->line(''); $this->line(' * Integer column types:'); $this->line(' [OK] BIGINT for all integers (IDs, counts, foreign keys, etc.)'); $this->line(' [OK] TINYINT(1) for boolean values (true/false only)'); $this->line(' [ERROR] NEVER use unsigned - always use signed integers'); $this->line(' [ERROR] 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('[WARNING] The web UI is disabled during migration mode.'); return 0; } catch (\Exception $e) { $this->error('[ERROR] Failed to create snapshot: ' . $e->getMessage()); // Try to restart MySQL if it's stopped $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'); } }