390 lines
13 KiB
TypeScript
390 lines
13 KiB
TypeScript
import { execSync } from 'child_process';
|
|
import { Nupst } from './nupst.js';
|
|
import { logger } from './logger.js';
|
|
|
|
/**
|
|
* 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<void> {
|
|
// Extract debug flag 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();
|
|
}
|
|
|
|
// 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<void> {
|
|
// Get access to the handlers
|
|
const upsHandler = this.nupst.getUpsHandler();
|
|
const groupHandler = this.nupst.getGroupHandler();
|
|
const serviceHandler = this.nupst.getServiceHandler();
|
|
|
|
// 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 'delete':
|
|
const groupIdToDelete = subcommandArgs[0];
|
|
if (!groupIdToDelete) {
|
|
logger.error('Group ID is required for delete command');
|
|
this.showGroupHelp();
|
|
return;
|
|
}
|
|
await groupHandler.delete(groupIdToDelete);
|
|
break;
|
|
|
|
case 'list':
|
|
await groupHandler.list();
|
|
break;
|
|
|
|
default:
|
|
this.showGroupHelp();
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Handle main commands
|
|
switch (command) {
|
|
case 'add':
|
|
await upsHandler.add();
|
|
break;
|
|
|
|
case 'edit':
|
|
const upsId = commandArgs[0];
|
|
await upsHandler.edit(upsId);
|
|
break;
|
|
|
|
case 'delete':
|
|
const upsIdToDelete = commandArgs[0];
|
|
if (!upsIdToDelete) {
|
|
logger.error('UPS ID is required for delete command');
|
|
this.showHelp();
|
|
return;
|
|
}
|
|
await upsHandler.delete(upsIdToDelete);
|
|
break;
|
|
|
|
case 'list':
|
|
await upsHandler.list();
|
|
break;
|
|
|
|
case 'setup':
|
|
// Backward compatibility: setup is now an alias for edit with no specific UPS ID
|
|
await upsHandler.edit(undefined);
|
|
break;
|
|
|
|
case 'enable':
|
|
await serviceHandler.enable();
|
|
break;
|
|
|
|
case 'daemon-start':
|
|
await serviceHandler.daemonStart(debugMode);
|
|
break;
|
|
|
|
case 'logs':
|
|
await serviceHandler.logs();
|
|
break;
|
|
|
|
case 'stop':
|
|
await serviceHandler.stop();
|
|
break;
|
|
|
|
case 'start':
|
|
await serviceHandler.start();
|
|
break;
|
|
|
|
case 'status':
|
|
await serviceHandler.status();
|
|
break;
|
|
|
|
case 'disable':
|
|
await serviceHandler.disable();
|
|
break;
|
|
|
|
case 'test':
|
|
await upsHandler.test(debugMode);
|
|
break;
|
|
|
|
case 'update':
|
|
await serviceHandler.update();
|
|
break;
|
|
|
|
case 'uninstall':
|
|
await serviceHandler.uninstall();
|
|
break;
|
|
|
|
case 'config':
|
|
await this.showConfig();
|
|
break;
|
|
|
|
case 'help':
|
|
default:
|
|
this.showHelp();
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Display the current configuration
|
|
*/
|
|
private async showConfig(): Promise<void> {
|
|
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
|
|
// 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
|
|
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.message}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Display help message
|
|
*/
|
|
private showHelp(): void {
|
|
logger.log(`
|
|
NUPST - Node.js UPS Shutdown Tool
|
|
|
|
Usage:
|
|
nupst enable - Install and enable the systemd service (requires root)
|
|
nupst disable - Stop and uninstall the systemd service (requires root)
|
|
nupst daemon-start - Start the daemon process directly
|
|
nupst logs - Show logs of the systemd service
|
|
nupst stop - Stop the systemd service
|
|
nupst start - Start the systemd service
|
|
nupst status - Show status of the systemd service and UPS status
|
|
|
|
UPS Management:
|
|
nupst add - Add a new UPS device
|
|
nupst edit [id] - Edit an existing UPS (default UPS if no ID provided)
|
|
nupst delete <id> - Delete a UPS by ID
|
|
nupst list - List all configured UPS devices
|
|
nupst setup - Alias for 'nupst edit' (backward compatibility)
|
|
|
|
Group Management:
|
|
nupst group list - List all UPS groups
|
|
nupst group add - Add a new UPS group
|
|
nupst group edit <id> - Edit an existing UPS group
|
|
nupst group delete <id> - Delete a UPS group
|
|
|
|
System Commands:
|
|
nupst test - Test the current configuration by connecting to all UPS devices
|
|
nupst config - Display the current configuration
|
|
nupst update - Update NUPST from repository and refresh systemd service (requires root)
|
|
nupst uninstall - Completely uninstall NUPST from the system (requires root)
|
|
nupst help - Show this help message
|
|
|
|
Options:
|
|
--debug, -d - Enable debug mode for detailed SNMP logging
|
|
(Example: nupst test --debug)
|
|
`);
|
|
}
|
|
|
|
/**
|
|
* Display help message for group commands
|
|
*/
|
|
private showGroupHelp(): void {
|
|
logger.log(`
|
|
NUPST - Group Management Commands
|
|
|
|
Usage:
|
|
nupst group list - List all UPS groups
|
|
nupst group add - Add a new UPS group
|
|
nupst group edit <id> - Edit an existing UPS group
|
|
nupst group delete <id> - Delete a UPS group
|
|
|
|
Options:
|
|
--debug, -d - Enable debug mode for detailed logging
|
|
`);
|
|
}
|
|
} |