Files
picpeak/backend/migrations/030_add_database_backup_tables.js
T
paul f6a79c815e feat: implement comprehensive backup and restore system with S3 support
- Add S3/MinIO storage adapter with multipart upload support
- Implement database backup service for SQLite and PostgreSQL
- Create backup manifest generator for tracking backup contents
- Enhance backup service with S3 integration and incremental backups
- Add restore service with safety measures and rollback capability
- Create comprehensive test suite for all backup functionality
- Add admin API endpoints for backup/restore management
- Implement frontend UI with dashboard, configuration, and restore wizard
- Add roadmap section to README with implemented backup feature

This implementation provides:
- Multiple backup destinations (local, rsync, S3/MinIO)
- Intelligent change detection to minimize backup frequency
- Full database backups with compression
- Manifest-based restore with integrity validation
- Pre-restore safety backups with rollback
- Comprehensive error handling and monitoring
- User-friendly admin interface

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-24 16:57:08 +02:00

182 lines
7.2 KiB
JavaScript

const { db } = require('../src/database/db');
async function up() {
console.log('Adding database backup tables and settings...');
// Create database_backup_runs table to track database backup history
const hasDatabaseBackupRunsTable = await db.schema.hasTable('database_backup_runs');
if (!hasDatabaseBackupRunsTable) {
await db.schema.createTable('database_backup_runs', (table) => {
table.increments('id').primary();
table.datetime('started_at').notNullable();
table.datetime('completed_at');
table.string('status').defaultTo('running'); // running, completed, failed
table.string('backup_type'); // sqlite, postgresql
table.string('destination_path');
table.string('file_path');
table.bigInteger('file_size_bytes').defaultTo(0);
table.bigInteger('original_size_bytes').defaultTo(0);
table.integer('duration_seconds');
table.string('checksum'); // SHA256 checksum of backup file
table.float('compression_ratio'); // Compression percentage
table.json('table_checksums'); // Individual table checksums
table.text('error_message');
table.json('statistics'); // Detailed stats about the backup
table.index(['started_at'], 'idx_db_backup_started');
table.index(['status'], 'idx_db_backup_status');
});
}
// Add database backup-related settings to app_settings
const databaseBackupSettings = [
{
setting_key: 'database_backup_enabled',
setting_value: JSON.stringify(false),
setting_type: 'database_backup'
},
{
setting_key: 'database_backup_schedule',
setting_value: JSON.stringify('0 3 * * *'), // Default: 3 AM daily
setting_type: 'database_backup'
},
{
setting_key: 'database_backup_destination_path',
setting_value: JSON.stringify('/backup/database'),
setting_type: 'database_backup'
},
{
setting_key: 'database_backup_compress',
setting_value: JSON.stringify(true),
setting_type: 'database_backup'
},
{
setting_key: 'database_backup_validate_integrity',
setting_value: JSON.stringify(true),
setting_type: 'database_backup'
},
{
setting_key: 'database_backup_include_checksums',
setting_value: JSON.stringify(true),
setting_type: 'database_backup'
},
{
setting_key: 'database_backup_retention_days',
setting_value: JSON.stringify(30),
setting_type: 'database_backup'
},
{
setting_key: 'database_backup_email_on_failure',
setting_value: JSON.stringify(true),
setting_type: 'database_backup'
},
{
setting_key: 'database_backup_email_on_success',
setting_value: JSON.stringify(false),
setting_type: 'database_backup'
},
{
setting_key: 'database_backup_max_retries',
setting_value: JSON.stringify(3),
setting_type: 'database_backup'
}
];
// Insert database backup settings if they don't exist
for (const setting of databaseBackupSettings) {
const exists = await db('app_settings')
.where('setting_key', setting.setting_key)
.first();
if (!exists) {
await db('app_settings').insert(setting);
}
}
// Add database backup-related email templates
const databaseBackupEmailTemplates = [
{
template_key: 'database_backup_failed',
subject_en: 'Database Backup Failed - Critical Alert',
subject_de: 'Datenbank-Backup fehlgeschlagen - Kritische Warnung',
body_html_en: `<h2>Database Backup Failed</h2>
<p>The scheduled database backup has failed and requires immediate attention.</p>
<p><strong>Error Details:</strong></p>
<ul>
<li>Backup Type: {{backup_type}}</li>
<li>Timestamp: {{timestamp}}</li>
<li>Error: {{error_message}}</li>
</ul>
<p>This is a critical issue that could affect disaster recovery. Please investigate immediately.</p>`,
body_html_de: `<h2>Datenbank-Backup fehlgeschlagen</h2>
<p>Das geplante Datenbank-Backup ist fehlgeschlagen und erfordert sofortige Aufmerksamkeit.</p>
<p><strong>Fehlerdetails:</strong></p>
<ul>
<li>Backup-Typ: {{backup_type}}</li>
<li>Zeitstempel: {{timestamp}}</li>
<li>Fehler: {{error_message}}</li>
</ul>
<p>Dies ist ein kritisches Problem, das die Disaster-Recovery beeinträchtigen könnte. Bitte untersuchen Sie es sofort.</p>`,
body_text_en: 'Database Backup Failed\n\nThe scheduled database backup has failed.\n\nBackup Type: {{backup_type}}\nTimestamp: {{timestamp}}\nError: {{error_message}}\n\nThis is critical - please investigate immediately.',
body_text_de: 'Datenbank-Backup fehlgeschlagen\n\nDas geplante Datenbank-Backup ist fehlgeschlagen.\n\nBackup-Typ: {{backup_type}}\nZeitstempel: {{timestamp}}\nFehler: {{error_message}}\n\nDies ist kritisch - bitte sofort untersuchen.',
variables: JSON.stringify(['backup_type', 'timestamp', 'error_message'])
},
{
template_key: 'database_backup_completed',
subject_en: 'Database Backup Completed Successfully',
subject_de: 'Datenbank-Backup erfolgreich abgeschlossen',
body_html_en: `<h2>Database Backup Completed</h2>
<p>The scheduled database backup has been completed successfully.</p>
<p><strong>Backup Summary:</strong></p>
<ul>
<li>Backup Type: {{backup_type}}</li>
<li>Duration: {{duration}}</li>
<li>File Size: {{file_size}}</li>
<li>Compression Ratio: {{compression_ratio}}</li>
<li>File Path: {{file_path}}</li>
</ul>`,
body_html_de: `<h2>Datenbank-Backup abgeschlossen</h2>
<p>Das geplante Datenbank-Backup wurde erfolgreich abgeschlossen.</p>
<p><strong>Backup-Zusammenfassung:</strong></p>
<ul>
<li>Backup-Typ: {{backup_type}}</li>
<li>Dauer: {{duration}}</li>
<li>Dateigröße: {{file_size}}</li>
<li>Komprimierungsverhältnis: {{compression_ratio}}</li>
<li>Dateipfad: {{file_path}}</li>
</ul>`,
body_text_en: 'Database Backup Completed\n\nThe scheduled database backup has been completed successfully.\n\nBackup Type: {{backup_type}}\nDuration: {{duration}}\nFile Size: {{file_size}}\nCompression Ratio: {{compression_ratio}}\nFile Path: {{file_path}}',
body_text_de: 'Datenbank-Backup abgeschlossen\n\nDas geplante Datenbank-Backup wurde erfolgreich abgeschlossen.\n\nBackup-Typ: {{backup_type}}\nDauer: {{duration}}\nDateigröße: {{file_size}}\nKomprimierungsverhältnis: {{compression_ratio}}\nDateipfad: {{file_path}}',
variables: JSON.stringify(['backup_type', 'duration', 'file_size', 'compression_ratio', 'file_path'])
}
];
// Insert database backup email templates if they don't exist
for (const template of databaseBackupEmailTemplates) {
const exists = await db('email_templates')
.where('template_key', template.template_key)
.first();
if (!exists) {
await db('email_templates').insert(template);
}
}
console.log('Database backup tables and settings added successfully');
}
async function down() {
// Remove database backup tables
await db.schema.dropTableIfExists('database_backup_runs');
// Remove database backup settings
await db('app_settings')
.where('setting_type', 'database_backup')
.delete();
// Remove database backup email templates
await db('email_templates')
.whereIn('template_key', ['database_backup_failed', 'database_backup_completed'])
.delete();
}
module.exports = { up, down };