@hivetechs/hive-ai
Version:
Real-time streaming AI consensus platform with HTTP+SSE MCP integration for Claude Code, VS Code, Cursor, and Windsurf - powered by OpenRouter's unified API
805 lines โข 35.5 kB
JavaScript
/**
* SQLite Backup Manager - Local Privacy-Preserving Backup System
*
* Features:
* - Automated local backups with rotation
* - Timestamp-based restoration
* - Incremental and full backup strategies
* - Backup verification and integrity checks
* - Disaster recovery utilities
* - Zero cloud storage - 100% local and private
*/
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import { logger } from '../../utils/logging.js';
import { BackupLocationManager } from './backup-location-manager.js';
export class SQLiteBackupManager {
backupDir;
dbPath;
config;
memorySystem;
locationManager;
constructor(memorySystem) {
this.memorySystem = memorySystem;
this.dbPath = path.join(os.homedir(), '.hive-ai', 'hive-ai.db');
this.backupDir = path.join(os.homedir(), '.hive-ai', 'backups');
this.locationManager = new BackupLocationManager();
this.initialize();
}
/**
* Initialize async operations
*/
async initialize() {
await this.loadConfig();
this.ensureBackupDirectory();
this.scheduleAutomaticBackups();
}
/**
* Load backup configuration or create default
*/
async loadConfig() {
try {
const { getConfig } = await import('../../storage/unified-database.js');
const configData = await getConfig('sqlite_backup_config');
if (configData) {
this.config = JSON.parse(configData);
return;
}
}
catch (error) {
logger.debug('[SQLiteBackupManager] Database read failed, creating default');
}
// Create default configuration
this.config = this.getDefaultConfig();
await this.saveConfig();
}
/**
* Default backup configuration
*/
getDefaultConfig() {
return {
enabled: true,
backup_directory: this.backupDir,
max_backups: {
daily: 7, // Keep 7 daily backups
weekly: 4, // Keep 4 weekly backups
monthly: 12, // Keep 12 monthly backups
emergency: 5 // Keep 5 emergency backups
},
auto_backup_schedule: {
daily_hour: 2, // 2 AM daily backup
weekly_day: 0, // Sunday weekly backup
monthly_date: 1 // 1st of month
},
compression_enabled: true,
verification_enabled: true,
encryption_enabled: false // Future feature
};
}
/**
* Save backup configuration
*/
async saveConfig() {
try {
const { setConfig } = await import('../../storage/unified-database.js');
await setConfig('sqlite_backup_config', JSON.stringify(this.config));
}
catch (error) {
logger.error('Warning: Could not save backup config to database:', error instanceof Error ? error.message : 'Unknown error');
throw error;
}
}
/**
* Capture MCP configuration information for backup metadata
*/
async captureMCPConfigInfo() {
try {
const { getConfig } = await import('../../storage/unified-database.js');
// Get MCP transport configuration
const transportType = await getConfig('mcp_transport_type');
const serverPort = await getConfig('mcp_server_port');
// Get IDE configuration
const ideSettings = await getConfig('mcp_ide_settings');
let configuredIDEs = [];
if (ideSettings) {
try {
const ideConfig = JSON.parse(ideSettings);
configuredIDEs = ideConfig.configurations?.map((c) => c.ide) || [];
}
catch (error) {
logger.debug('Could not parse IDE settings for backup metadata');
}
}
return {
mcp_config_included: !!transportType,
mcp_transport_type: transportType || undefined,
mcp_server_port: serverPort ? parseInt(serverPort) : undefined,
configured_ides: configuredIDEs.length > 0 ? configuredIDEs : undefined
};
}
catch (error) {
logger.debug(`Could not capture MCP config info for backup: ${error instanceof Error ? error.message : 'Unknown error'}`);
return {
mcp_config_included: false
};
}
}
/**
* Create standalone MCP configuration backup
*/
async createMCPConfigBackup() {
try {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const configBackupPath = path.join(this.backupDir, 'mcp-configs', `mcp-config-${timestamp}.json`);
// Ensure MCP config directory exists
const configDir = path.dirname(configBackupPath);
if (!fs.existsSync(configDir)) {
fs.mkdirSync(configDir, { recursive: true });
}
const { getConfig } = await import('../../storage/unified-database.js');
// Collect all MCP-related configurations
const mcpBackup = {
timestamp: new Date().toISOString(),
transport_type: await getConfig('mcp_transport_type'),
server_configs: {},
ide_settings: await getConfig('mcp_ide_settings'),
security_config: await getConfig('mcp_security_config'),
health_config: await getConfig('mcp_health_config'),
port_range: await getConfig('mcp_port_range'),
auto_port_detection: await getConfig('mcp_auto_port_detection')
};
// Get environment-specific server ports
const environments = ['development', 'staging', 'production'];
for (const env of environments) {
const port = await getConfig(`mcp_server_port_${env}`);
const ideConfig = await getConfig(`mcp_ide_config_${env}`);
if (port || ideConfig) {
mcpBackup.server_configs[env] = {
port: port ? parseInt(port) : undefined,
ide_config: ideConfig ? JSON.parse(ideConfig) : undefined
};
}
}
// Write MCP configuration backup
fs.writeFileSync(configBackupPath, JSON.stringify(mcpBackup, null, 2));
logger.info(`๐ก MCP configuration backup created: ${path.basename(configBackupPath)}`);
return {
success: true,
backup_path: configBackupPath
};
}
catch (error) {
logger.error('Failed to create MCP configuration backup:', error instanceof Error ? error.message : 'Unknown error');
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
/**
* Restore MCP configuration from backup
*/
async restoreMCPConfig(configBackupPath) {
try {
if (!fs.existsSync(configBackupPath)) {
return { success: false, error: 'MCP configuration backup file not found' };
}
const mcpBackup = JSON.parse(fs.readFileSync(configBackupPath, 'utf8'));
const { setConfig } = await import('../../storage/unified-database.js');
const restoredSettings = [];
// Restore core MCP settings
if (mcpBackup.transport_type) {
await setConfig('mcp_transport_type', mcpBackup.transport_type);
restoredSettings.push('transport_type');
}
if (mcpBackup.ide_settings) {
await setConfig('mcp_ide_settings', mcpBackup.ide_settings);
restoredSettings.push('ide_settings');
}
if (mcpBackup.security_config) {
await setConfig('mcp_security_config', mcpBackup.security_config);
restoredSettings.push('security_config');
}
if (mcpBackup.health_config) {
await setConfig('mcp_health_config', mcpBackup.health_config);
restoredSettings.push('health_config');
}
if (mcpBackup.port_range) {
await setConfig('mcp_port_range', mcpBackup.port_range);
restoredSettings.push('port_range');
}
if (mcpBackup.auto_port_detection) {
await setConfig('mcp_auto_port_detection', mcpBackup.auto_port_detection);
restoredSettings.push('auto_port_detection');
}
// Restore environment-specific configurations
if (mcpBackup.server_configs) {
for (const [env, config] of Object.entries(mcpBackup.server_configs)) {
const envConfig = config;
if (envConfig.port) {
await setConfig(`mcp_server_port_${env}`, envConfig.port.toString());
restoredSettings.push(`server_port_${env}`);
}
if (envConfig.ide_config) {
await setConfig(`mcp_ide_config_${env}`, JSON.stringify(envConfig.ide_config));
restoredSettings.push(`ide_config_${env}`);
}
}
}
logger.info(`๐ก MCP configuration restored: ${restoredSettings.length} settings`);
logger.info(`๐ง Restored settings: ${restoredSettings.join(', ')}`);
return {
success: true,
restored_settings: restoredSettings
};
}
catch (error) {
logger.error('Failed to restore MCP configuration:', error instanceof Error ? error.message : 'Unknown error');
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
/**
* Post-restoration MCP server setup
*/
async setupMCPAfterRestore() {
try {
const actionsTaken = [];
// Check if MCP server needs to be restarted
const { getConfig } = await import('../../storage/unified-database.js');
const transportType = await getConfig('mcp_transport_type');
if (transportType === 'http+sse') {
logger.info('๐ Restarting MCP server after database restoration...');
// Try to restart MCP server (this would need to be implemented in CLI)
try {
const { execSync } = await import('child_process');
// Stop any existing MCP server
try {
execSync('hive mcp-server stop', { stdio: 'pipe' });
actionsTaken.push('stopped_existing_server');
}
catch (error) {
// Server might not be running, which is fine
}
// Start MCP server
execSync('hive mcp-server start', { stdio: 'pipe' });
actionsTaken.push('started_mcp_server');
// Setup IDE configurations
execSync('hive setup-ide --silent', { stdio: 'pipe' });
actionsTaken.push('configured_ides');
logger.info('โ
MCP server setup completed after restoration');
}
catch (error) {
logger.warn('Could not automatically restart MCP server. Manual restart recommended: hive mcp-server restart');
actionsTaken.push('manual_restart_required');
}
}
return {
success: true,
actions_taken: actionsTaken
};
}
catch (error) {
logger.error('Failed to setup MCP after restoration:', error instanceof Error ? error.message : 'Unknown error');
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
/**
* Ensure backup directory structure exists
*/
ensureBackupDirectory() {
const subdirs = ['daily', 'weekly', 'monthly', 'emergency', 'manual', 'mcp-configs'];
try {
if (!fs.existsSync(this.backupDir)) {
fs.mkdirSync(this.backupDir, { recursive: true });
}
for (const subdir of subdirs) {
const fullPath = path.join(this.backupDir, subdir);
if (!fs.existsSync(fullPath)) {
fs.mkdirSync(fullPath, { recursive: true });
}
}
logger.info('โ
Backup directory structure initialized');
}
catch (error) {
logger.error(`โ Failed to create backup directories: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
/**
* Create a full backup of the SQLite database
*/
async createBackup(type = 'full', label) {
const startTime = Date.now();
try {
if (!fs.existsSync(this.dbPath)) {
return { success: false, error: 'Source database does not exist' };
}
// Get primary backup location
const primaryLocation = await this.locationManager.getPrimaryLocation();
if (!primaryLocation || !primaryLocation.available) {
return { success: false, error: 'Primary backup location not available' };
}
// Generate backup filename with timestamp
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const backupSubdir = type === 'emergency' ? 'emergency' :
type === 'incremental' ? 'incremental' :
label ? 'manual' : this.getBackupSubdir();
const filename = label ?
`hive-ai-knowledge-${label}-${timestamp}.db` :
`hive-ai-knowledge-${type}-${timestamp}.db`;
const backupPath = path.join(primaryLocation.path, backupSubdir, filename);
// Get source database stats
const sourceStats = fs.statSync(this.dbPath);
// Get conversation count for metadata
const conversationCount = await this.getConversationCount();
// Perform the backup (SQLite BACKUP command for consistency)
await this.performSQLiteBackup(this.dbPath, backupPath);
// Calculate checksum for integrity verification
const checksum = await this.calculateChecksum(backupPath);
// Capture MCP configuration information for backup metadata
const mcpConfigInfo = await this.captureMCPConfigInfo();
// Create metadata
const metadata = {
timestamp: new Date().toISOString(),
type,
size_bytes: fs.statSync(backupPath).size,
conversation_count: conversationCount,
checksum,
schema_version: await this.getSchemaVersion(),
backup_duration_ms: Date.now() - startTime,
integrity_verified: await this.verifyBackupIntegrity(backupPath, checksum),
...mcpConfigInfo
};
// Save metadata alongside backup
const metadataPath = backupPath.replace('.db', '.metadata.json');
fs.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2));
// Compression if enabled
if (this.config.compression_enabled) {
await this.compressBackup(backupPath);
}
// Cleanup old backups according to retention policy
await this.cleanupOldBackups(backupSubdir, type);
// Sync to secondary backup locations if enabled
const syncResult = await this.locationManager.syncBackupsAcrossLocations(filename);
if (syncResult.synced_locations.length > 0) {
logger.info(`๐ Synced backup to ${syncResult.synced_locations.length} additional locations: ${syncResult.synced_locations.join(', ')}`);
}
if (syncResult.failed_locations.length > 0) {
logger.warn(`โ ๏ธ Failed to sync to ${syncResult.failed_locations.length} locations: ${syncResult.failed_locations.join(', ')}`);
}
logger.info(`โ
Backup created successfully: ${filename}`);
logger.info(`๐ Backup stats: ${metadata.size_bytes} bytes, ${metadata.conversation_count} conversations, ${metadata.backup_duration_ms}ms`);
return {
success: true,
backup_path: backupPath,
metadata
};
}
catch (error) {
logger.error(`โ Backup creation failed: ${error instanceof Error ? error.message : 'Unknown backup error'}`);
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown backup error'
};
}
}
/**
* Perform SQLite backup using file copy (reliable and consistent)
*/
async performSQLiteBackup(sourcePath, backupPath) {
try {
// Use file copy for reliable backup
// SQLite database files can be safely copied when no active writes are happening
fs.copyFileSync(sourcePath, backupPath);
logger.debug(`๐ Backup copied: ${path.basename(sourcePath)} -> ${path.basename(backupPath)}`);
}
catch (error) {
throw new Error(`Failed to copy database file: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
/**
* Restore database from a specific backup
*/
async restoreFromBackup(backupPath, options = {}) {
try {
// Validate backup exists
if (!fs.existsSync(backupPath)) {
return { success: false, error: 'Backup file does not exist' };
}
// Load and verify metadata
const metadataPath = backupPath.replace('.db', '.metadata.json');
let metadata = null;
if (fs.existsSync(metadataPath)) {
metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
// Verify integrity if requested and metadata available
if (options.verify_integrity && metadata) {
const isValid = await this.verifyBackupIntegrity(backupPath, metadata.checksum);
if (!isValid) {
return { success: false, error: 'Backup integrity verification failed' };
}
}
}
// Create emergency backup of current database if requested
let emergencyBackupPath;
if (options.create_emergency_backup && fs.existsSync(this.dbPath)) {
const emergencyResult = await this.createBackup('emergency', 'pre-restore');
if (emergencyResult.success) {
emergencyBackupPath = emergencyResult.backup_path;
logger.info(`๐ฆ Emergency backup created: ${emergencyBackupPath}`);
}
}
// Decompress if needed
let actualBackupPath = backupPath;
if (backupPath.endsWith('.gz')) {
actualBackupPath = await this.decompressBackup(backupPath);
}
// Perform the restoration
if (fs.existsSync(this.dbPath)) {
// Create a backup of current file before overwriting
const tempPath = `${this.dbPath}.pre-restore-${Date.now()}`;
fs.copyFileSync(this.dbPath, tempPath);
}
// Copy backup to primary location
fs.copyFileSync(actualBackupPath, this.dbPath);
// Verify the restored database
const isValidRestore = await this.verifyRestoredDatabase();
if (!isValidRestore) {
return { success: false, error: 'Restored database failed validation' };
}
// Log restoration success with metadata
if (metadata) {
logger.info(`โ
Database restored successfully from backup created: ${metadata.timestamp}`);
logger.info(`๐ Restored: ${metadata.conversation_count} conversations, ${metadata.size_bytes} bytes`);
// If MCP configuration was included in backup, attempt to restart MCP server
if (metadata.mcp_config_included && metadata.mcp_transport_type === 'http+sse') {
logger.info('๐ง MCP configuration detected in backup, setting up MCP server...');
const mcpSetupResult = await this.setupMCPAfterRestore();
if (mcpSetupResult.success) {
logger.info(`๐ก MCP server setup completed: ${mcpSetupResult.actions_taken?.join(', ')}`);
}
else {
logger.warn(`โ ๏ธ MCP server setup failed: ${mcpSetupResult.error}`);
logger.warn('Manual MCP server restart recommended: hive mcp-server restart');
}
}
}
else {
logger.info(`โ
Database restored successfully from: ${path.basename(backupPath)}`);
}
return {
success: true,
emergency_backup_path: emergencyBackupPath
};
}
catch (error) {
logger.error(`โ Database restoration failed: ${error instanceof Error ? error.message : 'Unknown restoration error'}`);
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown restoration error'
};
}
}
/**
* List all available backups with metadata
*/
async listBackups() {
const backupTypes = ['daily', 'weekly', 'monthly', 'emergency', 'manual'];
const result = {};
for (const type of backupTypes) {
const typeDir = path.join(this.backupDir, type);
result[type] = [];
if (fs.existsSync(typeDir)) {
const files = fs.readdirSync(typeDir)
.filter(file => file.endsWith('.metadata.json'))
.sort()
.reverse(); // Most recent first
for (const metadataFile of files) {
try {
const metadataPath = path.join(typeDir, metadataFile);
const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
result[type].push(metadata);
}
catch (error) {
logger.warn(`Failed to read backup metadata: ${metadataFile} - ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
}
}
return result;
}
/**
* Get backup statistics and health
*/
async getBackupHealth() {
try {
const allBackups = await this.listBackups();
const flatBackups = [
...allBackups.daily,
...allBackups.weekly,
...allBackups.monthly,
...allBackups.emergency,
...allBackups.manual
].sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
const totalSize = flatBackups.reduce((sum, backup) => sum + backup.size_bytes, 0);
const integrityIssues = flatBackups
.filter(backup => !backup.integrity_verified)
.map(backup => `${backup.timestamp}: Integrity not verified`);
// Calculate backup frequency
let backupFrequency = 0;
if (flatBackups.length >= 2) {
const latest = new Date(flatBackups[0].timestamp);
const oldest = new Date(flatBackups[flatBackups.length - 1].timestamp);
const daysDiff = (latest.getTime() - oldest.getTime()) / (1000 * 60 * 60 * 24);
backupFrequency = daysDiff / flatBackups.length;
}
// Generate recommendations
const recommendations = [];
if (flatBackups.length < 3) {
recommendations.push('Consider creating more backup points for better disaster recovery');
}
if (integrityIssues.length > 0) {
recommendations.push('Some backups have integrity issues - recommend re-creating them');
}
if (backupFrequency > 7) {
recommendations.push('Backup frequency is low - consider more frequent backups');
}
return {
total_backups: flatBackups.length,
total_size_bytes: totalSize,
latest_backup: flatBackups.length > 0 ? flatBackups[0].timestamp : null,
oldest_backup: flatBackups.length > 0 ? flatBackups[flatBackups.length - 1].timestamp : null,
backup_frequency_days: backupFrequency,
integrity_issues: integrityIssues,
disk_usage_percentage: await this.calculateDiskUsage(),
recommendations
};
}
catch (error) {
logger.error(`Failed to get backup health: ${error instanceof Error ? error.message : 'Unknown error'}`);
return {
total_backups: 0,
total_size_bytes: 0,
latest_backup: null,
oldest_backup: null,
backup_frequency_days: 0,
integrity_issues: ['Failed to analyze backup health'],
disk_usage_percentage: 0,
recommendations: ['Backup system health check failed - investigate backup directory']
};
}
}
/**
* Emergency disaster recovery - restore from most recent valid backup
*/
async emergencyRestore() {
const stepsTaken = [];
try {
stepsTaken.push('Starting emergency disaster recovery');
// Find the most recent backup with verified integrity
const allBackups = await this.listBackups();
const flatBackups = [
...allBackups.daily,
...allBackups.weekly,
...allBackups.monthly,
...allBackups.emergency,
...allBackups.manual
].sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
stepsTaken.push(`Found ${flatBackups.length} backup candidates`);
// Find first backup with verified integrity
let selectedBackup = null;
for (const backup of flatBackups) {
if (backup.integrity_verified) {
selectedBackup = backup;
break;
}
}
if (!selectedBackup) {
// If no verified backups, use the most recent one
selectedBackup = flatBackups[0];
stepsTaken.push('No verified backups found, using most recent backup');
}
else {
stepsTaken.push(`Selected verified backup from ${selectedBackup.timestamp}`);
}
if (!selectedBackup) {
return {
success: false,
error: 'No backups available for emergency restoration',
steps_taken: stepsTaken
};
}
// Determine backup file path
const backupFileName = `hive-ai-knowledge-${selectedBackup.type}-${selectedBackup.timestamp.replace(/[:.]/g, '-')}.db`;
const possiblePaths = [
path.join(this.backupDir, 'daily', backupFileName),
path.join(this.backupDir, 'weekly', backupFileName),
path.join(this.backupDir, 'monthly', backupFileName),
path.join(this.backupDir, 'emergency', backupFileName),
path.join(this.backupDir, 'manual', backupFileName)
];
let backupPath = null;
for (const possiblePath of possiblePaths) {
if (fs.existsSync(possiblePath)) {
backupPath = possiblePath;
break;
}
}
if (!backupPath) {
return {
success: false,
error: 'Backup file not found on disk',
steps_taken: stepsTaken
};
}
stepsTaken.push(`Located backup file: ${path.basename(backupPath)}`);
// Perform the restoration
const restoreResult = await this.restoreFromBackup(backupPath, {
verify_integrity: true,
create_emergency_backup: true,
timestamp_verification: true
});
if (restoreResult.success) {
stepsTaken.push('Database restored successfully');
stepsTaken.push('Emergency recovery completed');
return {
success: true,
restored_from: selectedBackup.timestamp,
steps_taken: stepsTaken
};
}
else {
return {
success: false,
error: restoreResult.error,
steps_taken: stepsTaken
};
}
}
catch (error) {
stepsTaken.push(`Emergency recovery failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown emergency recovery error',
steps_taken: stepsTaken
};
}
}
// Helper methods (implementation details)
getBackupSubdir() {
const now = new Date();
const hour = now.getHours();
const day = now.getDay();
const date = now.getDate();
// Determine if this should be monthly, weekly, or daily backup
if (date === this.config.auto_backup_schedule.monthly_date) {
return 'monthly';
}
else if (day === this.config.auto_backup_schedule.weekly_day) {
return 'weekly';
}
else {
return 'daily';
}
}
async getConversationCount() {
try {
const stats = await this.memorySystem.getMemoryStats();
return stats.total_conversations || 0;
}
catch {
return 0;
}
}
async calculateChecksum(filePath) {
const crypto = await import('crypto');
const data = fs.readFileSync(filePath);
return crypto.createHash('sha256').update(data).digest('hex');
}
async getSchemaVersion() {
// This could query the database for schema version
return '1.0.0';
}
async verifyBackupIntegrity(backupPath, expectedChecksum) {
try {
const actualChecksum = await this.calculateChecksum(backupPath);
return actualChecksum === expectedChecksum;
}
catch {
return false;
}
}
async compressBackup(backupPath) {
// Implementation for backup compression using gzip
const zlib = await import('zlib');
const input = fs.createReadStream(backupPath);
const output = fs.createWriteStream(`${backupPath}.gz`);
return new Promise((resolve, reject) => {
input.pipe(zlib.createGzip()).pipe(output)
.on('finish', () => {
fs.unlinkSync(backupPath); // Remove uncompressed version
resolve();
})
.on('error', reject);
});
}
async decompressBackup(compressedPath) {
const zlib = await import('zlib');
const outputPath = compressedPath.replace('.gz', '');
const input = fs.createReadStream(compressedPath);
const output = fs.createWriteStream(outputPath);
return new Promise((resolve, reject) => {
input.pipe(zlib.createGunzip()).pipe(output)
.on('finish', () => resolve(outputPath))
.on('error', reject);
});
}
async cleanupOldBackups(subdir, type) {
const typeDir = path.join(this.backupDir, subdir);
const maxBackups = this.config.max_backups[subdir] || 5;
try {
const files = fs.readdirSync(typeDir)
.filter(file => file.endsWith('.db') || file.endsWith('.db.gz'))
.map(file => ({
name: file,
path: path.join(typeDir, file),
mtime: fs.statSync(path.join(typeDir, file)).mtime
}))
.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
// Remove excess backups
if (files.length > maxBackups) {
const filesToRemove = files.slice(maxBackups);
for (const file of filesToRemove) {
fs.unlinkSync(file.path);
// Also remove corresponding metadata file
const metadataPath = file.path.replace(/\.db(\.gz)?$/, '.metadata.json');
if (fs.existsSync(metadataPath)) {
fs.unlinkSync(metadataPath);
}
}
logger.info(`๐งน Cleaned up ${filesToRemove.length} old ${subdir} backups`);
}
}
catch (error) {
logger.warn(`Failed to cleanup old backups in ${subdir}: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
async verifyRestoredDatabase() {
try {
// Basic integrity check - try to open and query the database
const stats = await this.memorySystem.getMemoryStats();
return typeof stats.total_conversations === 'number';
}
catch {
return false;
}
}
async calculateDiskUsage() {
try {
const stats = fs.statSync(this.backupDir);
// This is a simplified calculation - in reality you'd want to calculate
// total directory size vs available disk space
return 5; // Placeholder percentage
}
catch {
return 0;
}
}
scheduleAutomaticBackups() {
if (!this.config.enabled) {
return;
}
// This would typically use a scheduler like node-cron
// For now, just log that scheduling would happen
logger.info('๐
Automatic backup scheduling configured');
logger.info(`โฐ Daily backups at ${this.config.auto_backup_schedule.daily_hour}:00`);
logger.info(`๐
Weekly backups on day ${this.config.auto_backup_schedule.weekly_day}`);
logger.info(`๐ Monthly backups on date ${this.config.auto_backup_schedule.monthly_date}`);
}
}
//# sourceMappingURL=sqlite-backup-manager.js.map