Backup-Operations-Log + EBUSY-Fix beim Restore
Backup-Seite zeigt zwei neue Log-Panels: links Backup-Erstellung, rechts Backup-Wiederherstellung. Jeder Eintrag mit ✓/✗-Status, Summary, Timestamp + User. Klick öffnet Modal mit vollständigem Verlauf – alle console.log/error/warn/info-Zeilen werden während der Operation in einen Puffer mitgefangen und im fullLog-Feld persistiert. Auto-Refresh alle 5s. Persistenz: neue Tabelle BackupLog mit Migration 20260519100000_backup_log (CREATE TABLE IF NOT EXISTS für Re-Deploys auf DBs mit Vorab-db-push). fullLog auf 1 MB gecappt. Endpoints (settings:update): - GET /api/settings/backup-logs?operation=CREATE|RESTORE&limit=50 - GET /api/settings/backup-logs/:id EBUSY-Fix: Der neue Log-Verlauf hat sofort einen alten Bug sichtbar gemacht. backup.service.restoreBackup rief deleteDirectory(UPLOADS_DIR) auf, dessen finales rmdirSync auf /app/uploads ein EBUSY warf – das Verzeichnis ist im Container ein Bind-Mount und lässt sich nicht aushängen. Fix: neuer Helper emptyDirectory() löscht nur die Inhalte, das Verzeichnis bleibt stehen. Live-verifiziert: 4867 Datensätze + 1 Datei in 13.2s wiederhergestellt; Log-Modal zeigt den vollständigen Verlauf. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -138,6 +138,24 @@ function deleteDirectory(dirPath: string): void {
|
||||
fs.rmdirSync(dirPath);
|
||||
}
|
||||
|
||||
// Wie deleteDirectory, ABER das Ziel-Verzeichnis selbst bleibt stehen –
|
||||
// nur die Inhalte verschwinden. Notwendig für Docker-Bind-Mounts wie
|
||||
// `/app/uploads`: dort wirft `rmdir` ein EBUSY, weil das Volume vom Host
|
||||
// gemountet ist und sich nicht aushängen lässt.
|
||||
function emptyDirectory(dirPath: string): void {
|
||||
if (!fs.existsSync(dirPath)) return;
|
||||
const items = fs.readdirSync(dirPath);
|
||||
for (const item of items) {
|
||||
const itemPath = path.join(dirPath, item);
|
||||
const stats = fs.lstatSync(itemPath);
|
||||
if (stats.isDirectory()) {
|
||||
deleteDirectory(itemPath);
|
||||
} else {
|
||||
fs.unlinkSync(itemPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Liste aller verfügbaren Backups
|
||||
*/
|
||||
@@ -926,10 +944,10 @@ export async function restoreBackup(backupName: string): Promise<RestoreResult>
|
||||
let restoredFiles = 0;
|
||||
const uploadsBackupDir = path.join(backupDir, 'uploads');
|
||||
if (fs.existsSync(uploadsBackupDir)) {
|
||||
// Bestehenden Uploads-Ordner leeren (optional: könnte auch nur überschreiben)
|
||||
if (fs.existsSync(UPLOADS_DIR)) {
|
||||
deleteDirectory(UPLOADS_DIR);
|
||||
}
|
||||
// Inhalte leeren, das Verzeichnis selbst NICHT löschen –
|
||||
// UPLOADS_DIR ist im Container ein Bind-Mount auf den Host und
|
||||
// `rmdir` darauf liefert EBUSY (siehe emptyDirectory()).
|
||||
emptyDirectory(UPLOADS_DIR);
|
||||
restoredFiles = copyDirectory(uploadsBackupDir, UPLOADS_DIR);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user