153 lines
6.4 KiB
PHP
153 lines
6.4 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use App\Models\BackupJob;
|
|
use Illuminate\Console\Command;
|
|
|
|
class RestoreRun extends Command
|
|
{
|
|
protected $signature = 'restore:run {backupJobId : ID des Quell-Backups} {token : Statusdatei-Token}';
|
|
protected $description = 'Stellt ein Backup wieder her und schreibt den Status in eine Temp-Datei';
|
|
|
|
public function handle(): int
|
|
{
|
|
$sourceJob = BackupJob::find($this->argument('backupJobId'));
|
|
$token = $this->argument('token');
|
|
$statusFile = sys_get_temp_dir() . '/' . $token . '.json';
|
|
|
|
$artifact = $sourceJob?->artifact_path;
|
|
|
|
if (!$artifact || !file_exists($artifact)) {
|
|
$this->writeStatus($statusFile, 'failed', ['✗ Archivdatei nicht gefunden: ' . $artifact]);
|
|
return self::FAILURE;
|
|
}
|
|
|
|
$this->writeStatus($statusFile, 'running', ['Archiv wird extrahiert…']);
|
|
|
|
$extractDir = sys_get_temp_dir() . '/mailwolt_restore_' . $token;
|
|
mkdir($extractDir, 0700, true);
|
|
|
|
$log = [];
|
|
$exitCode = 0;
|
|
|
|
// ── 1. Archiv extrahieren ─────────────────────────────────────────
|
|
$tarOut = [];
|
|
$tarExit = 0;
|
|
exec(
|
|
"tar --ignore-failed-read -xzf " . escapeshellarg($artifact)
|
|
. " -C " . escapeshellarg($extractDir) . " 2>&1",
|
|
$tarOut, $tarExit
|
|
);
|
|
|
|
if ($tarExit > 1) {
|
|
$log[] = '✗ Extraktion fehlgeschlagen: ' . implode('; ', array_slice($tarOut, -3));
|
|
$exitCode = 1;
|
|
} else {
|
|
$log[] = '✓ Archiv extrahiert';
|
|
}
|
|
$this->writeStatus($statusFile, 'running', $log);
|
|
|
|
// ── 2. Datenbank ─────────────────────────────────────────────────
|
|
$dbFiles = [];
|
|
exec("find " . escapeshellarg($extractDir) . " -name 'database.sql' -type f 2>/dev/null", $dbFiles);
|
|
|
|
if (!empty($dbFiles)) {
|
|
$log[] = 'Datenbank wird importiert…';
|
|
$this->writeStatus($statusFile, 'running', $log);
|
|
|
|
[$ok, $msg] = $this->importDatabase($dbFiles[0]);
|
|
$log[] = $ok ? '✓ Datenbank wiederhergestellt' : '✗ Datenbank: ' . $msg;
|
|
if (!$ok) $exitCode = 1;
|
|
} else {
|
|
$log[] = '— Kein Datenbank-Dump im Archiv';
|
|
}
|
|
$this->writeStatus($statusFile, 'running', $log);
|
|
|
|
// ── 3. E-Mails (Maildirs) ────────────────────────────────────────
|
|
foreach (["{$extractDir}/var/mail", "{$extractDir}/var/vmail"] as $mailSrc) {
|
|
if (is_dir($mailSrc)) {
|
|
$log[] = 'E-Mails werden wiederhergestellt…';
|
|
$this->writeStatus($statusFile, 'running', $log);
|
|
|
|
$destParent = '/' . implode('/', array_slice(explode('/', $mailSrc, -1 + substr_count($mailSrc, '/')), 1, -1));
|
|
$cpOut = [];
|
|
$cpExit = 0;
|
|
exec("cp -rp " . escapeshellarg($mailSrc) . " " . escapeshellarg($destParent) . "/ 2>&1", $cpOut, $cpExit);
|
|
$log[] = $cpExit === 0
|
|
? '✓ E-Mails wiederhergestellt'
|
|
: '✗ Mails: ' . implode('; ', array_slice($cpOut, -3));
|
|
if ($cpExit !== 0) $exitCode = 1;
|
|
}
|
|
}
|
|
|
|
// ── 4. Konfiguration ─────────────────────────────────────────────
|
|
$etcSrc = "{$extractDir}/etc";
|
|
if (is_dir($etcSrc)) {
|
|
$log[] = 'Konfiguration wird wiederhergestellt…';
|
|
$this->writeStatus($statusFile, 'running', $log);
|
|
|
|
foreach (scandir($etcSrc) ?: [] as $entry) {
|
|
if ($entry === '.' || $entry === '..') continue;
|
|
$cpOut = [];
|
|
$cpExit = 0;
|
|
exec("cp -rp " . escapeshellarg("{$etcSrc}/{$entry}") . " /etc/ 2>&1", $cpOut, $cpExit);
|
|
$log[] = $cpExit === 0
|
|
? '✓ /etc/' . $entry
|
|
: '— /etc/' . $entry . ': ' . implode('; ', array_slice($cpOut, -1));
|
|
}
|
|
}
|
|
|
|
// ── 5. Dienste neu laden ─────────────────────────────────────────
|
|
foreach (['postfix', 'dovecot'] as $svc) {
|
|
if (is_dir("{$etcSrc}/{$svc}")) {
|
|
$restOut = [];
|
|
exec("systemctl reload-or-restart {$svc} 2>&1", $restOut, $restExit);
|
|
$log[] = $restExit === 0 ? '✓ ' . $svc . ' neu geladen' : '— ' . $svc . ': ' . implode('; ', $restOut);
|
|
}
|
|
}
|
|
|
|
// ── Aufräumen ────────────────────────────────────────────────────
|
|
exec("rm -rf " . escapeshellarg($extractDir));
|
|
|
|
$finalStatus = $exitCode === 0 ? 'ok' : 'failed';
|
|
$this->writeStatus($statusFile, $finalStatus, $log);
|
|
|
|
return $exitCode === 0 ? self::SUCCESS : self::FAILURE;
|
|
}
|
|
|
|
private function importDatabase(string $sqlFile): array
|
|
{
|
|
$conn = config('database.connections.' . config('database.default'));
|
|
if (($conn['driver'] ?? '') !== 'mysql') {
|
|
return [false, 'Nur MySQL/MariaDB wird unterstützt.'];
|
|
}
|
|
|
|
$cmd = 'MYSQL_PWD=' . escapeshellarg($conn['password'] ?? '')
|
|
. ' mysql'
|
|
. ' -h ' . escapeshellarg($conn['host'] ?? '127.0.0.1')
|
|
. ' -P ' . escapeshellarg((string)($conn['port'] ?? '3306'))
|
|
. ' -u ' . escapeshellarg($conn['username'] ?? '')
|
|
. ' ' . escapeshellarg($conn['database'] ?? '')
|
|
. ' < ' . escapeshellarg($sqlFile)
|
|
. ' 2>&1';
|
|
|
|
$output = [];
|
|
$exit = 0;
|
|
exec($cmd, $output, $exit);
|
|
|
|
return $exit === 0
|
|
? [true, '']
|
|
: [false, implode('; ', array_slice($output, -3))];
|
|
}
|
|
|
|
private function writeStatus(string $file, string $status, array $log): void
|
|
{
|
|
file_put_contents($file, json_encode([
|
|
'status' => $status,
|
|
'log' => implode("\n", $log),
|
|
'timestamp' => time(),
|
|
]));
|
|
}
|
|
}
|