#!/usr/bin/env tsx /** * Script de gestion des sauvegardes via CLI * Usage: tsx scripts/backup-manager.ts [command] [options] */ import { backupService, BackupConfig } from '../src/services/backup'; import { backupScheduler } from '../src/services/backup-scheduler'; interface CliOptions { command: string; filename?: string; config?: Partial; force?: boolean; help?: boolean; } class BackupManagerCLI { private printHelp(): void { console.log(` 🔧 TowerControl Backup Manager COMMANDES: create [--force] CrĂ©er une nouvelle sauvegarde (--force pour ignorer la dĂ©tection de changements) list Lister toutes les sauvegardes delete Supprimer une sauvegarde restore Restaurer une sauvegarde verify VĂ©rifier l'intĂ©gritĂ© de la base config Afficher la configuration config-set Modifier la configuration scheduler-start DĂ©marrer le planificateur scheduler-stop ArrĂȘter le planificateur scheduler-status Statut du planificateur help Afficher cette aide EXEMPLES: tsx backup-manager.ts create tsx backup-manager.ts create --force tsx backup-manager.ts list tsx backup-manager.ts delete towercontrol_2025-01-15T10-30-00-000Z.db tsx backup-manager.ts restore towercontrol_2025-01-15T10-30-00-000Z.db.gz tsx backup-manager.ts config-set interval=daily tsx backup-manager.ts config-set maxBackups=10 tsx backup-manager.ts verify OPTIONS: --force Forcer l'action sans confirmation --help Afficher l'aide `); } private parseArgs(args: string[]): CliOptions { const options: CliOptions = { command: args[0] || 'help', }; for (let i = 1; i < args.length; i++) { const arg = args[i]; if (arg === '--force') { options.force = true; } else if (arg === '--help') { options.help = true; } else if (!options.filename && !arg.startsWith('--')) { options.filename = arg; } } return options; } private async confirmAction(message: string, force?: boolean): Promise { if (force) return true; // Simulation d'une confirmation (en CLI rĂ©el, utiliser readline) console.log(`⚠ ${message}`); console.log('✅ Action confirmĂ©e (--force activĂ© ou mode auto)'); return true; } private formatFileSize(bytes: number): string { const units = ['B', 'KB', 'MB', 'GB']; let size = bytes; let unitIndex = 0; while (size >= 1024 && unitIndex < units.length - 1) { size /= 1024; unitIndex++; } return `${size.toFixed(1)} ${units[unitIndex]}`; } private formatDate(date: Date): string { return new Date(date).toLocaleString('fr-FR'); } async run(args: string[]): Promise { const options = this.parseArgs(args); if (options.help || options.command === 'help') { this.printHelp(); return; } try { switch (options.command) { case 'create': await this.createBackup(options.force || false); break; case 'list': await this.listBackups(); break; case 'delete': if (!options.filename) { console.error('❌ Nom de fichier requis pour la suppression'); process.exit(1); } await this.deleteBackup(options.filename, options.force); break; case 'restore': if (!options.filename) { console.error('❌ Nom de fichier requis pour la restauration'); process.exit(1); } await this.restoreBackup(options.filename, options.force); break; case 'verify': await this.verifyDatabase(); break; case 'config': await this.showConfig(); break; case 'config-set': if (!options.filename) { console.error('❌ Configuration requise (format: key=value)'); process.exit(1); } await this.setConfig(options.filename); break; case 'scheduler-start': await this.startScheduler(); break; case 'scheduler-stop': await this.stopScheduler(); break; case 'scheduler-status': await this.schedulerStatus(); break; default: console.error(`❌ Commande inconnue: ${options.command}`); this.printHelp(); process.exit(1); } } catch (error) { console.error(`❌ Erreur:`, error); process.exit(1); } } private async createBackup(force: boolean = false): Promise { console.log('🔄 CrĂ©ation d\'une sauvegarde...'); const result = await backupService.createBackup('manual', force); if (result === null) { console.log('⏭ Sauvegarde sautĂ©e: Aucun changement dĂ©tectĂ© depuis la derniĂšre sauvegarde'); console.log(' 💡 Utilisez --force pour crĂ©er une sauvegarde malgrĂ© tout'); return; } if (result.status === 'success') { console.log(`✅ Sauvegarde créée: ${result.filename}`); console.log(` Taille: ${this.formatFileSize(result.size)}`); if (result.databaseHash) { console.log(` Hash: ${result.databaseHash.substring(0, 12)}...`); } } else { console.error(`❌ Échec de la sauvegarde: ${result.error}`); process.exit(1); } } private async listBackups(): Promise { console.log('📋 Liste des sauvegardes:\n'); const backups = await backupService.listBackups(); if (backups.length === 0) { console.log(' Aucune sauvegarde disponible'); return; } console.log(`${'Nom'.padEnd(40)} ${'Taille'.padEnd(10)} ${'Type'.padEnd(12)} ${'Date'}`); console.log('─'.repeat(80)); for (const backup of backups) { const name = backup.filename.padEnd(40); const size = this.formatFileSize(backup.size).padEnd(10); const type = (backup.type === 'manual' ? 'Manuelle' : 'Automatique').padEnd(12); const date = this.formatDate(backup.createdAt); console.log(`${name} ${size} ${type} ${date}`); } console.log(`\n📊 Total: ${backups.length} sauvegarde(s)`); } private async deleteBackup(filename: string, force?: boolean): Promise { const confirmed = await this.confirmAction( `Supprimer la sauvegarde "${filename}" ?`, force ); if (!confirmed) { console.log('❌ Suppression annulĂ©e'); return; } await backupService.deleteBackup(filename); console.log(`✅ Sauvegarde supprimĂ©e: ${filename}`); } private async restoreBackup(filename: string, force?: boolean): Promise { const confirmed = await this.confirmAction( `Restaurer la base de donnĂ©es depuis "${filename}" ? ATTENTION: Cela remplacera toutes les donnĂ©es actuelles !`, force ); if (!confirmed) { console.log('❌ Restauration annulĂ©e'); return; } console.log('🔄 Restauration en cours...'); await backupService.restoreBackup(filename); console.log(`✅ Base de donnĂ©es restaurĂ©e depuis: ${filename}`); } private async verifyDatabase(): Promise { console.log('🔍 VĂ©rification de l\'intĂ©gritĂ© de la base...'); await backupService.verifyDatabaseHealth(); console.log('✅ Base de donnĂ©es vĂ©rifiĂ©e avec succĂšs'); } private async showConfig(): Promise { const config = backupService.getConfig(); const status = backupScheduler.getStatus(); console.log('⚙ Configuration des sauvegardes:\n'); console.log(` ActivĂ©: ${config.enabled ? '✅ Oui' : '❌ Non'}`); console.log(` FrĂ©quence: ${config.interval}`); console.log(` Max sauvegardes: ${config.maxBackups}`); console.log(` Compression: ${config.compression ? '✅ Oui' : '❌ Non'}`); console.log(` Chemin: ${config.backupPath}`); console.log(`\n📊 Statut du planificateur:`); console.log(` En cours: ${status.isRunning ? '✅ Oui' : '❌ Non'}`); console.log(` Prochaine: ${status.nextBackup ? this.formatDate(status.nextBackup) : 'Non planifiĂ©e'}`); } private async setConfig(configString: string): Promise { const [key, value] = configString.split('='); if (!key || !value) { console.error('❌ Format invalide. Utilisez: key=value'); process.exit(1); } const newConfig: Partial = {}; switch (key) { case 'enabled': newConfig.enabled = value.toLowerCase() === 'true'; break; case 'interval': if (!['hourly', 'daily', 'weekly'].includes(value)) { console.error('❌ Interval invalide. Utilisez: hourly, daily, ou weekly'); process.exit(1); } newConfig.interval = value as BackupConfig['interval']; break; case 'maxBackups': const maxBackups = parseInt(value); if (isNaN(maxBackups) || maxBackups < 1) { console.error('❌ maxBackups doit ĂȘtre un nombre positif'); process.exit(1); } newConfig.maxBackups = maxBackups; break; case 'compression': newConfig.compression = value.toLowerCase() === 'true'; break; default: console.error(`❌ ClĂ© de configuration inconnue: ${key}`); process.exit(1); } backupService.updateConfig(newConfig); console.log(`✅ Configuration mise Ă  jour: ${key} = ${value}`); // RedĂ©marrer le scheduler si nĂ©cessaire if (key === 'enabled' || key === 'interval') { backupScheduler.restart(); console.log('🔄 Planificateur redĂ©marrĂ© avec la nouvelle configuration'); } } private async startScheduler(): Promise { backupScheduler.start(); console.log('✅ Planificateur de sauvegarde dĂ©marrĂ©'); } private async stopScheduler(): Promise { backupScheduler.stop(); console.log('🛑 Planificateur de sauvegarde arrĂȘtĂ©'); } private async schedulerStatus(): Promise { const status = backupScheduler.getStatus(); console.log('📊 Statut du planificateur:\n'); console.log(` État: ${status.isRunning ? '✅ Actif' : '❌ ArrĂȘtĂ©'}`); console.log(` ActivĂ©: ${status.isEnabled ? '✅ Oui' : '❌ Non'}`); console.log(` FrĂ©quence: ${status.interval}`); console.log(` Prochaine: ${status.nextBackup ? this.formatDate(status.nextBackup) : 'Non planifiĂ©e'}`); console.log(` Max sauvegardes: ${status.maxBackups}`); } } // ExĂ©cution du script if (require.main === module) { const cli = new BackupManagerCLI(); const args = process.argv.slice(2); cli.run(args).catch((error) => { console.error('❌ Erreur fatale:', error); process.exit(1); }); } export { BackupManagerCLI };