Quelltext des aktuellen Standes
<?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);