diff --git a/changelog.md b/changelog.md index 1fbd1ea..c127446 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,12 @@ # Changelog +## 2025-03-26 - 2.6.8 - fix(cli) +Improve CLI formatting, refine debug option filtering, and remove unused dgram import in SNMP manager + +- Standardize whitespace and formatting in ts/cli.ts for consistency +- Refine argument filtering for debug mode and prompt messages +- Remove unused 'dgram' import from ts/snmp/manager.ts + ## 2025-03-26 - 2.6.7 - fix(setup.sh) Clarify net-snmp dependency installation message in setup.sh diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 16f4d2d..5376368 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,7 +9,7 @@ importers: .: dependencies: net-snmp: - specifier: ^3.20.0 + specifier: 3.20.0 version: 3.20.0 devDependencies: '@git.zone/tsbuild': diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index be7cc80..8b80f29 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@serve.zone/nupst', - version: '2.6.7', + version: '2.6.8', description: 'Node.js UPS Shutdown Tool for SNMP-enabled UPS devices' } diff --git a/ts/cli.ts b/ts/cli.ts index 48d13fc..a51229b 100644 --- a/ts/cli.ts +++ b/ts/cli.ts @@ -30,7 +30,7 @@ export class NupstCli { // Enable debug mode in the SNMP client this.nupst.getSnmp().enableDebug(); } - + // Get the command (default to help if none provided) const command = args[2] || 'help'; @@ -46,8 +46,8 @@ export class NupstCli { private extractDebugOptions(args: string[]): { debugMode: boolean; cleanedArgs: string[] } { const debugMode = args.includes('--debug') || args.includes('-d'); // Remove debug flags from args - const cleanedArgs = args.filter(arg => arg !== '--debug' && arg !== '-d'); - + const cleanedArgs = args.filter((arg) => arg !== '--debug' && arg !== '-d'); + return { debugMode, cleanedArgs }; } @@ -85,23 +85,23 @@ export class NupstCli { case 'disable': await this.disable(); break; - + case 'setup': await this.setup(); break; - + case 'test': await this.test(debugMode); break; - + case 'update': await this.update(); break; - + case 'uninstall': await this.uninstall(); break; - + case 'config': await this.showConfig(); break; @@ -149,17 +149,17 @@ export class NupstCli { // Use exec with spawn to properly follow logs in real-time const { spawn } = await import('child_process'); console.log('Tailing nupst service logs (Ctrl+C to exit)...\n'); - + const journalctl = spawn('journalctl', ['-u', 'nupst.service', '-n', '50', '-f'], { - stdio: ['ignore', 'inherit', 'inherit'] + stdio: ['ignore', 'inherit', 'inherit'], }); - + // Forward signals to child process process.on('SIGINT', () => { journalctl.kill('SIGINT'); process.exit(0); }); - + // Wait for process to exit await new Promise((resolve) => { journalctl.on('exit', () => resolve()); @@ -229,21 +229,21 @@ export class NupstCli { console.log('│ SNMP debugging enabled - detailed logs will be shown'); console.log('└──────────────────────────────────────────┘'); } - + // Try to load the configuration try { await this.nupst.getDaemon().loadConfig(); } catch (error) { console.error('┌─ Configuration Error ─────────────────────┐'); console.error('│ No configuration found.'); - console.error('│ Please run \'nupst setup\' first to create a configuration.'); + console.error("│ Please run 'nupst setup' first to create a configuration."); console.error('└──────────────────────────────────────────┘'); return; } - + // Get current configuration const config = this.nupst.getDaemon().getConfig(); - + this.displayTestConfig(config); await this.testConnection(config); } catch (error) { @@ -262,26 +262,26 @@ export class NupstCli { console.log(`│ Port: ${config.snmp.port}`); console.log(`│ Version: ${config.snmp.version}`); console.log(`│ UPS Model: ${config.snmp.upsModel || 'cyberpower'}`); - + if (config.snmp.version === 1 || config.snmp.version === 2) { console.log(`│ Community: ${config.snmp.community}`); } else if (config.snmp.version === 3) { console.log(`│ Security Level: ${config.snmp.securityLevel}`); console.log(`│ Username: ${config.snmp.username}`); - + // Show auth and privacy details based on security level if (config.snmp.securityLevel === 'authNoPriv' || config.snmp.securityLevel === 'authPriv') { console.log(`│ Auth Protocol: ${config.snmp.authProtocol || 'None'}`); } - + if (config.snmp.securityLevel === 'authPriv') { console.log(`│ Privacy Protocol: ${config.snmp.privProtocol || 'None'}`); } - + // Show timeout value console.log(`│ Timeout: ${config.snmp.timeout / 1000} seconds`); } - + // Show OIDs if custom model is selected if (config.snmp.upsModel === 'custom' && config.snmp.customOIDs) { console.log('│ Custom OIDs:'); @@ -304,20 +304,20 @@ export class NupstCli { console.log('\nTesting connection to UPS...'); try { // Create a test config with a short timeout - const testConfig = { + const testConfig = { ...config.snmp, - timeout: Math.min(config.snmp.timeout, 10000) // Use at most 10 seconds for testing + timeout: Math.min(config.snmp.timeout, 10000), // Use at most 10 seconds for testing }; - + const status = await this.nupst.getSnmp().getUpsStatus(testConfig); - + console.log('┌─ Connection Successful! ─────────────────┐'); console.log('│ UPS Status:'); console.log(`│ Power Status: ${status.powerStatus}`); console.log(`│ Battery Capacity: ${status.batteryCapacity}%`); console.log(`│ Runtime Remaining: ${status.batteryRuntime} minutes`); console.log('└──────────────────────────────────────────┘'); - + // Check status against thresholds if on battery if (status.powerStatus === 'onBattery') { this.analyzeThresholds(status, config); @@ -326,7 +326,7 @@ export class NupstCli { console.error('┌─ Connection Failed! ───────────────────────┐'); console.error(`│ Error: ${error.message}`); console.error('└──────────────────────────────────────────┘'); - console.log('\nPlease check your settings and run \'nupst setup\' to reconfigure.'); + console.log("\nPlease check your settings and run 'nupst setup' to reconfigure."); } } @@ -337,25 +337,33 @@ export class NupstCli { */ private analyzeThresholds(status: any, config: any): void { console.log('┌─ Threshold Analysis ───────────────────────┐'); - + if (status.batteryCapacity < config.thresholds.battery) { console.log('│ ⚠️ WARNING: Battery capacity below threshold'); - console.log(`│ Current: ${status.batteryCapacity}% | Threshold: ${config.thresholds.battery}%`); + console.log( + `│ Current: ${status.batteryCapacity}% | Threshold: ${config.thresholds.battery}%` + ); console.log('│ System would initiate shutdown'); } else { console.log('│ ✓ Battery capacity above threshold'); - console.log(`│ Current: ${status.batteryCapacity}% | Threshold: ${config.thresholds.battery}%`); + console.log( + `│ Current: ${status.batteryCapacity}% | Threshold: ${config.thresholds.battery}%` + ); } - + if (status.batteryRuntime < config.thresholds.runtime) { console.log('│ ⚠️ WARNING: Runtime below threshold'); - console.log(`│ Current: ${status.batteryRuntime} min | Threshold: ${config.thresholds.runtime} min`); + console.log( + `│ Current: ${status.batteryRuntime} min | Threshold: ${config.thresholds.runtime} min` + ); console.log('│ System would initiate shutdown'); } else { console.log('│ ✓ Runtime above threshold'); - console.log(`│ Current: ${status.batteryRuntime} min | Threshold: ${config.thresholds.runtime} min`); + console.log( + `│ Current: ${status.batteryRuntime} min | Threshold: ${config.thresholds.runtime} min` + ); } - + console.log('└──────────────────────────────────────────┘'); } @@ -386,60 +394,72 @@ Options: (Example: nupst test --debug) `); } - + /** * Update NUPST from repository and refresh systemd service */ private async update(): Promise { try { // Check if running as root - this.checkRootAccess('This command must be run as root to update NUPST and refresh the systemd service.'); - + this.checkRootAccess( + 'This command must be run as root to update NUPST and refresh the systemd service.' + ); + console.log('┌─ NUPST Update Process ──────────────────┐'); console.log('│ Updating NUPST from repository...'); - + // Determine the installation directory (assuming it's either /opt/nupst or the current directory) const { existsSync } = await import('fs'); let installDir = '/opt/nupst'; - + if (!existsSync(installDir)) { // If not installed in /opt/nupst, use the current directory const { dirname } = await import('path'); installDir = dirname(dirname(process.argv[1])); // Go up two levels from the executable console.log(`│ Using local installation directory: ${installDir}`); } - + try { // 1. Update the repository console.log('│ Pulling latest changes from git repository...'); - execSync(`cd ${installDir} && git fetch origin && git reset --hard origin/main`, { stdio: 'pipe' }); - + execSync(`cd ${installDir} && git fetch origin && git reset --hard origin/main`, { + stdio: 'pipe', + }); + // 2. Run the install.sh script console.log('│ Running install.sh to update NUPST...'); execSync(`cd ${installDir} && bash ./install.sh`, { stdio: 'pipe' }); - + // 3. Run the setup.sh script with force flag to update Node.js and dependencies console.log('│ Running setup.sh to update Node.js and dependencies...'); execSync(`cd ${installDir} && bash ./setup.sh --force`, { stdio: 'pipe' }); - + // 4. Refresh the systemd service console.log('│ Refreshing systemd service...'); - + // First check if service exists - const serviceExists = execSync('systemctl list-unit-files | grep nupst.service').toString().includes('nupst.service'); - + let serviceExists = false; + try { + const output = execSync('systemctl list-unit-files | grep nupst.service').toString(); + serviceExists = output.includes('nupst.service'); + } catch (error) { + // If grep fails (service not found), serviceExists remains false + serviceExists = false; + } + if (serviceExists) { // Stop the service if it's running - const isRunning = execSync('systemctl is-active nupst.service || true').toString().trim() === 'active'; + const isRunning = + execSync('systemctl is-active nupst.service || true').toString().trim() === 'active'; if (isRunning) { console.log('│ Stopping nupst service...'); execSync('systemctl stop nupst.service'); } - + // Reinstall the service console.log('│ Reinstalling systemd service...'); await this.nupst.getSystemd().install(); - + // Restart the service if it was running if (isRunning) { console.log('│ Restarting nupst service...'); @@ -449,7 +469,7 @@ Options: console.log('│ Systemd service not installed, skipping service refresh.'); console.log('│ Run "nupst enable" to install the service.'); } - + console.log('│ Update completed successfully!'); console.log('└──────────────────────────────────────────┘'); } catch (error) { @@ -463,7 +483,7 @@ Options: process.exit(1); } } - + /** * Interactive setup for configuring SNMP settings */ @@ -471,12 +491,12 @@ Options: try { // Import readline module (ESM style) const readline = await import('readline'); - + const rl = readline.createInterface({ input: process.stdin, - output: process.stdout + output: process.stdout, }); - + // Helper function to prompt for input const prompt = (question: string): Promise => { return new Promise((resolve) => { @@ -485,7 +505,7 @@ Options: }); }); }; - + try { await this.runSetupProcess(prompt); } finally { @@ -504,7 +524,7 @@ Options: console.log('\nNUPST Interactive Setup'); console.log('======================\n'); console.log('This will guide you through configuring your UPS SNMP settings.\n'); - + // Try to load existing config if available let config; try { @@ -518,24 +538,24 @@ Options: // Gather SNMP settings config = await this.gatherSnmpSettings(config, prompt); - + // Gather threshold settings config = await this.gatherThresholdSettings(config, prompt); - + // Gather UPS model settings config = await this.gatherUpsModelSettings(config, prompt); - + // Save the configuration await this.nupst.getDaemon().saveConfig(config); - + this.displayConfigSummary(config); - + // Test the connection if requested await this.optionallyTestConnection(config, prompt); - + // Check if service is running and restart it if needed await this.restartServiceIfRunning(); - + console.log('\nSetup complete!'); await this.optionallyEnableService(prompt); } @@ -546,18 +566,21 @@ Options: * @param prompt Function to prompt for user input * @returns Updated configuration */ - private async gatherSnmpSettings(config: any, prompt: (question: string) => Promise): Promise { + private async gatherSnmpSettings( + config: any, + prompt: (question: string) => Promise + ): Promise { // SNMP IP Address const defaultHost = config.snmp.host; const host = await prompt(`UPS IP Address [${defaultHost}]: `); config.snmp.host = host.trim() || defaultHost; - + // SNMP Port const defaultPort = config.snmp.port; const portInput = await prompt(`SNMP Port [${defaultPort}]: `); const port = parseInt(portInput, 10); - config.snmp.port = (portInput.trim() && !isNaN(port)) ? port : defaultPort; - + config.snmp.port = portInput.trim() && !isNaN(port) ? port : defaultPort; + // SNMP Version const defaultVersion = config.snmp.version; console.log('\nSNMP Version:'); @@ -566,8 +589,11 @@ Options: console.log(' 3) SNMPv3 (with security features)'); const versionInput = await prompt(`Select SNMP version [${defaultVersion}]: `); const version = parseInt(versionInput, 10); - config.snmp.version = (versionInput.trim() && (version === 1 || version === 2 || version === 3)) ? version : defaultVersion; - + config.snmp.version = + versionInput.trim() && (version === 1 || version === 2 || version === 3) + ? version + : defaultVersion; + if (config.snmp.version === 1 || config.snmp.version === 2) { // SNMP Community String (for v1/v2c) const defaultCommunity = config.snmp.community || 'public'; @@ -577,7 +603,7 @@ Options: // SNMP v3 settings config = await this.gatherSnmpV3Settings(config, prompt); } - + return config; } @@ -587,20 +613,27 @@ Options: * @param prompt Function to prompt for user input * @returns Updated configuration */ - private async gatherSnmpV3Settings(config: any, prompt: (question: string) => Promise): Promise { + private async gatherSnmpV3Settings( + config: any, + prompt: (question: string) => Promise + ): Promise { console.log('\nSNMPv3 Security Settings:'); - + // Security Level console.log('\nSecurity Level:'); console.log(' 1) noAuthNoPriv (No Authentication, No Privacy)'); console.log(' 2) authNoPriv (Authentication, No Privacy)'); console.log(' 3) authPriv (Authentication and Privacy)'); - const defaultSecLevel = config.snmp.securityLevel ? - (config.snmp.securityLevel === 'noAuthNoPriv' ? 1 : - config.snmp.securityLevel === 'authNoPriv' ? 2 : 3) : 3; + const defaultSecLevel = config.snmp.securityLevel + ? config.snmp.securityLevel === 'noAuthNoPriv' + ? 1 + : config.snmp.securityLevel === 'authNoPriv' + ? 2 + : 3 + : 3; const secLevelInput = await prompt(`Select Security Level [${defaultSecLevel}]: `); const secLevel = parseInt(secLevelInput, 10) || defaultSecLevel; - + if (secLevel === 1) { config.snmp.securityLevel = 'noAuthNoPriv'; // No auth, no priv - clear out authentication and privacy settings @@ -622,31 +655,33 @@ Options: // Set appropriate timeout for security level config.snmp.timeout = 15000; // 15 seconds for full encryption } - + // Username const defaultUsername = config.snmp.username || ''; const username = await prompt(`SNMPv3 Username [${defaultUsername}]: `); config.snmp.username = username.trim() || defaultUsername; - + if (secLevel >= 2) { // Authentication settings config = await this.gatherAuthenticationSettings(config, prompt); - + if (secLevel === 3) { // Privacy settings config = await this.gatherPrivacySettings(config, prompt); } - + // Allow customizing the timeout value const defaultTimeout = config.snmp.timeout / 1000; // Convert from ms to seconds for display - console.log('\nSNMPv3 operations with authentication and privacy may require longer timeouts.'); + console.log( + '\nSNMPv3 operations with authentication and privacy may require longer timeouts.' + ); const timeoutInput = await prompt(`SNMP Timeout in seconds [${defaultTimeout}]: `); const timeout = parseInt(timeoutInput, 10); if (timeoutInput.trim() && !isNaN(timeout)) { config.snmp.timeout = timeout * 1000; // Convert to ms } } - + return config; } @@ -656,21 +691,26 @@ Options: * @param prompt Function to prompt for user input * @returns Updated configuration */ - private async gatherAuthenticationSettings(config: any, prompt: (question: string) => Promise): Promise { + private async gatherAuthenticationSettings( + config: any, + prompt: (question: string) => Promise + ): Promise { // Authentication protocol console.log('\nAuthentication Protocol:'); console.log(' 1) MD5'); console.log(' 2) SHA'); const defaultAuthProtocol = config.snmp.authProtocol === 'SHA' ? 2 : 1; - const authProtocolInput = await prompt(`Select Authentication Protocol [${defaultAuthProtocol}]: `); + const authProtocolInput = await prompt( + `Select Authentication Protocol [${defaultAuthProtocol}]: ` + ); const authProtocol = parseInt(authProtocolInput, 10) || defaultAuthProtocol; config.snmp.authProtocol = authProtocol === 2 ? 'SHA' : 'MD5'; - + // Authentication Key/Password const defaultAuthKey = config.snmp.authKey || ''; const authKey = await prompt(`Authentication Password ${defaultAuthKey ? '[*****]' : ''}: `); config.snmp.authKey = authKey.trim() || defaultAuthKey; - + return config; } @@ -680,7 +720,10 @@ Options: * @param prompt Function to prompt for user input * @returns Updated configuration */ - private async gatherPrivacySettings(config: any, prompt: (question: string) => Promise): Promise { + private async gatherPrivacySettings( + config: any, + prompt: (question: string) => Promise + ): Promise { // Privacy protocol console.log('\nPrivacy Protocol:'); console.log(' 1) DES'); @@ -689,12 +732,12 @@ Options: const privProtocolInput = await prompt(`Select Privacy Protocol [${defaultPrivProtocol}]: `); const privProtocol = parseInt(privProtocolInput, 10) || defaultPrivProtocol; config.snmp.privProtocol = privProtocol === 2 ? 'AES' : 'DES'; - + // Privacy Key/Password const defaultPrivKey = config.snmp.privKey || ''; const privKey = await prompt(`Privacy Password ${defaultPrivKey ? '[*****]' : ''}: `); config.snmp.privKey = privKey.trim() || defaultPrivKey; - + return config; } @@ -704,33 +747,43 @@ Options: * @param prompt Function to prompt for user input * @returns Updated configuration */ - private async gatherThresholdSettings(config: any, prompt: (question: string) => Promise): Promise { + private async gatherThresholdSettings( + config: any, + prompt: (question: string) => Promise + ): Promise { console.log('\nShutdown Thresholds:'); - + // Battery threshold const defaultBatteryThreshold = config.thresholds.battery; - const batteryThresholdInput = await prompt(`Battery percentage threshold [${defaultBatteryThreshold}%]: `); + const batteryThresholdInput = await prompt( + `Battery percentage threshold [${defaultBatteryThreshold}%]: ` + ); const batteryThreshold = parseInt(batteryThresholdInput, 10); - config.thresholds.battery = (batteryThresholdInput.trim() && !isNaN(batteryThreshold)) - ? batteryThreshold - : defaultBatteryThreshold; - + config.thresholds.battery = + batteryThresholdInput.trim() && !isNaN(batteryThreshold) + ? batteryThreshold + : defaultBatteryThreshold; + // Runtime threshold const defaultRuntimeThreshold = config.thresholds.runtime; - const runtimeThresholdInput = await prompt(`Runtime minutes threshold [${defaultRuntimeThreshold} minutes]: `); + const runtimeThresholdInput = await prompt( + `Runtime minutes threshold [${defaultRuntimeThreshold} minutes]: ` + ); const runtimeThreshold = parseInt(runtimeThresholdInput, 10); - config.thresholds.runtime = (runtimeThresholdInput.trim() && !isNaN(runtimeThreshold)) - ? runtimeThreshold - : defaultRuntimeThreshold; - + config.thresholds.runtime = + runtimeThresholdInput.trim() && !isNaN(runtimeThreshold) + ? runtimeThreshold + : defaultRuntimeThreshold; + // Check interval const defaultInterval = config.checkInterval / 1000; // Convert from ms to seconds for display const intervalInput = await prompt(`Check interval in seconds [${defaultInterval}]: `); const interval = parseInt(intervalInput, 10); - config.checkInterval = (intervalInput.trim() && !isNaN(interval)) - ? interval * 1000 // Convert to ms - : defaultInterval * 1000; - + config.checkInterval = + intervalInput.trim() && !isNaN(interval) + ? interval * 1000 // Convert to ms + : defaultInterval * 1000; + return config; } @@ -740,7 +793,10 @@ Options: * @param prompt Function to prompt for user input * @returns Updated configuration */ - private async gatherUpsModelSettings(config: any, prompt: (question: string) => Promise): Promise { + private async gatherUpsModelSettings( + config: any, + prompt: (question: string) => Promise + ): Promise { console.log('\nUPS Model Selection:'); console.log(' 1) CyberPower'); console.log(' 2) APC'); @@ -748,17 +804,25 @@ Options: console.log(' 4) TrippLite'); console.log(' 5) Liebert/Vertiv'); console.log(' 6) Custom (Advanced)'); - - const defaultModelValue = config.snmp.upsModel === 'cyberpower' ? 1 : - config.snmp.upsModel === 'apc' ? 2 : - config.snmp.upsModel === 'eaton' ? 3 : - config.snmp.upsModel === 'tripplite' ? 4 : - config.snmp.upsModel === 'liebert' ? 5 : - config.snmp.upsModel === 'custom' ? 6 : 1; - + + const defaultModelValue = + config.snmp.upsModel === 'cyberpower' + ? 1 + : config.snmp.upsModel === 'apc' + ? 2 + : config.snmp.upsModel === 'eaton' + ? 3 + : config.snmp.upsModel === 'tripplite' + ? 4 + : config.snmp.upsModel === 'liebert' + ? 5 + : config.snmp.upsModel === 'custom' + ? 6 + : 1; + const modelInput = await prompt(`Select UPS model [${defaultModelValue}]: `); const modelValue = parseInt(modelInput, 10) || defaultModelValue; - + if (modelValue === 1) { config.snmp.upsModel = 'cyberpower'; } else if (modelValue === 2) { @@ -773,20 +837,20 @@ Options: config.snmp.upsModel = 'custom'; console.log('\nEnter custom OIDs for your UPS:'); console.log('(Leave blank to use standard RFC 1628 OIDs as fallback)'); - + // Custom OIDs const powerStatusOID = await prompt('Power Status OID: '); const batteryCapacityOID = await prompt('Battery Capacity OID: '); const batteryRuntimeOID = await prompt('Battery Runtime OID: '); - + // Create custom OIDs object config.snmp.customOIDs = { POWER_STATUS: powerStatusOID.trim(), BATTERY_CAPACITY: batteryCapacityOID.trim(), - BATTERY_RUNTIME: batteryRuntimeOID.trim() + BATTERY_RUNTIME: batteryRuntimeOID.trim(), }; } - + return config; } @@ -799,8 +863,10 @@ Options: console.log(`│ SNMP Host: ${config.snmp.host}:${config.snmp.port}`); console.log(`│ SNMP Version: ${config.snmp.version}`); console.log(`│ UPS Model: ${config.snmp.upsModel}`); - console.log(`│ Thresholds: ${config.thresholds.battery}% battery, ${config.thresholds.runtime} min runtime`); - console.log(`│ Check Interval: ${config.checkInterval/1000} seconds`); + console.log( + `│ Thresholds: ${config.thresholds.battery}% battery, ${config.thresholds.runtime} min runtime` + ); + console.log(`│ Check Interval: ${config.checkInterval / 1000} seconds`); console.log('└──────────────────────────────────────────┘\n'); } @@ -809,17 +875,22 @@ Options: * @param config Current configuration * @param prompt Function to prompt for user input */ - private async optionallyTestConnection(config: any, prompt: (question: string) => Promise): Promise { - const testConnection = await prompt('Would you like to test the connection to your UPS? (y/N): '); + private async optionallyTestConnection( + config: any, + prompt: (question: string) => Promise + ): Promise { + const testConnection = await prompt( + 'Would you like to test the connection to your UPS? (y/N): ' + ); if (testConnection.toLowerCase() === 'y') { console.log('\nTesting connection to UPS...'); try { // Create a test config with a short timeout - const testConfig = { + const testConfig = { ...config.snmp, - timeout: Math.min(config.snmp.timeout, 10000) // Use at most 10 seconds for testing + timeout: Math.min(config.snmp.timeout, 10000), // Use at most 10 seconds for testing }; - + const status = await this.nupst.getSnmp().getUpsStatus(testConfig); console.log('\n┌─ Connection Successful! ─────────────────┐'); console.log('│ UPS Status:'); @@ -843,14 +914,15 @@ Options: private async restartServiceIfRunning(): Promise { try { // Check if the service is active - const isActive = execSync('systemctl is-active nupst.service || true').toString().trim() === 'active'; - + const isActive = + execSync('systemctl is-active nupst.service || true').toString().trim() === 'active'; + if (isActive) { // Service is running, restart it console.log('┌─ Service Update ─────────────────────────┐'); console.log('│ Configuration has changed.'); console.log('│ Restarting NUPST service to apply changes...'); - + try { if (process.getuid && process.getuid() === 0) { // We have root access, restart directly @@ -866,7 +938,7 @@ Options: console.log('│ You may need to restart the service manually:'); console.log('│ sudo systemctl restart nupst.service'); } - + console.log('└──────────────────────────────────────────┘'); } } catch (error) { @@ -878,18 +950,24 @@ Options: * Optionally enable and start systemd service * @param prompt Function to prompt for user input */ - private async optionallyEnableService(prompt: (question: string) => Promise): Promise { + private async optionallyEnableService( + prompt: (question: string) => Promise + ): Promise { if (process.getuid && process.getuid() !== 0) { console.log('\nNote: Run "sudo nupst enable" to set up NUPST as a system service.'); } else { - const setupService = await prompt('Would you like to enable NUPST as a system service? (y/N): '); + const setupService = await prompt( + 'Would you like to enable NUPST as a system service? (y/N): ' + ); if (setupService.toLowerCase() === 'y') { try { await this.nupst.getSystemd().install(); console.log('Service installed and enabled to start on boot.'); - + // Ask if the user wants to start the service now - const startService = await prompt('Would you like to start the NUPST service now? (Y/n): '); + const startService = await prompt( + 'Would you like to start the NUPST service now? (Y/n): ' + ); if (startService.toLowerCase() !== 'n') { await this.nupst.getSystemd().start(); console.log('NUPST service started successfully.'); @@ -902,7 +980,7 @@ Options: } } } - + /** * Display the current configuration */ @@ -914,68 +992,75 @@ Options: } catch (error) { console.error('┌─ Configuration Error ─────────────────────┐'); console.error('│ No configuration found.'); - console.error('│ Please run \'nupst setup\' first to create a configuration.'); + console.error("│ Please run 'nupst setup' first to create a configuration."); console.error('└──────────────────────────────────────────┘'); return; } - + // Get current configuration const config = this.nupst.getDaemon().getConfig(); - + console.log('┌─ NUPST Configuration ──────────────────────┐'); - + // SNMP Settings console.log('│ SNMP Settings:'); console.log(`│ Host: ${config.snmp.host}`); console.log(`│ Port: ${config.snmp.port}`); console.log(`│ Version: ${config.snmp.version}`); console.log(`│ UPS Model: ${config.snmp.upsModel || 'cyberpower'}`); - + if (config.snmp.version === 1 || config.snmp.version === 2) { console.log(`│ Community: ${config.snmp.community}`); } else if (config.snmp.version === 3) { console.log(`│ Security Level: ${config.snmp.securityLevel}`); console.log(`│ Username: ${config.snmp.username}`); - + // Show auth and privacy details based on security level - if (config.snmp.securityLevel === 'authNoPriv' || config.snmp.securityLevel === 'authPriv') { + if ( + config.snmp.securityLevel === 'authNoPriv' || + config.snmp.securityLevel === 'authPriv' + ) { console.log(`│ Auth Protocol: ${config.snmp.authProtocol || 'None'}`); } - + if (config.snmp.securityLevel === 'authPriv') { console.log(`│ Privacy Protocol: ${config.snmp.privProtocol || 'None'}`); } - + // Show timeout value console.log(`│ Timeout: ${config.snmp.timeout / 1000} seconds`); } - + // Show OIDs if custom model is selected if (config.snmp.upsModel === 'custom' && config.snmp.customOIDs) { console.log('│ Custom OIDs:'); console.log(`│ Power Status: ${config.snmp.customOIDs.POWER_STATUS || 'Not set'}`); - console.log(`│ Battery Capacity: ${config.snmp.customOIDs.BATTERY_CAPACITY || 'Not set'}`); + console.log( + `│ Battery Capacity: ${config.snmp.customOIDs.BATTERY_CAPACITY || 'Not set'}` + ); console.log(`│ Battery Runtime: ${config.snmp.customOIDs.BATTERY_RUNTIME || 'Not set'}`); } - + // Thresholds console.log('│ Thresholds:'); console.log(`│ Battery: ${config.thresholds.battery}%`); console.log(`│ Runtime: ${config.thresholds.runtime} minutes`); console.log(`│ Check Interval: ${config.checkInterval / 1000} seconds`); - + // Configuration file location console.log('│'); console.log('│ Configuration File Location:'); console.log('│ /etc/nupst/config.json'); - + console.log('└──────────────────────────────────────────┘'); - + // Show service status try { - const isActive = execSync('systemctl is-active nupst.service || true').toString().trim() === 'active'; - const isEnabled = execSync('systemctl is-enabled nupst.service || true').toString().trim() === 'enabled'; - + const isActive = + execSync('systemctl is-active nupst.service || true').toString().trim() === 'active'; + const isEnabled = + execSync('systemctl is-enabled nupst.service || true').toString().trim() === 'enabled'; + console.log('┌─ Service Status ─────────────────────────┐'); console.log(`│ Service Active: ${isActive ? 'Yes' : 'No'}`); console.log(`│ Service Enabled: ${isEnabled ? 'Yes' : 'No'}`); @@ -983,7 +1068,6 @@ Options: } catch (error) { // Ignore errors checking service status } - } catch (error) { console.error(`Failed to display configuration: ${error.message}`); } @@ -999,12 +1083,12 @@ Options: try { // Import readline module for user input const readline = await import('readline'); - + const rl = readline.createInterface({ input: process.stdin, - output: process.stdout + output: process.stdout, }); - + // Helper function to prompt for input const prompt = (question: string): Promise => { return new Promise((resolve) => { @@ -1019,11 +1103,13 @@ Options: console.log('This will completely remove NUPST from your system.\n'); // Ask about removing configuration - const removeConfig = await prompt('Do you want to remove the NUPST configuration files? (y/N): '); - + const removeConfig = await prompt( + 'Do you want to remove the NUPST configuration files? (y/N): ' + ); + // Find the uninstall.sh script location let uninstallScriptPath: string; - + // Try to determine script location based on executable path try { // For ESM, we can use import.meta.url, but since we might be in CJS @@ -1031,16 +1117,13 @@ Options: const binPath = process.argv[1]; const modulePath = dirname(dirname(binPath)); uninstallScriptPath = join(modulePath, 'uninstall.sh'); - + // Check if the script exists await fs.access(uninstallScriptPath); } catch (error) { // If we can't find it in the expected location, try common installation paths - const commonPaths = [ - '/opt/nupst/uninstall.sh', - join(process.cwd(), 'uninstall.sh') - ]; - + const commonPaths = ['/opt/nupst/uninstall.sh', join(process.cwd(), 'uninstall.sh')]; + for (const path of commonPaths) { try { await fs.access(path); @@ -1050,37 +1133,36 @@ Options: // Continue to next path } } - + if (!uninstallScriptPath) { console.error('Could not locate uninstall.sh script. Aborting uninstall.'); rl.close(); process.exit(1); } } - + // Close readline before executing script rl.close(); - + // Execute uninstall.sh with the appropriate option console.log(`\nRunning uninstaller from ${uninstallScriptPath}...`); - + // Pass the configuration removal option as an environment variable const env = { ...process.env, REMOVE_CONFIG: removeConfig.toLowerCase() === 'y' ? 'yes' : 'no', - REMOVE_REPO: 'yes', // Always remove repo as requested - NUPST_CLI_CALL: 'true' // Flag to indicate this is being called from CLI + REMOVE_REPO: 'yes', // Always remove repo as requested + NUPST_CLI_CALL: 'true', // Flag to indicate this is being called from CLI }; - + // Run the uninstall script with sudo - execSync(`sudo bash ${uninstallScriptPath}`, { + execSync(`sudo bash ${uninstallScriptPath}`, { env, - stdio: 'inherit' // Show output in the terminal + stdio: 'inherit', // Show output in the terminal }); - } catch (error) { console.error(`Uninstall failed: ${error.message}`); process.exit(1); } } -} \ No newline at end of file +} diff --git a/ts/snmp/manager.ts b/ts/snmp/manager.ts index 17d153b..22a607c 100644 --- a/ts/snmp/manager.ts +++ b/ts/snmp/manager.ts @@ -1,4 +1,3 @@ -import * as dgram from 'dgram'; import * as snmp from 'net-snmp'; import type { IOidSet, ISnmpConfig, TUpsModel, IUpsStatus } from './types.js'; import { UpsOidSets } from './oid-sets.js';