/** * Backup Restore Command * Restores from a backup archive, executing the module's on_restore hook. */ import { formatSize, getBackup } from '../../services/backup-metadata'; import { restoreModuleBackup, restoreSystemStateBackup } from '../../services/backup-restore'; import { getBackupStorage } from '../../services/backup-storage'; import { askConfirm, withInterviewSession } from '../../services/bus-interview'; import { celiloIntro, celiloOutro } from '../prompts'; import type { CommandResult } from '../types'; export async function handleBackupRestore( args: string[], flags: Record = {}, ): Promise { try { celiloIntro('Restore Backup'); const backupId = args[0]; if (!backupId) { return { success: false, error: 'Backup ID is required\n\nUsage: celilo backup restore ', }; } const backup = getBackup(backupId); if (!backup) { return { success: false, error: `Backup not found: ${backupId}` }; } if (backup.status !== 'completed') { return { success: false, error: `Cannot restore backup with status '${backup.status}'. Only completed backups can be restored.`, }; } // Show backup details const storage = getBackupStorage(backup.storageId); const isSystem = backup.backupType === 'system_state'; console.log('\nBackup Details:'); console.log(` Type: ${isSystem ? 'System State' : 'Module Data'}`); if (!isSystem) { console.log(` Module: ${backup.moduleId ?? 'unknown'}`); if (backup.moduleVersion) { console.log(` Module Version: ${backup.moduleVersion}`); } if (backup.schemaVersion) { console.log(` Schema Version: ${backup.schemaVersion}`); } } console.log( ` Date: ${new Date(backup.startedAt).toISOString().replace('T', ' ').substring(0, 19)} UTC`, ); console.log(` Size: ${formatSize(backup.sizeBytes)}`); console.log(` Storage: ${storage?.storageId ?? 'unknown'}`); console.log(''); if (isSystem) { console.log('⚠ This will replace the current Celilo database with the backup version.'); console.log(' All module configs, secrets, and state will be overwritten.\n'); } // Confirm unless --yes if (!flags.yes) { const confirmed = await withInterviewSession(() => askConfirm({ scope: `backup-restore:${backupId}`, key: 'proceed', message: 'Proceed with restore?', defaultValue: false, }), ); if (!confirmed) { return { success: false, error: 'Cancelled by user' }; } } if (isSystem) { // System state restore console.log('\n▸ Downloading and decrypting backup...'); const result = await restoreSystemStateBackup(backup); if (!result.success) { console.log(`✗ ${result.error}`); return { success: false, error: result.error ?? 'Restore failed' }; } console.log('✓ System state restored'); celiloOutro('Restore complete. The database has been replaced with the backup version.'); } else { // Module data restore console.log('\n▸ Downloading and decrypting backup...'); console.log('▸ Executing on_restore hook...'); const result = await restoreModuleBackup(backup, { runHealthCheck: true }); if (!result.success) { console.log(`✗ ${result.error}`); return { success: false, error: result.error ?? 'Restore failed' }; } console.log('✓ on_restore completed'); if (result.healthCheckPassed === true) { console.log('✓ Health check passed'); } else if (result.healthCheckPassed === false) { console.log('⚠ Health check failed after restore'); } celiloOutro('Restore complete.'); } return { success: true, message: `Restored backup: ${backupId}` }; } catch (error) { return { success: false, error: `Restore failed: ${error instanceof Error ? error.message : String(error)}`, }; } }