Upload via GUI (338 Dateien)

This commit is contained in:
2026-10-25 17:46:58 +00:00
parent ac6f8ef915
commit b38f5b34ad
5 changed files with 926 additions and 411 deletions
+34 -3
View File
@@ -60,7 +60,9 @@ Deny from all
// Zeitlimit erhöhen für große Backups
@set_time_limit(300);
@ini_set('memory_limit', '256M');
// Arbeitsspeicher nur erhöhen, nie senken (ein Limit von 512 MB oder mehr vom Hoster bleibt erhalten)
$memBytes = (function (string $v): int { $n = (int) $v; return match (strtolower(substr(trim($v), -1))) { 'g' => $n * 1073741824, 'm' => $n * 1048576, 'k' => $n * 1024, default => $n }; })((string) ini_get('memory_limit'));
if ($memBytes >= 0 && $memBytes < 512 * 1048576 && $memBytes !== 0) { @ini_set('memory_limit', '512M'); }
// php_zip automatisch aktivieren falls nicht geladen
if (!class_exists('ZipArchive')) {
@@ -96,6 +98,10 @@ Deny from all
// Alle Dateien sammeln
$exclude = [realpath($backupDir) ?: $backupDir];
// Der Backup-Ordner des Servers (backups/) gehört NIE ins Backup, auch wenn das Ziel ein anderer Ordner ist (zum Beispiel ein Zwischenordner des
// NAS-Plugins): sonst steckten alle älteren Backups noch einmal darin. Nur die .htaccess bleibt dabei, damit der Ordner nach einer
// Wiederherstellung geschützt ist.
$stdBackups = realpath($rootDir . '/backups') ?: null;
$iter = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($rootDir, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::SELF_FIRST
@@ -107,8 +113,12 @@ Deny from all
$filePath = $file->getRealPath();
$norm = str_replace('\\', '/', $filePath);
$skip = false;
foreach ($exclude as $ex) {
if ($ex && strpos($filePath, $ex) === 0) { $skip = true; break; }
if ($stdBackups !== null && strpos($filePath, $stdBackups . DIRECTORY_SEPARATOR) === 0) {
if ($filePath !== $stdBackups . DIRECTORY_SEPARATOR . '.htaccess') $skip = true; // alles außer der .htaccess
} else {
foreach ($exclude as $ex) {
if ($ex && ($filePath === $ex || strpos($filePath, rtrim($ex, '/' . chr(92)) . DIRECTORY_SEPARATOR) === 0)) { $skip = true; break; }
}
}
foreach (backup_excluded_fragments() as $frag) {
if (str_contains($norm, $frag)) { $skip = true; break; }
@@ -205,8 +215,13 @@ Deny from all
if ($zip->open($backupPath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
return ['ok' => false, 'error' => 'Backup-ZIP konnte nicht erstellt werden.'];
}
$canStore = method_exists($zip, 'setCompressionName');
foreach ($files as $filePath => $relativePath) {
$zip->addFile($filePath, $relativePath);
// Bereits komprimierte Dateien nur ablegen statt erneut zu packen: spart bei großen Uploads viel Rechenzeit
if ($canStore && preg_match('/\.(zip|jpe?g|png|gif|webp|pdf|exe|apk|aab|jar|7z|gz|mp[34]|woff2?)$/i', $relativePath)) {
@$zip->setCompressionName($relativePath, ZipArchive::CM_STORE);
}
}
if ($sqlDump && $sqlFile) $zip->addFromString($sqlFile, $sqlDump);
if ($sqliteSnapshot) $zip->addFile($sqliteSnapshot, 'database_backup.sqlite');
@@ -276,3 +291,19 @@ Deny from all
'size' => round(filesize($backupPath) / 1024 / 1024, 2),
];
}
/**
* Sperre „es läuft gerade ein Backup“: höchstens ein Backup gleichzeitig (Cronjob, eingebauter Hintergrunddienst, Knopf auf der Seite, NAS-Plugin).
* Atomar über die Einstellungstabelle; eine Sperre älter als $maxSeconds (abgestürzter Lauf) gilt als frei.
*/
function backup_lock_acquire(PDO $pdo, int $maxSeconds = 10800): bool
{
if (!function_exists('pseudo_cron_try_claim')) return true;
return pseudo_cron_try_claim($pdo, 'backup_run_lock', $maxSeconds);
}
function backup_lock_release(PDO $pdo): void
{
if (!function_exists('pseudo_cron_try_claim')) return;
try { $pdo->prepare("UPDATE settings SET setting_value = '1970-01-01 00:00:00' WHERE setting_key = 'backup_run_lock'")->execute(); } catch (Throwable $e) { /* Sperre verfällt von selbst */ }
}