351 lines
11 KiB
TypeScript
351 lines
11 KiB
TypeScript
#!/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';
|
|
import { formatDateForDisplay } from '../src/lib/date-utils';
|
|
|
|
interface CliOptions {
|
|
command: string;
|
|
filename?: string;
|
|
config?: Partial<BackupConfig>;
|
|
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 <filename> Supprimer une sauvegarde
|
|
restore <filename> Restaurer une sauvegarde
|
|
verify Vérifier l'intégrité de la base
|
|
config Afficher la configuration
|
|
config-set <key=value> 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<boolean> {
|
|
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 formatDateForDisplay(date, 'DISPLAY_LONG');
|
|
}
|
|
|
|
async run(args: string[]): Promise<void> {
|
|
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<void> {
|
|
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<void> {
|
|
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<void> {
|
|
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<void> {
|
|
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<void> {
|
|
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<void> {
|
|
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<void> {
|
|
const [key, value] = configString.split('=');
|
|
|
|
if (!key || !value) {
|
|
console.error('❌ Format invalide. Utilisez: key=value');
|
|
process.exit(1);
|
|
}
|
|
|
|
const newConfig: Partial<BackupConfig> = {};
|
|
|
|
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<void> {
|
|
backupScheduler.start();
|
|
console.log('✅ Planificateur de sauvegarde démarré');
|
|
}
|
|
|
|
private async stopScheduler(): Promise<void> {
|
|
backupScheduler.stop();
|
|
console.log('🛑 Planificateur de sauvegarde arrêté');
|
|
}
|
|
|
|
private async schedulerStatus(): Promise<void> {
|
|
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 };
|