156 lines
5.3 KiB
PHP
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;
|
|
}
|
|
}
|