Files
lis/htdocs/app/Console/Commands/BackupRestoreLatestCommand.php
T
2026-08-01 12:15:25 +07:00

156 lines
5.3 KiB
PHP

<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http;
use Symfony\Component\Process\Process;
class BackupRestoreLatestCommand extends Command
{
protected $signature = 'backup:restore-latest
{--url= : Base URL server cloud backup}
{--token= : Token API cloud backup}
{--name= : Nama backup}
{--output= : Lokasi file download}
{--file= : Pakai file backup lokal, tanpa download}
{--download-only : Hanya download, tidak restore}
{--host= : Host PostgreSQL target}
{--port= : Port PostgreSQL target}
{--database= : Database PostgreSQL target}
{--username= : Username PostgreSQL target}
{--password= : Password PostgreSQL target, opsional jika .pgpass sudah ada}
{--clean=1 : Jalankan pg_restore --clean}
{--if-exists=1 : Jalankan pg_restore --if-exists}
{--no-owner=1 : Jalankan pg_restore --no-owner}
{--yes : Lewati konfirmasi restore}';
protected $description = 'Download backup terbaru dari duidev.com dan restore penuh ke PostgreSQL.';
public function handle(): int
{
$backupName = (string) ($this->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;
}
}