OpenNIT

Quelltext des aktuellen Standes

Fassung 1.1.1

restore-backup.php

6 KB
<?php
/**
 * Spielt eine mit `/admin/selfbackup` erzeugte Sicherung zurück.
 *
 *   php bin/restore-backup.php [--dry-run] [--only=database,storage,config] [--yes] <archiv.zip>
 *
 * Bewusst nur auf der Kommandozeile: Eine Wiederherstellung überschreibt die
 * laufende Installation – mit derselben Anwendung, die dabei gerade läuft.
 * Aus dem Browser heraus wäre das ein Knopf, den jemand versehentlich trifft.
 *
 * Reihenfolge: erst prüfen, dann bestätigen lassen, dann schreiben. Wer
 * `--dry-run` angibt, bekommt nur die Aufstellung dessen, was geschähe.
 */

declare(strict_types=1);

require_once __DIR__ . '/../vendor/autoload.php';
require_once __DIR__ . '/../bootstrap.php';

use OpenNIT\Backup\BackupRestorer;
use OpenNIT\Backup\SelfBackupManager;
use OpenNIT\Core\Paths;
use OpenNIT\Cron\Bootstrap;

$rootDir = dirname(__DIR__);

// ── Aufrufparameter ─────────────────────────────────────────────────
$dryRun  = false;
$ohneRueckfrage = false;
$nurTeile = array_keys(SelfBackupManager::PARTS);
$archiv  = null;

foreach (array_slice($argv, 1) as $arg) {
    if ($arg === '--dry-run') {
        $dryRun = true;
    } elseif ($arg === '--yes' || $arg === '-y') {
        $ohneRueckfrage = true;
    } elseif (str_starts_with($arg, '--only=')) {
        $nurTeile = array_values(array_intersect(
            array_keys(SelfBackupManager::PARTS),
            array_map('trim', explode(',', substr($arg, 7)))
        ));
    } elseif (str_starts_with($arg, '-')) {
        fwrite(STDERR, "Unbekannte Option: $arg\n");
        exit(2);
    } else {
        $archiv = $arg;
    }
}

if ($archiv === null) {
    fwrite(STDERR, "Aufruf: restore-backup.php [--dry-run] [--only=database,storage,config] [--yes] <archiv.zip>\n");
    exit(2);
}
if (!is_file($archiv)) {
    fwrite(STDERR, "Die Datei gibt es nicht: $archiv\n");
    exit(1);
}
if ($nurTeile === []) {
    fwrite(STDERR, "--only nennt keinen bekannten Teil.\n");
    exit(2);
}
if (!class_exists('ZipArchive')) {
    fwrite(STDERR, "Die PHP-Erweiterung „zip\" fehlt.\n");
    exit(1);
}

$ctx     = Bootstrap::init($rootDir);
$manager = new SelfBackupManager($ctx->db, $ctx->audit, $rootDir, $ctx->config);

// ── Erst prüfen ─────────────────────────────────────────────────────
echo "Prüfe Archiv: $archiv\n";
$pruefung = $manager->pruefeDatei($archiv);
if (!$pruefung['ok']) {
    fwrite(STDERR, 'Das Archiv ist nicht brauchbar: ' . $pruefung['message'] . "\n");
    exit(1);
}
echo '  ' . $pruefung['message'] . "\n";

$zip = new ZipArchive();
if ($zip->open($archiv) !== true) {
    fwrite(STDERR, "Das Archiv lässt sich nicht öffnen.\n");
    exit(1);
}
$manifest = json_decode((string)$zip->getFromName(SelfBackupManager::MANIFEST), true) ?: [];
$enthalten = array_values((array)($manifest['parts'] ?? []));
$zuTun     = array_values(array_intersect($enthalten, $nurTeile));

echo '  Erzeugt am ' . (string)($manifest['created_at'] ?? '?')
   . ', Version ' . (string)($manifest['version'] ?? '?') . "\n";
echo '  Enthalten: ' . implode(', ', $enthalten) . "\n";
echo '  Einzuspielen: ' . ($zuTun === [] ? '(nichts)' : implode(', ', $zuTun)) . "\n\n";

if ($zuTun === []) {
    fwrite(STDERR, "Im Archiv ist keiner der gewählten Teile enthalten.\n");
    exit(1);
}

// Spiegelbild des Packens: `storage/` kommt ins Projektverzeichnis, `uploads/`
// dorthin, wo DIESE Installation seine Dateien erwartet – nicht dorthin, wo
// sie in der Quellinstallation lagen.
$storageZiel = $rootDir . '/storage';
$uploadsZiel = Paths::uploads($ctx->config);
$configZiel  = $rootDir . '/config';

echo "Was geschieht:\n";
foreach ($zuTun as $teil) {
    echo match ($teil) {
        'database' => "  - Datenbank: alle Tabellen werden ERSETZT (vorhandene Daten gehen verloren)\n",
        'storage'  => "  - Speicherverzeichnis: nach $storageZiel\n"
                    . "    Uploads: nach $uploadsZiel (gleichnamige Dateien werden überschrieben)\n",
        'config'   => "  - Konfiguration: Dateien werden nach $configZiel geschrieben, .env ins Projektverzeichnis\n",
        default    => '',
    };
}
echo "\n";

if ($dryRun) {
    echo "--dry-run: Es wurde nichts verändert.\n";
    $zip->close();
    exit(0);
}

// ── Dann bestätigen lassen ──────────────────────────────────────────
if (!$ohneRueckfrage) {
    echo "Zum Fortfahren \"JA\" eingeben: ";
    $eingabe = trim((string)fgets(STDIN));
    if ($eingabe !== 'JA') {
        echo "Abgebrochen. Es wurde nichts verändert.\n";
        $zip->close();
        exit(0);
    }
    echo "\n";
}

// ── Dann schreiben ──────────────────────────────────────────────────
$fehler = 0;

if (in_array('database', $zuTun, true)) {
    echo "Spiele Datenbank ein …\n";
    $strom = $zip->getStream('database.sql');
    if ($strom === false) {
        fwrite(STDERR, "  database.sql fehlt im Archiv.\n");
        $fehler++;
    } else {
        $ergebnis = BackupRestorer::applySql(
            $ctx->db->getPdo(),
            $strom,
            static fn (string $meldung) => fwrite(STDERR, '  Fehler: ' . $meldung . "\n")
        );
        fclose($strom);
        echo '  ' . $ergebnis['executed'] . " Anweisungen ausgeführt";
        echo $ergebnis['failed'] > 0 ? ', ' . $ergebnis['failed'] . " gescheitert.\n" : ".\n";
        $fehler += $ergebnis['failed'] > 0 ? 1 : 0;
    }
}

if (in_array('storage', $zuTun, true)) {
    echo "Schreibe Speicherverzeichnis …\n";
    $melder = static fn (string $m) => fwrite(STDERR, '  ' . $m . "\n");
    $n  = BackupRestorer::extract($zip, 'storage/', $storageZiel, [], $melder);
    $n += BackupRestorer::extract($zip, 'uploads/', $uploadsZiel, [], $melder);
    echo "  $n Dateien geschrieben.\n";
}

if (in_array('config', $zuTun, true)) {
    echo "Schreibe Konfiguration …\n";
    $n = BackupRestorer::extract(
        $zip,
        'config/',
        $configZiel,
        ['.env' => $rootDir . '/.env'],
        static fn (string $m) => fwrite(STDERR, '  ' . $m . "\n")
    );
    echo "  $n Dateien geschrieben.\n";
}

$zip->close();

echo "\n" . ($fehler === 0 ? "Fertig." : "Mit $fehler Fehler(n) beendet.") . "\n";
echo "Prüfen Sie die Installation im Browser, bevor Sie sie wieder freigeben.\n";
exit($fehler === 0 ? 0 : 1);