import { execSync } from 'node:child_process'; import { Nupst } from './nupst.ts'; import { logger } from './logger.ts'; /** * Class for handling CLI commands * Provides interface between user commands and the application */ export class NupstCli { private readonly nupst: Nupst; /** * Create a new CLI handler */ constructor() { this.nupst = new Nupst(); } /** * Parse command line arguments and execute the appropriate command * @param args Command line arguments (process.argv) */ public async parseAndExecute(args: string[]): Promise { // Extract debug and version flags from any position const debugOptions = this.extractDebugOptions(args); if (debugOptions.debugMode) { logger.log('Debug mode enabled'); // Enable debug mode in the SNMP client this.nupst.getSnmp().enableDebug(); } // Check for version flag if (debugOptions.cleanedArgs.includes('--version') || debugOptions.cleanedArgs.includes('-v')) { this.showVersion(); return; } // Get the command (default to help if none provided) const command = debugOptions.cleanedArgs[2] || 'help'; const commandArgs = debugOptions.cleanedArgs.slice(3); // Route to the appropriate command handler await this.executeCommand(command, commandArgs, debugOptions.debugMode); } /** * Extract and remove debug options from args * @param args Command line arguments * @returns Object with debug flags and cleaned args */ 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'); return { debugMode, cleanedArgs }; } /** * Execute the command with the given arguments * @param command Command to execute * @param commandArgs Additional command arguments * @param debugMode Whether debug mode is enabled */ private async executeCommand( command: string, commandArgs: string[], debugMode: boolean, ): Promise { // Get access to the handlers const upsHandler = this.nupst.getUpsHandler(); const groupHandler = this.nupst.getGroupHandler(); const serviceHandler = this.nupst.getServiceHandler(); // Handle service subcommands if (command === 'service') { const subcommand = commandArgs[0] || 'status'; switch (subcommand) { case 'enable': await serviceHandler.enable(); break; case 'disable': await serviceHandler.disable(); break; case 'start': await serviceHandler.start(); break; case 'stop': await serviceHandler.stop(); break; case 'restart': await serviceHandler.stop(); await new Promise((resolve) => setTimeout(resolve, 2000)); // Wait 2s await serviceHandler.start(); break; case 'status': await serviceHandler.status(); break; case 'logs': await serviceHandler.logs(); break; case 'start-daemon': await serviceHandler.daemonStart(debugMode); break; default: this.showServiceHelp(); break; } return; } // Handle UPS subcommands if (command === 'ups') { const subcommand = commandArgs[0] || 'list'; const subcommandArgs = commandArgs.slice(1); switch (subcommand) { case 'add': await upsHandler.add(); break; case 'edit': { const upsId = subcommandArgs[0]; await upsHandler.edit(upsId); break; } case 'remove': case 'rm': // Alias case 'delete': { // Backward compatibility const upsIdToRemove = subcommandArgs[0]; if (!upsIdToRemove) { logger.error('UPS ID is required for remove command'); this.showUpsHelp(); return; } await upsHandler.remove(upsIdToRemove); break; } case 'list': case 'ls': // Alias await upsHandler.list(); break; case 'test': await upsHandler.test(debugMode); break; default: this.showUpsHelp(); break; } return; } // Handle group subcommands if (command === 'group') { const subcommand = commandArgs[0] || 'list'; const subcommandArgs = commandArgs.slice(1); switch (subcommand) { case 'add': await groupHandler.add(); break; case 'edit': { const groupId = subcommandArgs[0]; if (!groupId) { logger.error('Group ID is required for edit command'); this.showGroupHelp(); return; } await groupHandler.edit(groupId); break; } case 'remove': case 'rm': // Alias case 'delete': { // Backward compatibility const groupIdToRemove = subcommandArgs[0]; if (!groupIdToRemove) { logger.error('Group ID is required for remove command'); this.showGroupHelp(); return; } await groupHandler.remove(groupIdToRemove); break; } case 'list': case 'ls': // Alias await groupHandler.list(); break; default: this.showGroupHelp(); break; } return; } // Handle config subcommand if (command === 'config') { const subcommand = commandArgs[0] || 'show'; switch (subcommand) { case 'show': case 'display': await this.showConfig(); break; default: await this.showConfig(); break; } return; } // Handle top-level commands and backward compatibility switch (command) { // Backward compatibility - old UPS commands case 'add': logger.log("Note: 'nupst add' is deprecated. Use 'nupst ups add' instead."); await upsHandler.add(); break; case 'edit': logger.log("Note: 'nupst edit' is deprecated. Use 'nupst ups edit' instead."); await upsHandler.edit(commandArgs[0]); break; case 'delete': logger.log("Note: 'nupst delete' is deprecated. Use 'nupst ups remove' instead."); if (!commandArgs[0]) { logger.error('UPS ID is required for delete command'); this.showHelp(); return; } await upsHandler.remove(commandArgs[0]); break; case 'list': logger.log("Note: 'nupst list' is deprecated. Use 'nupst ups list' instead."); await upsHandler.list(); break; case 'test': logger.log("Note: 'nupst test' is deprecated. Use 'nupst ups test' instead."); await upsHandler.test(debugMode); break; case 'setup': logger.log("Note: 'nupst setup' is deprecated. Use 'nupst ups edit' instead."); await upsHandler.edit(undefined); break; // Backward compatibility - old service commands case 'enable': logger.log("Note: 'nupst enable' is deprecated. Use 'nupst service enable' instead."); await serviceHandler.enable(); break; case 'disable': logger.log("Note: 'nupst disable' is deprecated. Use 'nupst service disable' instead."); await serviceHandler.disable(); break; case 'start': logger.log("Note: 'nupst start' is deprecated. Use 'nupst service start' instead."); await serviceHandler.start(); break; case 'stop': logger.log("Note: 'nupst stop' is deprecated. Use 'nupst service stop' instead."); await serviceHandler.stop(); break; case 'status': logger.log("Note: 'nupst status' is deprecated. Use 'nupst service status' instead."); await serviceHandler.status(); break; case 'logs': logger.log("Note: 'nupst logs' is deprecated. Use 'nupst service logs' instead."); await serviceHandler.logs(); break; case 'daemon-start': logger.log( "Note: 'nupst daemon-start' is deprecated. Use 'nupst service start-daemon' instead.", ); await serviceHandler.daemonStart(debugMode); break; // Top-level commands (no changes) case 'update': await serviceHandler.update(); break; case 'uninstall': await serviceHandler.uninstall(); break; case 'help': case '--help': case '-h': this.showHelp(); break; default: logger.error(`Unknown command: ${command}`); logger.log(''); this.showHelp(); break; } } /** * Display the current configuration */ private async showConfig(): Promise { try { // Try to load configuration try { await this.nupst.getDaemon().loadConfig(); } catch (_error) { const errorBoxWidth = 45; logger.logBoxTitle('Configuration Error', errorBoxWidth); logger.logBoxLine('No configuration found.'); logger.logBoxLine("Please run 'nupst setup' first to create a configuration."); logger.logBoxEnd(); return; } // Get current configuration const config = this.nupst.getDaemon().getConfig(); const boxWidth = 50; logger.logBoxTitle('NUPST Configuration', boxWidth); // Check if multi-UPS config if (config.upsDevices && Array.isArray(config.upsDevices)) { // Multi-UPS configuration logger.logBoxLine(`UPS Devices: ${config.upsDevices.length}`); logger.logBoxLine(`Groups: ${config.groups ? config.groups.length : 0}`); logger.logBoxLine(`Check Interval: ${config.checkInterval / 1000} seconds`); logger.logBoxLine(''); logger.logBoxLine('Configuration File Location:'); logger.logBoxLine(' /etc/nupst/config.json'); logger.logBoxEnd(); // Show UPS devices if (config.upsDevices.length > 0) { logger.logBoxTitle('UPS Devices', boxWidth); for (const ups of config.upsDevices) { logger.logBoxLine(`${ups.name} (${ups.id}):`); logger.logBoxLine(` Host: ${ups.snmp.host}:${ups.snmp.port}`); logger.logBoxLine(` Model: ${ups.snmp.upsModel}`); logger.logBoxLine( ` Thresholds: ${ups.thresholds.battery}% battery, ${ups.thresholds.runtime} min runtime`, ); logger.logBoxLine( ` Groups: ${ups.groups.length > 0 ? ups.groups.join(', ') : 'None'}`, ); logger.logBoxLine(''); } logger.logBoxEnd(); } // Show groups if (config.groups && config.groups.length > 0) { logger.logBoxTitle('UPS Groups', boxWidth); for (const group of config.groups) { logger.logBoxLine(`${group.name} (${group.id}):`); logger.logBoxLine(` Mode: ${group.mode}`); if (group.description) { logger.logBoxLine(` Description: ${group.description}`); } // List UPS devices in this group const upsInGroup = config.upsDevices.filter((ups) => ups.groups && ups.groups.includes(group.id) ); logger.logBoxLine( ` UPS Devices: ${ upsInGroup.length > 0 ? upsInGroup.map((ups) => ups.name).join(', ') : 'None' }`, ); logger.logBoxLine(''); } logger.logBoxEnd(); } } else { // Legacy single UPS configuration if (!config.snmp) { logger.logBoxLine('Error: Legacy configuration missing SNMP settings'); } else { // SNMP Settings logger.logBoxLine('SNMP Settings:'); logger.logBoxLine(` Host: ${config.snmp.host}`); logger.logBoxLine(` Port: ${config.snmp.port}`); logger.logBoxLine(` Version: ${config.snmp.version}`); logger.logBoxLine(` UPS Model: ${config.snmp.upsModel || 'cyberpower'}`); if (config.snmp.version === 1 || config.snmp.version === 2) { logger.logBoxLine(` Community: ${config.snmp.community}`); } else if (config.snmp.version === 3) { logger.logBoxLine(` Security Level: ${config.snmp.securityLevel}`); logger.logBoxLine(` Username: ${config.snmp.username}`); // Show auth and privacy details based on security level if ( config.snmp.securityLevel === 'authNoPriv' || config.snmp.securityLevel === 'authPriv' ) { logger.logBoxLine(` Auth Protocol: ${config.snmp.authProtocol || 'None'}`); } if (config.snmp.securityLevel === 'authPriv') { logger.logBoxLine(` Privacy Protocol: ${config.snmp.privProtocol || 'None'}`); } // Show timeout value logger.logBoxLine(` Timeout: ${config.snmp.timeout / 1000} seconds`); } // Show OIDs if custom model is selected if (config.snmp.upsModel === 'custom' && config.snmp.customOIDs) { logger.logBoxLine('Custom OIDs:'); logger.logBoxLine( ` Power Status: ${config.snmp.customOIDs.POWER_STATUS || 'Not set'}`, ); logger.logBoxLine( ` Battery Capacity: ${config.snmp.customOIDs.BATTERY_CAPACITY || 'Not set'}`, ); logger.logBoxLine( ` Battery Runtime: ${config.snmp.customOIDs.BATTERY_RUNTIME || 'Not set'}`, ); } } // Thresholds if (!config.thresholds) { logger.logBoxLine('Error: Legacy configuration missing threshold settings'); } else { logger.logBoxLine('Thresholds:'); logger.logBoxLine(` Battery: ${config.thresholds.battery}%`); logger.logBoxLine(` Runtime: ${config.thresholds.runtime} minutes`); } logger.logBoxLine(`Check Interval: ${config.checkInterval / 1000} seconds`); // Configuration file location logger.logBoxLine(''); logger.logBoxLine('Configuration File Location:'); logger.logBoxLine(' /etc/nupst/config.json'); logger.logBoxLine(''); logger.logBoxLine('Note: Using legacy single-UPS configuration format.'); logger.logBoxLine('Consider using "nupst add" to migrate to multi-UPS format.'); logger.logBoxEnd(); } // 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 statusBoxWidth = 45; logger.logBoxTitle('Service Status', statusBoxWidth); logger.logBoxLine(`Service Active: ${isActive ? 'Yes' : 'No'}`); logger.logBoxLine(`Service Enabled: ${isEnabled ? 'Yes' : 'No'}`); logger.logBoxEnd(); } catch (_error) { // Ignore errors checking service status } } catch (error) { logger.error( `Failed to display configuration: ${ error instanceof Error ? error.message : String(error) }`, ); } } /** * Display version information */ private showVersion(): void { const version = this.nupst.getVersion(); logger.log(`NUPST version ${version}`); logger.log('Deno-powered UPS monitoring tool'); } /** * Display help message */ private showHelp(): void { logger.log(` NUPST - UPS Shutdown Tool Usage: nupst [options] Commands: service - Manage systemd service ups - Manage UPS devices group - Manage UPS groups config [show] - Display current configuration update - Update NUPST from repository (requires root) uninstall - Completely remove NUPST from system (requires root) help, --help, -h - Show this help message --version, -v - Show version information Service Subcommands: nupst service enable - Install and enable systemd service (requires root) nupst service disable - Stop and disable systemd service (requires root) nupst service start - Start the systemd service nupst service stop - Stop the systemd service nupst service restart - Restart the systemd service nupst service status - Show service and UPS status nupst service logs - Show service logs in real-time nupst service start-daemon - Start daemon process directly UPS Subcommands: nupst ups add - Add a new UPS device nupst ups edit [id] - Edit a UPS device (default if no ID) nupst ups remove - Remove a UPS device by ID nupst ups list (or ls) - List all configured UPS devices nupst ups test - Test UPS connections Group Subcommands: nupst group add - Add a new UPS group nupst group edit - Edit an existing UPS group nupst group remove - Remove a UPS group by ID nupst group list (or ls) - List all UPS groups Options: --debug, -d - Enable debug mode for detailed SNMP logging (Example: nupst ups test --debug) Examples: nupst service enable - Install and start the service nupst ups add - Add a new UPS interactively nupst group list - Show all configured groups nupst config - Display current configuration Note: Old command format (e.g., 'nupst add') still works but is deprecated. Use the new format (e.g., 'nupst ups add') going forward. `); } /** * Display help message for service commands */ private showServiceHelp(): void { logger.log(` NUPST - Service Management Commands Usage: nupst service Subcommands: enable - Install and enable the systemd service (requires root) disable - Stop and disable the systemd service (requires root) start - Start the systemd service stop - Stop the systemd service restart - Restart the systemd service status - Show service status and UPS information logs - Show service logs in real-time start-daemon - Start the daemon process directly (for testing) Options: --debug, -d - Enable debug mode for detailed logging `); } /** * Display help message for UPS commands */ private showUpsHelp(): void { logger.log(` NUPST - UPS Management Commands Usage: nupst ups [arguments] Subcommands: add - Add a new UPS device interactively edit [id] - Edit a UPS device (edits default if no ID provided) remove - Remove a UPS device by ID (alias: rm) list - List all configured UPS devices (alias: ls) test - Test connections to all configured UPS devices Options: --debug, -d - Enable debug mode for detailed SNMP logging Examples: nupst ups add - Add a new UPS device nupst ups edit ups-1 - Edit UPS with ID 'ups-1' nupst ups remove ups-1 - Remove UPS with ID 'ups-1' nupst ups test --debug - Test all UPS connections with debug output `); } /** * Display help message for group commands */ private showGroupHelp(): void { logger.log(` NUPST - Group Management Commands Usage: nupst group [arguments] Subcommands: add - Add a new UPS group interactively edit - Edit an existing UPS group remove - Remove a UPS group by ID (alias: rm) list - List all UPS groups (alias: ls) Options: --debug, -d - Enable debug mode for detailed logging Examples: nupst group add - Create a new group nupst group edit dc-1 - Edit group with ID 'dc-1' nupst group remove dc-1 - Remove group with ID 'dc-1' `); } }