UNPKG

polyv-live-cli

Version:

CLI tool for managing PolyV live streaming services.

593 lines 25 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.defaultConfigRecoveryManager = exports.ConfigRecoveryManager = void 0; const fs = __importStar(require("fs")); const path = __importStar(require("path")); const config_version_manager_1 = require("./config-version-manager"); const file_permission_manager_1 = require("./file-permission-manager"); class ConfigRecoveryManager { constructor() { this.versionManager = new config_version_manager_1.ConfigVersionManager(); this.permissionManager = new file_permission_manager_1.FilePermissionManager(); } checkIntegrity(configPath) { const issues = []; const validationDetails = { fileAccessible: false, validJson: false, validStructure: false, validEncryption: false, securePermissions: false }; try { if (!fs.existsSync(configPath)) { issues.push({ type: 'file_access', severity: 'critical', description: 'Configuration file does not exist', suggestedFix: 'Create a new configuration file or restore from backup', autoFixable: false }); } else { validationDetails.fileAccessible = true; try { const fileContent = fs.readFileSync(configPath, 'utf8'); let config; try { config = JSON.parse(fileContent); validationDetails.validJson = true; } catch (jsonError) { issues.push({ type: 'json_format', severity: 'critical', description: 'Configuration file contains invalid JSON', suggestedFix: 'Restore from backup or recreate configuration file', autoFixable: false }); throw jsonError; } if (this.validateConfigStructure(config)) { validationDetails.validStructure = true; } else { issues.push({ type: 'structure', severity: 'critical', description: 'Configuration file has invalid structure', suggestedFix: 'Restore from backup or recreate with correct structure', autoFixable: false }); } if (this.validateEncryptionData(config)) { validationDetails.validEncryption = true; } else { issues.push({ type: 'encryption', severity: 'critical', description: 'Encrypted data in configuration is corrupted or invalid', suggestedFix: 'Check POLYV_MASTER_KEY environment variable or restore from backup', autoFixable: false }); } try { const versionValidation = this.versionManager.validateVersion(config); if (!versionValidation.isValid && versionValidation.compatibility === 'incompatible') { issues.push({ type: 'version', severity: 'warning', description: `Configuration version is incompatible: ${versionValidation.detectedVersion}`, suggestedFix: 'Update CLI or migrate configuration to compatible version', autoFixable: false }); } } catch (versionError) { issues.push({ type: 'version', severity: 'warning', description: 'Unable to determine configuration version', suggestedFix: 'Check configuration format and structure', autoFixable: false }); } } catch (readError) { issues.push({ type: 'file_access', severity: 'critical', description: 'Cannot read configuration file (permission denied or file corrupted)', suggestedFix: 'Check file permissions and integrity', autoFixable: false }); } try { const permissionValidation = this.permissionManager.validatePermissions(configPath); if (permissionValidation.isValid) { validationDetails.securePermissions = true; } else { issues.push({ type: 'permissions', severity: 'warning', description: 'Configuration file has insecure permissions', suggestedFix: 'Run: polyv-live-cli config fix-permissions', autoFixable: true }); } } catch (permError) { issues.push({ type: 'permissions', severity: 'warning', description: 'Cannot check file permissions', suggestedFix: 'Manually verify file permissions are secure (600)', autoFixable: false }); } } const isValid = issues.filter(i => i.severity === 'critical').length === 0; const criticalCount = issues.filter(i => i.severity === 'critical').length; const warningCount = issues.filter(i => i.severity === 'warning').length; let message; if (isValid && issues.length === 0) { message = 'Configuration file is healthy and valid'; } else if (isValid) { message = `Configuration file is valid but has ${warningCount} warning(s)`; } else { message = `Configuration file is corrupted with ${criticalCount} critical issue(s) and ${warningCount} warning(s)`; } return { isValid, message, issues, validationDetails }; } catch (error) { return { isValid: false, message: `Integrity check failed: ${error instanceof Error ? error.message : 'Unknown error'}`, issues: [{ type: 'file_access', severity: 'critical', description: 'Failed to perform integrity check', suggestedFix: 'Check file path and permissions', autoFixable: false }], validationDetails }; } } validateConfigStructure(config) { if (!config || typeof config !== 'object') { return false; } if (!config.version || typeof config.version !== 'string') { return false; } if (!config.accounts || typeof config.accounts !== 'object') { return false; } if (!config.metadata || typeof config.metadata !== 'object') { return false; } if (!config.metadata.createdAt || typeof config.metadata.createdAt !== 'string') { return false; } if (!config.metadata.updatedAt || typeof config.metadata.updatedAt !== 'string') { return false; } for (const accountName in config.accounts) { const account = config.accounts[accountName]; if (!account || typeof account !== 'object') { return false; } if (!account.name || typeof account.name !== 'string') { return false; } if (!account.appId || typeof account.appId !== 'string') { return false; } if (!account.appSecret) { return false; } if (!account.createdAt || typeof account.createdAt !== 'string') { return false; } if (!account.updatedAt || typeof account.updatedAt !== 'string') { return false; } } return true; } validateEncryptionData(config) { if (!config.accounts) { return true; } try { for (const accountName in config.accounts) { const account = config.accounts[accountName]; if (!account.appSecret) { continue; } if (typeof account.appSecret === 'object') { if (!account.appSecret.algorithm || !account.appSecret.iv || !account.appSecret.authTag || !account.appSecret.encrypted) { return false; } } else if (typeof account.appSecret === 'string') { try { const decoded = Buffer.from(account.appSecret, 'base64').toString('utf8'); JSON.parse(decoded); } catch { } } else { return false; } } return true; } catch { return false; } } getRecoveryStrategies(integrityResult) { const strategies = []; const autoFixableIssues = integrityResult.issues.filter(i => i.autoFixable); if (autoFixableIssues.length > 0) { strategies.push({ id: 'auto_fix', name: 'Auto-fix Minor Issues', description: 'Automatically fix permission and minor structural issues', steps: [ 'Fix file permissions (set to 600)', 'Repair minor structural issues', 'Validate configuration after fixes' ], riskLevel: 'low', dataPreservation: 'full' }); } strategies.push({ id: 'restore_backup', name: 'Restore from Backup', description: 'Restore configuration from the most recent valid backup', steps: [ 'Locate most recent backup file', 'Validate backup integrity', 'Replace current configuration with backup', 'Set secure file permissions' ], riskLevel: 'low', dataPreservation: 'full' }); if (integrityResult.validationDetails.fileAccessible && integrityResult.validationDetails.validJson) { strategies.push({ id: 'manual_fix', name: 'Manual Configuration Repair', description: 'Manually repair specific issues in the configuration', steps: [ 'Create backup of current configuration', 'Guide through manual repair process', 'Validate each fixed section', 'Test encryption/decryption functionality' ], riskLevel: 'medium', dataPreservation: 'partial' }); } strategies.push({ id: 'recreate', name: 'Create New Configuration', description: 'Create a completely new configuration file (data loss)', steps: [ 'Backup existing configuration (if possible)', 'Create new configuration structure', 'Guide through account re-addition process', 'Set secure permissions and encryption' ], riskLevel: 'high', dataPreservation: 'none' }); const hasEncryptionIssues = integrityResult.issues.some(i => i.type === 'encryption'); if (hasEncryptionIssues) { strategies.push({ id: 'key_recovery', name: 'Encryption Key Recovery', description: 'Attempt to recover using different encryption keys', steps: [ 'Check for POLYV_MASTER_KEY environment variable', 'Try default key generation', 'Guide through key recovery process', 'Test decryption with recovered key' ], riskLevel: 'medium', dataPreservation: 'full' }); } return strategies; } executeRecovery(strategyId, configPath, options = {}) { const stepsExecuted = []; const warnings = []; try { switch (strategyId) { case 'auto_fix': return this.executeAutoFix(configPath, stepsExecuted, warnings); case 'restore_backup': return this.executeBackupRestore(configPath, options.backupPath, stepsExecuted, warnings); case 'manual_fix': return this.executeManualFix(configPath, stepsExecuted, warnings); case 'recreate': return this.executeRecreate(configPath, stepsExecuted, warnings); case 'key_recovery': return this.executeKeyRecovery(configPath, options.newMasterKey, stepsExecuted, warnings); default: return { success: false, message: `Unknown recovery strategy: ${strategyId}`, strategyUsed: strategyId, stepsExecuted, warnings }; } } catch (error) { return { success: false, message: `Recovery failed: ${error instanceof Error ? error.message : 'Unknown error'}`, strategyUsed: strategyId, stepsExecuted, warnings }; } } executeAutoFix(configPath, stepsExecuted, warnings) { stepsExecuted.push('Starting auto-fix process'); const permissionResult = this.permissionManager.repairConfigurationFile(configPath); if (permissionResult.success) { stepsExecuted.push('Fixed file permissions'); } else { warnings.push(`Could not fix permissions: ${permissionResult.message}`); } stepsExecuted.push('Auto-fix completed'); return { success: true, message: 'Auto-fix completed successfully', strategyUsed: 'auto_fix', stepsExecuted, warnings }; } executeBackupRestore(configPath, backupPath, stepsExecuted, warnings) { stepsExecuted.push('Starting backup restore process'); if (!backupPath) { const backups = this.findBackupFiles(configPath); if (backups.length === 0) { return { success: false, message: 'No backup files found', strategyUsed: 'restore_backup', stepsExecuted, warnings: ['No backup files available for restoration'] }; } backupPath = backups[0].path; stepsExecuted.push(`Found backup file: ${backupPath}`); } const backupIntegrity = this.checkIntegrity(backupPath); if (!backupIntegrity.isValid) { return { success: false, message: 'Backup file is also corrupted', strategyUsed: 'restore_backup', stepsExecuted, warnings: ['Backup file integrity check failed'] }; } stepsExecuted.push('Backup integrity validated'); const currentBackupPath = `${configPath}.corrupted.${Date.now()}`; if (fs.existsSync(configPath)) { fs.copyFileSync(configPath, currentBackupPath); stepsExecuted.push(`Backed up corrupted file to: ${currentBackupPath}`); } fs.copyFileSync(backupPath, configPath); stepsExecuted.push('Configuration restored from backup'); this.permissionManager.setSecurePermissions(configPath); stepsExecuted.push('Set secure file permissions'); return { success: true, message: 'Configuration successfully restored from backup', strategyUsed: 'restore_backup', stepsExecuted, warnings, backupFile: currentBackupPath }; } executeManualFix(_configPath, stepsExecuted, warnings) { stepsExecuted.push('Manual fix requires interactive process'); warnings.push('Manual fix must be performed interactively'); return { success: false, message: 'Manual fix requires interactive CLI process', strategyUsed: 'manual_fix', stepsExecuted, warnings }; } executeRecreate(configPath, stepsExecuted, warnings) { stepsExecuted.push('Starting configuration recreation'); let backupFile; if (fs.existsSync(configPath)) { backupFile = `${configPath}.backup.${Date.now()}`; fs.copyFileSync(configPath, backupFile); stepsExecuted.push(`Backed up existing configuration to: ${backupFile}`); } const newConfig = this.versionManager.createNewConfiguration(); const configDir = path.dirname(configPath); if (!fs.existsSync(configDir)) { fs.mkdirSync(configDir, { recursive: true, mode: 0o700 }); stepsExecuted.push('Created configuration directory'); } fs.writeFileSync(configPath, JSON.stringify(newConfig, null, 2), 'utf8'); stepsExecuted.push('Created new configuration file'); this.permissionManager.setSecurePermissions(configPath); stepsExecuted.push('Set secure file permissions'); warnings.push('All previous account data has been lost'); warnings.push('You will need to re-add your accounts'); return { success: true, message: 'New configuration file created successfully', strategyUsed: 'recreate', stepsExecuted, warnings, recoveredConfig: newConfig, ...(backupFile && { backupFile }) }; } executeKeyRecovery(_configPath, _newMasterKey, stepsExecuted, warnings) { stepsExecuted.push('Key recovery requires encryption key'); warnings.push('Key recovery must be performed with correct master key'); return { success: false, message: 'Key recovery requires valid master key', strategyUsed: 'key_recovery', stepsExecuted, warnings }; } findBackupFiles(configPath) { const backups = []; const configDir = path.dirname(configPath); const configName = path.basename(configPath); try { if (!fs.existsSync(configDir)) { return backups; } const files = fs.readdirSync(configDir); const backupPattern = new RegExp(`^${configName}\\.backup\\.(\\d+)$`); for (const file of files) { if (backupPattern.test(file)) { const filePath = path.join(configDir, file); const stats = fs.statSync(filePath); const integrity = this.checkIntegrity(filePath); backups.push({ path: filePath, createdAt: stats.mtime.toISOString(), size: stats.size, isValid: integrity.isValid }); } } backups.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()); } catch (error) { } return backups; } createBackup(configPath) { try { if (!fs.existsSync(configPath)) { return null; } const backupPath = `${configPath}.backup.${Date.now()}`; fs.copyFileSync(configPath, backupPath); this.permissionManager.setSecurePermissions(backupPath); return backupPath; } catch { return null; } } getRecoveryGuidance(integrityResult) { const guidance = []; if (integrityResult.isValid) { guidance.push('✅ Configuration file is healthy'); if (integrityResult.issues.length > 0) { guidance.push('⚠️ Minor issues detected:'); for (const issue of integrityResult.issues) { guidance.push(` • ${issue.description}`); guidance.push(` Fix: ${issue.suggestedFix}`); } } return guidance; } guidance.push('❌ Configuration file has critical issues'); guidance.push(''); guidance.push('Recommended recovery steps:'); guidance.push(''); const criticalIssues = integrityResult.issues.filter(i => i.severity === 'critical'); const warningIssues = integrityResult.issues.filter(i => i.severity === 'warning'); if (criticalIssues.length > 0) { guidance.push('🚨 Critical Issues:'); for (const issue of criticalIssues) { guidance.push(` • ${issue.description}`); guidance.push(` Solution: ${issue.suggestedFix}`); } guidance.push(''); } if (warningIssues.length > 0) { guidance.push('⚠️ Warnings:'); for (const issue of warningIssues) { guidance.push(` • ${issue.description}`); guidance.push(` Solution: ${issue.suggestedFix}`); } guidance.push(''); } guidance.push('💡 Recovery Options:'); guidance.push(' 1. Try: polyv-live-cli config recover --strategy auto_fix'); guidance.push(' 2. Try: polyv-live-cli config recover --strategy restore_backup'); guidance.push(' 3. Last resort: polyv-live-cli config recover --strategy recreate'); guidance.push(''); guidance.push('⚠️ Always backup your configuration before recovery attempts'); return guidance; } } exports.ConfigRecoveryManager = ConfigRecoveryManager; exports.defaultConfigRecoveryManager = new ConfigRecoveryManager(); //# sourceMappingURL=config-recovery-manager.js.map