option('force') && ! $this->confirm('This will wipe your local database and replace it with production. Are you sure?')) { $this->info('Aborted.'); return self::SUCCESS; } $local = $this->connectionConfig('local'); $production = $this->connectionConfig('production'); $timestamp = now()->format('Y_m_d_His'); $dumpPath = storage_path("app/private/db_dumps/production_{$timestamp}.dump"); $backupPath = storage_path("app/private/db_dumps/local_backup_{$timestamp}.dump"); if (! is_dir(storage_path('app/private/db_dumps'))) { mkdir(storage_path('app/private/db_dumps'), 0755, true); } // Step 1: Dump production $this->info('Dumping production database...'); $dumpResult = $this->pgDump($production, $dumpPath); if ($dumpResult !== 0) { $this->error('Failed to dump production database.'); return self::FAILURE; } $this->info("Production dump saved to: {$dumpPath}"); // Step 2: Back up local (unless skipped) if (! $this->option('no-backup')) { $this->info('Backing up local database...'); $backupResult = $this->pgDump($local, $backupPath); if ($backupResult !== 0) { $this->error('Failed to back up local database. Aborting to be safe. Use --no-backup to skip.'); return self::FAILURE; } $this->info("Local backup saved to: {$backupPath}"); } // Step 3: Drop and recreate the local database $this->info('Wiping local database...'); $this->dropAndRecreateLocalDatabase($local); // Step 4: Restore production dump into local $this->info('Restoring production dump to local database...'); $restoreResult = $this->pgRestore($local, $dumpPath); if ($restoreResult !== 0) { $this->error('Restore failed. Your local backup is at: ' . ($this->option('no-backup') ? 'N/A (backup was skipped)' : $backupPath)); return self::FAILURE; } $this->info('Done. Local database now mirrors production.'); return self::SUCCESS; } private function connectionConfig(string $type): array { $connection = $type === 'production' ? 'pgsql_production' : config('database.default'); return [ 'host' => config("database.connections.{$connection}.host"), 'port' => config("database.connections.{$connection}.port"), 'database' => config("database.connections.{$connection}.database"), 'username' => config("database.connections.{$connection}.username"), 'password' => config("database.connections.{$connection}.password"), ]; } private function pgDump(array $config, string $outputPath): int { $pgpass = $this->writePgPass($config); $command = sprintf( 'pg_dump -Fc -h %s -p %s -U %s %s -f %s', escapeshellarg($config['host']), escapeshellarg($config['port']), escapeshellarg($config['username']), escapeshellarg($config['database']), escapeshellarg($outputPath), ); $resultCode = $this->runWithPgPass($pgpass, $command); unlink($pgpass); return $resultCode; } private function pgRestore(array $config, string $dumpPath): int { $pgpass = $this->writePgPass($config); $command = sprintf( 'pg_restore -h %s -p %s -U %s -d %s --no-owner --no-privileges %s', escapeshellarg($config['host']), escapeshellarg($config['port']), escapeshellarg($config['username']), escapeshellarg($config['database']), escapeshellarg($dumpPath), ); $resultCode = $this->runWithPgPass($pgpass, $command); unlink($pgpass); return $resultCode; } /** * Write a temporary pgpass file and return its path. * This is the cross-platform alternative to PGPASSWORD which doesn't work on Windows. */ private function writePgPass(array $config): string { // Escape any colons or backslashes in the password per pgpass format rules $password = str_replace(['\\', ':'], ['\\\\', '\\:'], $config['password']); $content = "{$config['host']}:{$config['port']}:*:{$config['username']}:{$password}"; $path = tempnam(sys_get_temp_dir(), 'pgpass_'); file_put_contents($path, $content); chmod($path, 0600); return $path; } private function runWithPgPass(string $pgpassFile, string $command): int { // PGPASSFILE is supported on all platforms including Windows putenv("PGPASSFILE={$pgpassFile}"); passthru($command, $resultCode); putenv('PGPASSFILE'); return $resultCode; } private function dropAndRecreateLocalDatabase(array $config): void { $database = $config['database']; // Connect to the postgres maintenance database to drop/recreate $pdo = new \PDO( "pgsql:host={$config['host']};port={$config['port']};dbname=postgres", $config['username'], $config['password'], ); $pdo->exec("SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = " . $pdo->quote($database)); $pdo->exec("DROP DATABASE IF EXISTS " . '"' . $database . '"'); $pdo->exec("CREATE DATABASE " . '"' . $database . '"'); } }