mailwolt/app/Console/Commands/RestoreRun.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(),
]));
}
}