option('name') ?: config('cloud_backup.backup_name', 'lismikro')); $file = $this->option('file') ? (string) $this->option('file') : $this->downloadLatest($backupName); if (!$file || !File::exists($file)) { $this->error('File backup tidak ditemukan.'); return Command::FAILURE; } $this->info('File backup siap: ' . $file); if ($this->option('download-only')) { return Command::SUCCESS; } $host = (string) ($this->option('host') ?: config('cloud_backup.db_host')); $port = (string) ($this->option('port') ?: config('cloud_backup.db_port')); $database = (string) ($this->option('database') ?: config('cloud_backup.db_database')); $username = (string) ($this->option('username') ?: config('cloud_backup.db_username')); if (!$this->option('yes') && !$this->confirm("Restore penuh ke database {$database} di {$host}:{$port}?", false)) { $this->warn('Restore dibatalkan.'); return Command::FAILURE; } $command = [ (string) config('cloud_backup.pg_restore', 'pg_restore'), '-h', $host, '-p', $port, '-U', $username, '-d', $database, '-v', ]; if ((int) $this->option('clean') === 1) { $command[] = '--clean'; } if ((int) $this->option('if-exists') === 1) { $command[] = '--if-exists'; } if ((int) $this->option('no-owner') === 1) { $command[] = '--no-owner'; } $command[] = $file; $env = null; $password = (string) ($this->option('password') ?: config('cloud_backup.db_password')); if ($password !== '') { $env = ['PGPASSWORD' => $password]; } $process = new Process($command, base_path(), $env, null, null); $process->setTimeout(null); $this->info('Menjalankan pg_restore...'); $process->run(function (string $type, string $buffer): void { $this->output->write($buffer); }); if (!$process->isSuccessful()) { $this->error('Restore gagal dengan kode: ' . $process->getExitCode()); return Command::FAILURE; } $this->info('Restore selesai.'); return Command::SUCCESS; } private function downloadLatest(string $backupName): ?string { $baseUrl = rtrim((string) ($this->option('url') ?: config('cloud_backup.api_url')), '/'); $token = (string) ($this->option('token') ?: config('cloud_backup.api_token')); if ($baseUrl === '' || $token === '') { $this->error('CLOUD_BACKUP_API_URL dan CLOUD_BACKUP_API_TOKEN wajib diisi.'); return null; } $output = $this->option('output') ? (string) $this->option('output') : (string) config('cloud_backup.download_path'); File::ensureDirectoryExists(dirname($output), 0750, true); $metadata = Http::withToken($token) ->timeout(60) ->get($baseUrl . '/api/backups/latest', ['backup_name' => $backupName]); if (!$metadata->successful()) { $this->error('Gagal membaca metadata backup terbaru. HTTP ' . $metadata->status()); $this->line($metadata->body()); return null; } $info = $metadata->json(); $this->info('Backup terbaru: ' . ($info['file'] ?? '-')); $download = Http::withToken($token) ->timeout(0) ->sink($output) ->get($baseUrl . '/api/backups/latest/download', ['backup_name' => $backupName]); if (!$download->successful()) { File::delete($output); $this->error('Gagal download backup. HTTP ' . $download->status()); $this->line($download->body()); return null; } if (!empty($info['sha256']) && hash_file('sha256', $output) !== strtolower((string) $info['sha256'])) { File::delete($output); $this->error('Checksum file download tidak sesuai.'); return null; } return $output; } }