From ba0845da47704b8d8dcbe3b20c2aca5fad629f9f Mon Sep 17 00:00:00 2001 From: oxpoe Date: Mon, 12 Oct 2026 23:24:24 +0700 Subject: [PATCH] fix(backup): terapkan restore DB saat startup (tunda swap) - menimpa DB di bawah koneksi hidup merusak (WAL) --- config/database.js | 20 ++++++++++++++++++++ services/backupService.js | 19 ++++++++----------- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/config/database.js b/config/database.js index 8a79de6..b154a24 100644 --- a/config/database.js +++ b/config/database.js @@ -10,6 +10,26 @@ if (!fs.existsSync(dbDir)) fs.mkdirSync(dbDir, { recursive: true }); const dbPath = path.join(dbDir, 'billing.db'); +// Terapkan restore DB yang ditunda SEBELUM koneksi dibuka. +// (menimpa billing.db di bawah koneksi hidup bisa merusak DB karena WAL bercampur) +const _pendingRestore = path.join(dbDir, '.restore-pending'); +if (fs.existsSync(_pendingRestore)) { + try { + const _bkName = fs.readFileSync(_pendingRestore, 'utf8').trim(); + const _bkPath = path.join(__dirname, '..', 'backups', _bkName); + if (_bkName && fs.existsSync(_bkPath)) { + fs.copyFileSync(_bkPath, dbPath); + for (const _ext of ['-wal', '-shm']) { try { fs.unlinkSync(dbPath + _ext); } catch (e) {} } + console.log('[DB] Restore tertunda diterapkan dari ' + _bkName); + } else { + console.error('[DB] Restore tertunda dilewati, backup tidak ada: ' + _bkName); + } + } catch (e) { + console.error('[DB] Gagal menerapkan restore tertunda:', e.message); + } + try { fs.unlinkSync(_pendingRestore); } catch (e) {} +} + let db; try { db = new Database(dbPath); diff --git a/services/backupService.js b/services/backupService.js index 7c352cd..b8e9a42 100644 --- a/services/backupService.js +++ b/services/backupService.js @@ -140,22 +140,19 @@ function restoreDatabase(backupFileName) { logger.warn('[Backup] Failed to create pre-restore backup'); } - // Restore database - fs.copyFileSync(backupFilePath, dbPath); - // Buang WAL/SHM lama: backup VACUUM INTO self-contained, WAL lama milik DB sebelumnya - for (const ext of ['-wal', '-shm']) { - try { fs.unlinkSync(dbPath + ext); } catch (e) {} - } + // Tunda restore ke saat startup: menimpa DB di bawah koneksi hidup bisa merusak (WAL bercampur). + // Penanda dibaca config/database.js saat boot, sebelum koneksi dibuka. + const pendingPath = path.join(projectRoot, 'database', '.restore-pending'); + fs.writeFileSync(pendingPath, backupFileName, 'utf8'); + const size = fs.statSync(backupFilePath).size; - const stats = fs.statSync(dbPath); - const sizeKB = Math.round(stats.size / 1024); + logger.info(`[Backup] Restore dijadwalkan (diterapkan saat restart): ${backupFileName}`); - logger.info(`[Backup] Database restored from: ${backupFileName} (${sizeKB} KB)`); - return { success: true, fileName: backupFileName, - size: stats.size, + size, + pendingRestart: true, timestamp: getNowLocalISO(), preRestoreBackup: preRestoreBackup.fileName };