Compare commits

...

13 Commits

10 changed files with 632 additions and 257 deletions

View File

@ -1,5 +1,42 @@
# Changelog
## 2025-03-26 - 2.6.16 - fix(cli)
Improve CLI logging consistency by replacing direct console output with unified logger calls.
- Replaced console.log and console.error with logger.log and logger.error in CLI commands
- Standardized debug, error, and status messages using logger's logbox utilities
- Enhanced consistency of log output throughout the ts/cli.ts file
## 2025-03-26 - 2.6.15 - fix(logger)
Replace direct console logging with unified logger interface for consistent formatting
- Substitute console.log, console.error, and related calls with logger methods in cli, daemon, systemd, nupst, and index modules
- Integrate logBox formatting for structured output and consistent log presentation
- Update test expectations in test.logger.ts to check for standardized error messages
- Refactor logging calls throughout the codebase for improved clarity and maintainability
## 2025-03-26 - 2.6.14 - fix(systemd)
Shorten closing log divider in systemd service installation output for consistent formatting.
- Replaced the overly long footer with a shorter one in ts/systemd.ts.
- This change improves log readability without affecting functionality.
## 2025-03-26 - 2.6.13 - fix(cli)
Fix CLI update output box formatting
- Adjusted the closing box line in the update process log messages for consistent visual formatting
## 2025-03-26 - 2.6.12 - fix(systemd)
Adjust logging border in systemd service installation output
- Updated the closing border line for consistent output formatting in ts/systemd.ts
## 2025-03-26 - 2.6.11 - fix(cli, systemd)
Adjust log formatting for consistent output in CLI and systemd commands
- Fixed spacing issues in service installation and status log messages in the systemd module.
- Revised output formatting in the CLI to improve message clarity.
## 2025-03-26 - 2.6.10 - fix(daemon)
Adjust console log box formatting for consistent output in daemon status messages

View File

@ -1,6 +1,6 @@
{
"name": "@serve.zone/nupst",
"version": "2.6.10",
"version": "2.6.16",
"description": "Node.js UPS Shutdown Tool for SNMP-enabled UPS devices",
"main": "dist/index.js",
"bin": {
@ -56,5 +56,6 @@
"mongodb-memory-server",
"puppeteer"
]
}
},
"packageManager": "pnpm@10.7.0+sha512.6b865ad4b62a1d9842b61d674a393903b871d9244954f652b8842c2b553c72176b278f64c463e52d40fff8aba385c235c8c9ecf5cc7de4fd78b8bb6d49633ab6"
}

147
test/test.logger.ts Normal file
View File

@ -0,0 +1,147 @@
import { tap, expect } from '@push.rocks/tapbundle';
import { Logger } from '../ts/logger.js';
// Create a Logger instance for testing
const logger = new Logger();
tap.test('should create a logger instance', async () => {
expect(logger instanceof Logger).toBeTruthy();
});
tap.test('should log messages with different log levels', async () => {
// We're not testing console output directly, just ensuring no errors
logger.log('Regular log message');
logger.error('Error message');
logger.warn('Warning message');
logger.success('Success message');
// Just assert that the test runs without errors
expect(true).toBeTruthy();
});
tap.test('should create a logbox with title, content, and end', async () => {
// Just ensuring no errors occur
logger.logBoxTitle('Test Box', 40);
logger.logBoxLine('This is a test line');
logger.logBoxEnd();
// Just assert that the test runs without errors
expect(true).toBeTruthy();
});
tap.test('should handle width persistence between logbox calls', async () => {
logger.logBoxTitle('Width Test', 45);
// These should use the width from the title
logger.logBoxLine('Line 1');
logger.logBoxLine('Line 2');
logger.logBoxEnd();
let errorThrown = false;
try {
// This should work fine after the reset in logBoxEnd
logger.logBoxTitle('New Box', 30);
logger.logBoxLine('New line');
logger.logBoxEnd();
} catch (error) {
errorThrown = true;
}
expect(errorThrown).toBeFalsy();
});
tap.test('should throw error when using logBoxLine without width', async () => {
let errorThrown = false;
let errorMessage = '';
try {
// Should throw because no width is set
logger.logBoxLine('This should fail');
} catch (error) {
errorThrown = true;
errorMessage = (error as Error).message;
}
expect(errorThrown).toBeTruthy();
expect(errorMessage).toBeTruthy();
expect(errorMessage.includes('No box width')).toBeTruthy();
});
tap.test('should create a complete logbox in one call', async () => {
// Just ensuring no errors occur
logger.logBox('Complete Box', [
'Line 1',
'Line 2',
'Line 3'
], 40);
// Just assert that the test runs without errors
expect(true).toBeTruthy();
});
tap.test('should handle content that exceeds box width', async () => {
// Just ensuring no errors occur when content is too long
logger.logBox('Truncation Test', [
'This line is way too long and should be truncated because it exceeds the available space'
], 30);
// Just assert that the test runs without errors
expect(true).toBeTruthy();
});
tap.test('should create dividers with custom characters', async () => {
// Just ensuring no errors occur
logger.logDivider(30);
logger.logDivider(20, '*');
// Just assert that the test runs without errors
expect(true).toBeTruthy();
});
tap.test('Logger Demo', async () => {
console.log('\n=== LOGGER DEMO ===\n');
// Basic logging
logger.log('Regular log message');
logger.error('Error message');
logger.warn('Warning message');
logger.success('Success message');
// Logbox with title, content lines, and end
logger.logBoxTitle('Configuration Loaded', 50);
logger.logBoxLine('SNMP Settings:');
logger.logBoxLine(' Host: 127.0.0.1');
logger.logBoxLine(' Port: 161');
logger.logBoxLine(' Version: 1');
logger.logBoxEnd();
// Complete logbox in one call
logger.logBox('UPS Status', [
'Power Status: onBattery',
'Battery Capacity: 75%',
'Runtime Remaining: 30 minutes'
], 45);
// Logbox with content that's too long for the width
logger.logBox('Truncation Example', [
'This line is short enough to fit within the box width',
'This line is way too long and will be truncated because it exceeds the available space for content within the logbox'
], 40);
// Demonstrating logbox width being remembered
logger.logBoxTitle('Width Persistence Example', 60);
logger.logBoxLine('These lines use the width from the title');
logger.logBoxLine('No need to specify the width again');
logger.logBoxEnd();
// Divider example
logger.log('\nDivider example:');
logger.logDivider(30);
logger.logDivider(30, '*');
expect(true).toBeTruthy();
});
// Export the default tap object
export default tap.start();

View File

@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@serve.zone/nupst',
version: '2.6.10',
version: '2.6.16',
description: 'Node.js UPS Shutdown Tool for SNMP-enabled UPS devices'
}

321
ts/cli.ts
View File

@ -3,6 +3,7 @@ import { promises as fs } from 'fs';
import { dirname, join } from 'path';
import { fileURLToPath } from 'url';
import { Nupst } from './nupst.js';
import { logger } from './logger.js';
/**
* Class for handling CLI commands
@ -26,7 +27,7 @@ export class NupstCli {
// Extract debug flag from any position
const debugOptions = this.extractDebugOptions(args);
if (debugOptions.debugMode) {
console.log('Debug mode enabled');
logger.log('Debug mode enabled');
// Enable debug mode in the SNMP client
this.nupst.getSnmp().enableDebug();
}
@ -119,7 +120,7 @@ export class NupstCli {
private async enable(): Promise<void> {
this.checkRootAccess('This command must be run as root.');
await this.nupst.getSystemd().install();
console.log('NUPST service has been installed. Use "nupst start" to start the service.');
logger.log('NUPST service has been installed. Use "nupst start" to start the service.');
}
/**
@ -127,12 +128,12 @@ export class NupstCli {
* @param debugMode Whether to enable debug mode
*/
private async daemonStart(debugMode: boolean = false): Promise<void> {
console.log('Starting NUPST daemon...');
logger.log('Starting NUPST daemon...');
try {
// Enable debug mode for SNMP if requested
if (debugMode) {
this.nupst.getSnmp().enableDebug();
console.log('SNMP debug mode enabled');
logger.log('SNMP debug mode enabled');
}
await this.nupst.getDaemon().start();
} catch (error) {
@ -148,7 +149,7 @@ export class NupstCli {
try {
// 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');
logger.log('Tailing nupst service logs (Ctrl+C to exit)...\n');
const journalctl = spawn('journalctl', ['-u', 'nupst.service', '-n', '50', '-f'], {
stdio: ['ignore', 'inherit', 'inherit'],
@ -165,7 +166,7 @@ export class NupstCli {
journalctl.on('exit', () => resolve());
});
} catch (error) {
console.error('Failed to retrieve logs:', error);
logger.error(`Failed to retrieve logs: ${error}`);
process.exit(1);
}
}
@ -212,7 +213,7 @@ export class NupstCli {
*/
private checkRootAccess(errorMessage: string): void {
if (process.getuid && process.getuid() !== 0) {
console.error(errorMessage);
logger.error(errorMessage);
process.exit(1);
}
}
@ -225,19 +226,21 @@ export class NupstCli {
try {
// Debug mode is now handled in parseAndExecute
if (debugMode) {
console.log('┌─ Debug Mode ─────────────────────────────┐');
console.log('│ SNMP debugging enabled - detailed logs will be shown');
console.log('└──────────────────────────────────────────┘');
const boxWidth = 45;
logger.logBoxTitle('Debug Mode', boxWidth);
logger.logBoxLine('SNMP debugging enabled - detailed logs will be shown');
logger.logBoxEnd();
}
// 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('└──────────────────────────────────────────┘');
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;
}
@ -247,7 +250,7 @@ export class NupstCli {
this.displayTestConfig(config);
await this.testConnection(config);
} catch (error) {
console.error(`Test failed: ${error.message}`);
logger.error(`Test failed: ${error.message}`);
}
}
@ -256,44 +259,45 @@ export class NupstCli {
* @param config Current configuration
*/
private displayTestConfig(config: any): void {
console.log('┌─ Testing Configuration ─────────────────────┐');
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'}`);
const boxWidth = 45;
logger.logBoxTitle('Testing Configuration', boxWidth);
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) {
console.log(` Community: ${config.snmp.community}`);
logger.logBoxLine(` Community: ${config.snmp.community}`);
} else if (config.snmp.version === 3) {
console.log(` Security Level: ${config.snmp.securityLevel}`);
console.log(` Username: ${config.snmp.username}`);
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') {
console.log(` Auth Protocol: ${config.snmp.authProtocol || 'None'}`);
logger.logBoxLine(` Auth Protocol: ${config.snmp.authProtocol || 'None'}`);
}
if (config.snmp.securityLevel === 'authPriv') {
console.log(` Privacy Protocol: ${config.snmp.privProtocol || 'None'}`);
logger.logBoxLine(` Privacy Protocol: ${config.snmp.privProtocol || 'None'}`);
}
// Show timeout value
console.log(` Timeout: ${config.snmp.timeout / 1000} seconds`);
logger.logBoxLine(` 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 Runtime: ${config.snmp.customOIDs.BATTERY_RUNTIME || 'Not set'}`);
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'}`);
}
console.log('Thresholds:');
console.log(` Battery: ${config.thresholds.battery}%`);
console.log(` Runtime: ${config.thresholds.runtime} minutes`);
console.log(`Check Interval: ${config.checkInterval / 1000} seconds`);
console.log('└──────────────────────────────────────────┘');
logger.logBoxLine('Thresholds:');
logger.logBoxLine(` Battery: ${config.thresholds.battery}%`);
logger.logBoxLine(` Runtime: ${config.thresholds.runtime} minutes`);
logger.logBoxLine(`Check Interval: ${config.checkInterval / 1000} seconds`);
logger.logBoxEnd();
}
/**
@ -301,7 +305,7 @@ export class NupstCli {
* @param config Current configuration
*/
private async testConnection(config: any): Promise<void> {
console.log('\nTesting connection to UPS...');
logger.log('\nTesting connection to UPS...');
try {
// Create a test config with a short timeout
const testConfig = {
@ -311,22 +315,24 @@ export class NupstCli {
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('└──────────────────────────────────────────┘');
const boxWidth = 45;
logger.logBoxTitle('Connection Successful!', boxWidth);
logger.logBoxLine('UPS Status:');
logger.logBoxLine(` Power Status: ${status.powerStatus}`);
logger.logBoxLine(` Battery Capacity: ${status.batteryCapacity}%`);
logger.logBoxLine(` Runtime Remaining: ${status.batteryRuntime} minutes`);
logger.logBoxEnd();
// Check status against thresholds if on battery
if (status.powerStatus === 'onBattery') {
this.analyzeThresholds(status, config);
}
} catch (error) {
console.error('┌─ Connection Failed! ───────────────────────┐');
console.error(`│ Error: ${error.message}`);
console.error('└──────────────────────────────────────────┘');
console.log("\nPlease check your settings and run 'nupst setup' to reconfigure.");
const errorBoxWidth = 45;
logger.logBoxTitle('Connection Failed!', errorBoxWidth);
logger.logBoxLine(`Error: ${error.message}`);
logger.logBoxEnd();
logger.log("\nPlease check your settings and run 'nupst setup' to reconfigure.");
}
}
@ -336,42 +342,43 @@ export class NupstCli {
* @param config Current configuration
*/
private analyzeThresholds(status: any, config: any): void {
console.log('┌─ Threshold Analysis ───────────────────────┐');
const boxWidth = 45;
logger.logBoxTitle('Threshold Analysis', boxWidth);
if (status.batteryCapacity < config.thresholds.battery) {
console.log('⚠️ WARNING: Battery capacity below threshold');
console.log(
` Current: ${status.batteryCapacity}% | Threshold: ${config.thresholds.battery}%`
logger.logBoxLine('⚠️ WARNING: Battery capacity below threshold');
logger.logBoxLine(
` Current: ${status.batteryCapacity}% | Threshold: ${config.thresholds.battery}%`
);
console.log(' System would initiate shutdown');
logger.logBoxLine(' System would initiate shutdown');
} else {
console.log('✓ Battery capacity above threshold');
console.log(
` Current: ${status.batteryCapacity}% | Threshold: ${config.thresholds.battery}%`
logger.logBoxLine('✓ Battery capacity above threshold');
logger.logBoxLine(
` 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`
logger.logBoxLine('⚠️ WARNING: Runtime below threshold');
logger.logBoxLine(
` Current: ${status.batteryRuntime} min | Threshold: ${config.thresholds.runtime} min`
);
console.log(' System would initiate shutdown');
logger.logBoxLine(' System would initiate shutdown');
} else {
console.log('✓ Runtime above threshold');
console.log(
` Current: ${status.batteryRuntime} min | Threshold: ${config.thresholds.runtime} min`
logger.logBoxLine('✓ Runtime above threshold');
logger.logBoxLine(
` Current: ${status.batteryRuntime} min | Threshold: ${config.thresholds.runtime} min`
);
}
console.log('└──────────────────────────────────────────┘');
logger.logBoxEnd();
}
/**
* Display help message
*/
private showHelp(): void {
console.log(`
logger.log(`
NUPST - Node.js UPS Shutdown Tool
Usage:
@ -405,8 +412,9 @@ Options:
'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...');
const boxWidth = 45;
logger.logBoxTitle('NUPST Update Process', boxWidth);
logger.logBoxLine('Updating NUPST from repository...');
// Determine the installation directory (assuming it's either /opt/nupst or the current directory)
const { existsSync } = await import('fs');
@ -416,26 +424,26 @@ Options:
// 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}`);
logger.logBoxLine(`Using local installation directory: ${installDir}`);
}
try {
// 1. Update the repository
console.log('Pulling latest changes from git repository...');
logger.logBoxLine('Pulling latest changes from git repository...');
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...');
logger.logBoxLine('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...');
logger.logBoxLine('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...');
logger.logBoxLine('Refreshing systemd service...');
// First check if service exists
let serviceExists = false;
@ -452,34 +460,34 @@ Options:
const isRunning =
execSync('systemctl is-active nupst.service || true').toString().trim() === 'active';
if (isRunning) {
console.log('Stopping nupst service...');
logger.logBoxLine('Stopping nupst service...');
execSync('systemctl stop nupst.service');
}
// Reinstall the service
console.log('Reinstalling systemd service...');
logger.logBoxLine('Reinstalling systemd service...');
await this.nupst.getSystemd().install();
// Restart the service if it was running
if (isRunning) {
console.log('Restarting nupst service...');
logger.logBoxLine('Restarting nupst service...');
execSync('systemctl start nupst.service');
}
} else {
console.log('Systemd service not installed, skipping service refresh.');
console.log('Run "nupst enable" to install the service.');
logger.logBoxLine('Systemd service not installed, skipping service refresh.');
logger.logBoxLine('Run "nupst enable" to install the service.');
}
console.log('Update completed successfully!');
console.log('└──────────────────────────────────────────┘');
logger.logBoxLine('Update completed successfully!');
logger.logBoxEnd();
} catch (error) {
console.error('Error during update process:');
console.error(`${error.message}`);
console.error('└──────────────────────────────────────────┘');
logger.logBoxLine('Error during update process:');
logger.logBoxLine(`${error.message}`);
logger.logBoxEnd();
process.exit(1);
}
} catch (error) {
console.error(`Update failed: ${error.message}`);
logger.error(`Update failed: ${error.message}`);
process.exit(1);
}
}
@ -512,7 +520,7 @@ Options:
rl.close();
}
} catch (error) {
console.error('Setup error:', error.message);
logger.error(`Setup error: ${error.message}`);
}
}
@ -521,9 +529,9 @@ Options:
* @param prompt Function to prompt for user input
*/
private async runSetupProcess(prompt: (question: string) => Promise<string>): Promise<void> {
console.log('\nNUPST Interactive Setup');
console.log('======================\n');
console.log('This will guide you through configuring your UPS SNMP settings.\n');
logger.log('\nNUPST Interactive Setup');
logger.log('======================\n');
logger.log('This will guide you through configuring your UPS SNMP settings.\n');
// Try to load existing config if available
let config;
@ -533,7 +541,7 @@ Options:
} catch (error) {
// If config doesn't exist, use default config
config = this.nupst.getDaemon().getConfig();
console.log('No existing configuration found. Creating a new configuration.');
logger.log('No existing configuration found. Creating a new configuration.');
}
// Gather SNMP settings
@ -556,7 +564,7 @@ Options:
// Check if service is running and restart it if needed
await this.restartServiceIfRunning();
console.log('\nSetup complete!');
logger.log('\nSetup complete!');
await this.optionallyEnableService(prompt);
}
@ -859,15 +867,18 @@ Options:
* @param config Current configuration
*/
private displayConfigSummary(config: any): void {
console.log('\n┌─ Configuration Summary ─────────────────┐');
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`
const boxWidth = 45;
logger.log('');
logger.logBoxTitle('Configuration Summary', boxWidth);
logger.logBoxLine(`SNMP Host: ${config.snmp.host}:${config.snmp.port}`);
logger.logBoxLine(`SNMP Version: ${config.snmp.version}`);
logger.logBoxLine(`UPS Model: ${config.snmp.upsModel}`);
logger.logBoxLine(
`Thresholds: ${config.thresholds.battery}% battery, ${config.thresholds.runtime} min runtime`
);
console.log(`Check Interval: ${config.checkInterval / 1000} seconds`);
console.log('└──────────────────────────────────────────┘\n');
logger.logBoxLine(`Check Interval: ${config.checkInterval / 1000} seconds`);
logger.logBoxEnd();
logger.log('');
}
/**
@ -883,7 +894,7 @@ Options:
'Would you like to test the connection to your UPS? (y/N): '
);
if (testConnection.toLowerCase() === 'y') {
console.log('\nTesting connection to UPS...');
logger.log('\nTesting connection to UPS...');
try {
// Create a test config with a short timeout
const testConfig = {
@ -892,17 +903,21 @@ Options:
};
const status = await this.nupst.getSnmp().getUpsStatus(testConfig);
console.log('\n┌─ 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('└──────────────────────────────────────────┘');
const boxWidth = 45;
logger.log('');
logger.logBoxTitle('Connection Successful!', boxWidth);
logger.logBoxLine('UPS Status:');
logger.logBoxLine(`✓ Power Status: ${status.powerStatus}`);
logger.logBoxLine(`✓ Battery Capacity: ${status.batteryCapacity}%`);
logger.logBoxLine(`✓ Runtime Remaining: ${status.batteryRuntime} minutes`);
logger.logBoxEnd();
} catch (error) {
console.error('\n┌─ Connection Failed! ───────────────────────┐');
console.error('│ Error: ' + error.message);
console.error('└──────────────────────────────────────────┘');
console.log('\nPlease check your settings and try again.');
const errorBoxWidth = 45;
logger.log('');
logger.logBoxTitle('Connection Failed!', errorBoxWidth);
logger.logBoxLine(`Error: ${error.message}`);
logger.logBoxEnd();
logger.log('\nPlease check your settings and try again.');
}
}
}
@ -919,27 +934,28 @@ Options:
if (isActive) {
// Service is running, restart it
console.log('┌─ Service Update ─────────────────────────┐');
console.log('│ Configuration has changed.');
console.log('│ Restarting NUPST service to apply changes...');
const boxWidth = 45;
logger.logBoxTitle('Service Update', boxWidth);
logger.logBoxLine('Configuration has changed.');
logger.logBoxLine('Restarting NUPST service to apply changes...');
try {
if (process.getuid && process.getuid() === 0) {
// We have root access, restart directly
execSync('systemctl restart nupst.service');
console.log('Service restarted successfully.');
logger.logBoxLine('Service restarted successfully.');
} else {
// No root access, show instructions
console.log('Please restart the service with:');
console.log(' sudo systemctl restart nupst.service');
logger.logBoxLine('Please restart the service with:');
logger.logBoxLine(' sudo systemctl restart nupst.service');
}
} catch (error) {
console.log(`Error restarting service: ${error.message}`);
console.log('You may need to restart the service manually:');
console.log(' sudo systemctl restart nupst.service');
logger.logBoxLine(`Error restarting service: ${error.message}`);
logger.logBoxLine('You may need to restart the service manually:');
logger.logBoxLine(' sudo systemctl restart nupst.service');
}
console.log('└──────────────────────────────────────────┘');
logger.logBoxEnd();
}
} catch (error) {
// Ignore errors checking service status
@ -990,69 +1006,71 @@ Options:
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('└──────────────────────────────────────────┘');
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();
console.log('┌─ NUPST Configuration ──────────────────────┐');
const boxWidth = 50;
logger.logBoxTitle('NUPST Configuration', boxWidth);
// 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'}`);
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) {
console.log(` Community: ${config.snmp.community}`);
logger.logBoxLine(` Community: ${config.snmp.community}`);
} else if (config.snmp.version === 3) {
console.log(` Security Level: ${config.snmp.securityLevel}`);
console.log(` Username: ${config.snmp.username}`);
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'
) {
console.log(` Auth Protocol: ${config.snmp.authProtocol || 'None'}`);
logger.logBoxLine(` Auth Protocol: ${config.snmp.authProtocol || 'None'}`);
}
if (config.snmp.securityLevel === 'authPriv') {
console.log(` Privacy Protocol: ${config.snmp.privProtocol || 'None'}`);
logger.logBoxLine(` Privacy Protocol: ${config.snmp.privProtocol || 'None'}`);
}
// Show timeout value
console.log(` Timeout: ${config.snmp.timeout / 1000} seconds`);
logger.logBoxLine(` 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'}`
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'}`
);
console.log(` Battery Runtime: ${config.snmp.customOIDs.BATTERY_RUNTIME || 'Not set'}`);
logger.logBoxLine(` 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`);
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
console.log('');
console.log('Configuration File Location:');
console.log(' /etc/nupst/config.json');
logger.logBoxLine('');
logger.logBoxLine('Configuration File Location:');
logger.logBoxLine(' /etc/nupst/config.json');
console.log('└──────────────────────────────────────────┘');
logger.logBoxEnd();
// Show service status
try {
@ -1061,15 +1079,16 @@ Options:
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'}`);
console.log('└──────────────────────────────────────────┘');
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) {
console.error(`Failed to display configuration: ${error.message}`);
logger.error(`Failed to display configuration: ${error.message}`);
}
}

View File

@ -4,6 +4,7 @@ import { exec, execFile } from 'child_process';
import { promisify } from 'util';
import { NupstSnmp } from './snmp/manager.js';
import type { ISnmpConfig } from './snmp/types.js';
import { logger } from './logger.js';
const execAsync = promisify(exec);
const execFileAsync = promisify(execFile);
@ -147,11 +148,11 @@ export class NupstDaemon {
*/
public async start(): Promise<void> {
if (this.isRunning) {
console.log('Daemon is already running');
logger.log('Daemon is already running');
return;
}
console.log('Starting NUPST daemon...');
logger.log('Starting NUPST daemon...');
try {
// Load configuration - this will throw an error if config doesn't exist
@ -165,11 +166,12 @@ export class NupstDaemon {
this.snmp.getNupst().checkForUpdates().then(updateAvailable => {
if (updateAvailable) {
const updateStatus = this.snmp.getNupst().getUpdateStatus();
console.log('┌─ Update Available ───────────────────────┐');
console.log(`│ Current Version: ${updateStatus.currentVersion}`);
console.log(`│ Latest Version: ${updateStatus.latestVersion}`);
console.log('│ Run "sudo nupst update" to update');
console.log('└──────────────────────────────────────────┘');
const boxWidth = 45;
logger.logBoxTitle('Update Available', boxWidth);
logger.logBoxLine(`Current Version: ${updateStatus.currentVersion}`);
logger.logBoxLine(`Latest Version: ${updateStatus.latestVersion}`);
logger.logBoxLine('Run "sudo nupst update" to update');
logger.logBoxEnd();
}
}).catch(() => {}); // Ignore errors checking for updates
@ -178,7 +180,7 @@ export class NupstDaemon {
await this.monitor();
} catch (error) {
this.isRunning = false;
console.error(`Daemon failed to start: ${error.message}`);
logger.error(`Daemon failed to start: ${error.message}`);
process.exit(1); // Exit with error
}
}
@ -187,23 +189,24 @@ export class NupstDaemon {
* Log the loaded configuration settings
*/
private logConfigLoaded(): void {
console.log('┌─ Configuration Loaded ─────────────────────┐');
console.log('│ SNMP Settings:');
console.log(`│ Host: ${this.config.snmp.host}`);
console.log(` Port: ${this.config.snmp.port}`);
console.log(`│ Version: ${this.config.snmp.version}`);
console.log('│ Thresholds:');
console.log(`│ Battery: ${this.config.thresholds.battery}%`);
console.log(`│ Runtime: ${this.config.thresholds.runtime} minutes`);
console.log(`│ Check Interval: ${this.config.checkInterval / 1000} seconds`);
console.log('└────────────────────────────────────────────┘');
const boxWidth = 50;
logger.logBoxTitle('Configuration Loaded', boxWidth);
logger.logBoxLine('SNMP Settings:');
logger.logBoxLine(` Host: ${this.config.snmp.host}`);
logger.logBoxLine(` Port: ${this.config.snmp.port}`);
logger.logBoxLine(` Version: ${this.config.snmp.version}`);
logger.logBoxLine('Thresholds:');
logger.logBoxLine(` Battery: ${this.config.thresholds.battery}%`);
logger.logBoxLine(` Runtime: ${this.config.thresholds.runtime} minutes`);
logger.logBoxLine(`Check Interval: ${this.config.checkInterval / 1000} seconds`);
logger.logBoxEnd();
}
/**
* Stop the monitoring daemon
*/
public stop(): void {
console.log('Stopping NUPST daemon...');
logger.log('Stopping NUPST daemon...');
this.isRunning = false;
}
@ -211,7 +214,7 @@ export class NupstDaemon {
* Monitor the UPS status and trigger shutdown when necessary
*/
private async monitor(): Promise<void> {
console.log('Starting UPS monitoring...');
logger.log('Starting UPS monitoring...');
let lastStatus: 'online' | 'onBattery' | 'unknown' = 'unknown';
let lastLogTime = 0; // Track when we last logged status
@ -226,20 +229,22 @@ export class NupstDaemon {
// Log status changes
if (status.powerStatus !== lastStatus) {
console.log('┌─ Power Status Change ─────────────────────┐');
console.log(` Status changed: ${lastStatus}${status.powerStatus}`);
console.log('└───────────────────────────────────────────┘');
const statusBoxWidth = 45;
logger.logBoxTitle('Power Status Change', statusBoxWidth);
logger.logBoxLine(`Status changed: ${lastStatus}${status.powerStatus}`);
logger.logBoxEnd();
lastStatus = status.powerStatus;
lastLogTime = currentTime; // Reset log timer when status changes
}
// Log status periodically (at least every 5 minutes)
else if (shouldLogStatus) {
const timestamp = new Date().toISOString();
console.log('┌─ Periodic Status Update ──────────────────┐');
console.log(`│ Timestamp: ${timestamp}`);
console.log(`│ Power Status: ${status.powerStatus}`);
console.log(`│ Battery: ${status.batteryCapacity}% | Runtime: ${status.batteryRuntime} min`);
console.log('└───────────────────────────────────────────┘');
const periodicBoxWidth = 45;
logger.logBoxTitle('Periodic Status Update', periodicBoxWidth);
logger.logBoxLine(`Timestamp: ${timestamp}`);
logger.logBoxLine(`Power Status: ${status.powerStatus}`);
logger.logBoxLine(`Battery: ${status.batteryCapacity}% | Runtime: ${status.batteryRuntime} min`);
logger.logBoxEnd();
lastLogTime = currentTime;
}
@ -293,7 +298,7 @@ export class NupstDaemon {
* @param reason Reason for shutdown
*/
public async initiateShutdown(reason: string): Promise<void> {
console.log(`Initiating system shutdown due to: ${reason}`);
logger.log(`Initiating system shutdown due to: ${reason}`);
// Set a longer delay for shutdown to allow VMs and services to close
const shutdownDelayMinutes = 5;
@ -312,7 +317,7 @@ export class NupstDaemon {
try {
if (fs.existsSync(path)) {
shutdownCmd = path;
console.log(`Found shutdown command at: ${shutdownCmd}`);
logger.log(`Found shutdown command at: ${shutdownCmd}`);
break;
}
} catch (e) {
@ -322,32 +327,32 @@ export class NupstDaemon {
if (shutdownCmd) {
// Execute shutdown command with delay to allow for VM graceful shutdown
console.log(`Executing: ${shutdownCmd} -h +${shutdownDelayMinutes} "UPS battery critical..."`);
logger.log(`Executing: ${shutdownCmd} -h +${shutdownDelayMinutes} "UPS battery critical..."`);
const { stdout } = await execFileAsync(shutdownCmd, [
'-h',
`+${shutdownDelayMinutes}`,
`UPS battery critical, shutting down in ${shutdownDelayMinutes} minutes`
]);
console.log('Shutdown initiated:', stdout);
console.log(`Allowing ${shutdownDelayMinutes} minutes for VMs to shut down safely`);
logger.log(`Shutdown initiated: ${stdout}`);
logger.log(`Allowing ${shutdownDelayMinutes} minutes for VMs to shut down safely`);
} else {
// Try using the PATH to find shutdown
try {
console.log('Shutdown command not found in common paths, trying via PATH...');
logger.log('Shutdown command not found in common paths, trying via PATH...');
const { stdout } = await execAsync(`shutdown -h +${shutdownDelayMinutes} "UPS battery critical, shutting down in ${shutdownDelayMinutes} minutes"`, {
env: process.env // Pass the current environment
});
console.log('Shutdown initiated:', stdout);
logger.log(`Shutdown initiated: ${stdout}`);
} catch (e) {
throw new Error(`Shutdown command not found: ${e.message}`);
}
}
// Monitor UPS during shutdown and force immediate shutdown if battery gets too low
console.log('Monitoring UPS during shutdown process...');
logger.log('Monitoring UPS during shutdown process...');
await this.monitorDuringShutdown();
} catch (error) {
console.error('Failed to initiate shutdown:', error);
logger.error(`Failed to initiate shutdown: ${error}`);
// Try alternative shutdown methods
const alternatives = [
@ -376,24 +381,24 @@ export class NupstDaemon {
}
if (cmdPath) {
console.log(`Trying alternative shutdown method: ${cmdPath} ${alt.args.join(' ')}`);
logger.log(`Trying alternative shutdown method: ${cmdPath} ${alt.args.join(' ')}`);
await execFileAsync(cmdPath, alt.args);
return; // Exit if successful
} else {
// Try using PATH environment
console.log(`Trying alternative via PATH: ${alt.cmd} ${alt.args.join(' ')}`);
logger.log(`Trying alternative via PATH: ${alt.cmd} ${alt.args.join(' ')}`);
await execAsync(`${alt.cmd} ${alt.args.join(' ')}`, {
env: process.env // Pass the current environment
});
return; // Exit if successful
}
} catch (altError) {
console.error(`Alternative method ${alt.cmd} failed:`, altError);
logger.error(`Alternative method ${alt.cmd} failed: ${altError}`);
// Continue to next method
}
}
console.error('All shutdown methods failed');
logger.error('All shutdown methods failed');
}
}

View File

@ -1,6 +1,7 @@
#!/usr/bin/env node
import { NupstCli } from './cli.js';
import { logger } from './logger.js';
/**
* Main entry point for NUPST
@ -13,6 +14,6 @@ async function main() {
// Run the main function and handle any errors
main().catch(error => {
console.error('Error:', error);
logger.error(`Error: ${error}`);
process.exit(1);
});

147
ts/logger.ts Normal file
View File

@ -0,0 +1,147 @@
/**
* A simple logger class that provides consistent formatting for log messages
* including support for logboxes with title, lines, and closing
*/
export class Logger {
private currentBoxWidth: number | null = null;
private static instance: Logger;
/**
* Creates a new Logger instance
*/
constructor() {
this.currentBoxWidth = null;
}
/**
* Get the singleton logger instance
* @returns The singleton logger instance
*/
public static getInstance(): Logger {
if (!Logger.instance) {
Logger.instance = new Logger();
}
return Logger.instance;
}
/**
* Log a message
* @param message Message to log
*/
public log(message: string): void {
console.log(message);
}
/**
* Log an error message
* @param message Error message to log
*/
public error(message: string): void {
console.error(message);
}
/**
* Log a warning message with a warning emoji
* @param message Warning message to log
*/
public warn(message: string): void {
console.warn(`⚠️ ${message}`);
}
/**
* Log a success message with a checkmark
* @param message Success message to log
*/
public success(message: string): void {
console.log(`${message}`);
}
/**
* Log a logbox title and set the current box width
* @param title Title of the logbox
* @param width Width of the logbox (including borders)
*/
public logBoxTitle(title: string, width: number): void {
this.currentBoxWidth = width;
// Create the title line with appropriate padding
const paddedTitle = ` ${title} `;
const remainingSpace = width - 3 - paddedTitle.length;
// Title line: ┌─ Title ───┐
const titleLine = `┌─${paddedTitle}${'─'.repeat(remainingSpace)}`;
console.log(titleLine);
}
/**
* Log a logbox line
* @param content Content of the line
* @param width Optional width override. If not provided, uses the current box width.
*/
public logBoxLine(content: string, width?: number): void {
const boxWidth = width || this.currentBoxWidth;
if (!boxWidth) {
throw new Error('No box width specified and no previous box width to use');
}
// Calculate the available space for content
const availableSpace = boxWidth - 2; // Account for left and right borders
if (content.length <= availableSpace - 1) {
// If content fits with at least one space for the right border stripe
const padding = availableSpace - content.length - 1;
console.log(`${content}${' '.repeat(padding)}`);
} else {
// Content is too long, let it flow out of boundaries.
console.log(`${content}`);
}
}
/**
* Log a logbox end
* @param width Optional width override. If not provided, uses the current box width.
*/
public logBoxEnd(width?: number): void {
const boxWidth = width || this.currentBoxWidth;
if (!boxWidth) {
throw new Error('No box width specified and no previous box width to use');
}
// Create the bottom border: └────────┘
console.log(`${'─'.repeat(boxWidth - 2)}`);
// Reset the current box width
this.currentBoxWidth = null;
}
/**
* Log a complete logbox with title, content lines, and ending
* @param title Title of the logbox
* @param lines Array of content lines
* @param width Width of the logbox
*/
public logBox(title: string, lines: string[], width: number): void {
this.logBoxTitle(title, width);
for (const line of lines) {
this.logBoxLine(line);
}
this.logBoxEnd();
}
/**
* Log a divider line
* @param width Width of the divider
* @param character Character to use for the divider (default: ─)
*/
public logDivider(width: number, character: string = '─'): void {
console.log(character.repeat(width));
}
}
// Export a singleton instance for easy use
export const logger = Logger.getInstance();

View File

@ -4,6 +4,7 @@ import { NupstSystemd } from './systemd.js';
import { commitinfo } from './00_commitinfo_data.js';
import { spawn } from 'child_process';
import * as https from 'https';
import { logger } from './logger.js';
/**
* Main Nupst class that coordinates all components
@ -70,7 +71,7 @@ export class Nupst {
return this.updateAvailable;
} catch (error) {
console.error(`Error checking for updates: ${error.message}`);
logger.error(`Error checking for updates: ${error.message}`);
return false;
}
}
@ -162,28 +163,33 @@ export class Nupst {
*/
public logVersionInfo(checkForUpdates: boolean = true): void {
const version = this.getVersion();
console.log('┌─ NUPST Version ────────────────────────────┐');
console.log(`│ Current Version: ${version}`);
const boxWidth = 45;
logger.logBoxTitle('NUPST Version', boxWidth);
logger.logBoxLine(`Current Version: ${version}`);
if (this.updateAvailable && this.latestVersion) {
console.log(`Update Available: ${this.latestVersion}`);
console.log('Run "sudo nupst update" to update');
logger.logBoxLine(`Update Available: ${this.latestVersion}`);
logger.logBoxLine('Run "sudo nupst update" to update');
logger.logBoxEnd();
} else if (checkForUpdates) {
console.log('Checking for updates...');
logger.logBoxLine('Checking for updates...');
// We can't end the box yet since we're in an async operation
this.checkForUpdates().then(updateAvailable => {
if (updateAvailable) {
console.log(`Update Available: ${this.latestVersion}`);
console.log('Run "sudo nupst update" to update');
logger.logBoxLine(`Update Available: ${this.latestVersion}`);
logger.logBoxLine('Run "sudo nupst update" to update');
} else {
console.log('You are running the latest version');
logger.logBoxLine('You are running the latest version');
}
console.log('└──────────────────────────────────────────┘');
logger.logBoxEnd();
}).catch(() => {
console.log('Could not check for updates');
console.log('└──────────────────────────────────────────┘');
logger.logBoxLine('Could not check for updates');
logger.logBoxEnd();
});
} else {
console.log('└──────────────────────────────────────────┘');
logger.logBoxEnd();
}
}
}

View File

@ -1,6 +1,7 @@
import { promises as fs } from 'fs';
import { execSync } from 'child_process';
import { NupstDaemon } from './daemon.js';
import { logger } from './logger.js';
/**
* Class for managing systemd service
@ -47,10 +48,11 @@ WantedBy=multi-user.target
try {
await fs.access(configPath);
} catch (error) {
console.error('┌─ Configuration Error ─────────────────────┐');
console.error(`│ No configuration file found at ${configPath}`);
console.error('│ Please run \'nupst setup\' first to create a configuration.');
console.error('└──────────────────────────────────────────┘');
const boxWidth = 50;
logger.logBoxTitle('Configuration Error', boxWidth);
logger.logBoxLine(`No configuration file found at ${configPath}`);
logger.logBoxLine("Please run 'nupst setup' first to create a configuration.");
logger.logBoxEnd();
throw new Error('Configuration not found');
}
}
@ -66,23 +68,24 @@ WantedBy=multi-user.target
// Write the service file
await fs.writeFile(this.serviceFilePath, this.serviceTemplate);
console.log('┌─ Service Installation ─────────────────────┐');
console.log(`│ Service file created at ${this.serviceFilePath}`);
const boxWidth = 50;
logger.logBoxTitle('Service Installation', boxWidth);
logger.logBoxLine(`Service file created at ${this.serviceFilePath}`);
// Reload systemd daemon
execSync('systemctl daemon-reload');
console.log('Systemd daemon reloaded');
logger.logBoxLine('Systemd daemon reloaded');
// Enable the service
execSync('systemctl enable nupst.service');
console.log('Service enabled to start on boot');
console.log('└──────────────────────────────────────────┘');
logger.logBoxLine('Service enabled to start on boot');
logger.logBoxEnd();
} catch (error) {
if (error.message === 'Configuration not found') {
// Just rethrow the error as the message has already been displayed
throw error;
}
console.error('Failed to install systemd service:', error);
logger.error(`Failed to install systemd service: ${error}`);
throw error;
}
}
@ -97,15 +100,16 @@ WantedBy=multi-user.target
await this.checkConfigExists();
execSync('systemctl start nupst.service');
console.log('┌─ Service Status ─────────────────────────┐');
console.log('│ NUPST service started successfully');
console.log('└──────────────────────────────────────────┘');
const boxWidth = 45;
logger.logBoxTitle('Service Status', boxWidth);
logger.logBoxLine('NUPST service started successfully');
logger.logBoxEnd();
} catch (error) {
if (error.message === 'Configuration not found') {
// Exit with error code since configuration is required
process.exit(1);
}
console.error('Failed to start service:', error);
logger.error(`Failed to start service: ${error}`);
throw error;
}
}
@ -117,9 +121,9 @@ WantedBy=multi-user.target
public async stop(): Promise<void> {
try {
execSync('systemctl stop nupst.service');
console.log('NUPST service stopped');
logger.success('NUPST service stopped');
} catch (error) {
console.error('Failed to stop service:', error);
logger.error(`Failed to stop service: ${error}`);
throw error;
}
}
@ -132,9 +136,10 @@ WantedBy=multi-user.target
try {
// Enable debug mode if requested
if (debugMode) {
console.log('┌─ Debug Mode ─────────────────────────────┐');
console.log('│ SNMP debugging enabled - detailed logs will be shown');
console.log('└──────────────────────────────────────────┘');
const boxWidth = 45;
logger.logBoxTitle('Debug Mode', boxWidth);
logger.logBoxLine('SNMP debugging enabled - detailed logs will be shown');
logger.logBoxEnd();
this.daemon.getNupstSnmp().enableDebug();
}
@ -152,7 +157,7 @@ WantedBy=multi-user.target
await this.displayServiceStatus();
await this.displayUpsStatus();
} catch (error) {
console.error(`Failed to get status: ${error.message}`);
logger.error(`Failed to get status: ${error.message}`);
}
}
@ -163,13 +168,18 @@ WantedBy=multi-user.target
private async displayServiceStatus(): Promise<void> {
try {
const serviceStatus = execSync('systemctl status nupst.service').toString();
console.log('┌─ Service Status ─────────────────────────┐');
console.log(serviceStatus.split('\n').map(line => `${line}`).join('\n'));
console.log('└──────────────────────────────────────────┘');
const boxWidth = 45;
logger.logBoxTitle('Service Status', boxWidth);
// Process each line of the status output
serviceStatus.split('\n').forEach(line => {
logger.logBoxLine(line);
});
logger.logBoxEnd();
} catch (error) {
console.error('┌─ Service Status ─────────────────────────┐');
console.error('Service is not running');
console.error('└──────────────────────────────────────────┘');
const boxWidth = 45;
logger.logBoxTitle('Service Status', boxWidth);
logger.logBoxLine('Service is not running');
logger.logBoxEnd();
}
}
@ -190,22 +200,24 @@ WantedBy=multi-user.target
timeout: Math.min(config.snmp.timeout, 10000) // Use at most 10 seconds for status check
};
console.log('┌─ Connecting to UPS... ─────────────────────┐');
console.log(`│ Host: ${config.snmp.host}:${config.snmp.port}`);
console.log(`│ UPS Model: ${config.snmp.upsModel || 'cyberpower'}`);
console.log('└────────────────────────────────────────────┘');
const boxWidth = 45;
logger.logBoxTitle('Connecting to UPS...', boxWidth);
logger.logBoxLine(`Host: ${config.snmp.host}:${config.snmp.port}`);
logger.logBoxLine(`UPS Model: ${config.snmp.upsModel || 'cyberpower'}`);
logger.logBoxEnd();
const status = await snmp.getUpsStatus(snmpConfig);
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('└──────────────────────────────────────────┘');
logger.logBoxTitle('UPS Status', boxWidth);
logger.logBoxLine(`Power Status: ${status.powerStatus}`);
logger.logBoxLine(`Battery Capacity: ${status.batteryCapacity}%`);
logger.logBoxLine(`Runtime Remaining: ${status.batteryRuntime} minutes`);
logger.logBoxEnd();
} catch (error) {
console.error('┌─ UPS Status ─────────────────────────────┐');
console.error(`│ Failed to retrieve UPS status: ${error.message}`);
console.error('└──────────────────────────────────────────┘');
const boxWidth = 45;
logger.logBoxTitle('UPS Status', boxWidth);
logger.logBoxLine(`Failed to retrieve UPS status: ${error.message}`);
logger.logBoxEnd();
}
}
@ -221,10 +233,10 @@ WantedBy=multi-user.target
// Reload systemd daemon
execSync('systemctl daemon-reload');
console.log('Systemd daemon reloaded');
console.log('NUPST service has been successfully uninstalled');
logger.log('Systemd daemon reloaded');
logger.success('NUPST service has been successfully uninstalled');
} catch (error) {
console.error('Failed to disable and uninstall service:', error);
logger.error(`Failed to disable and uninstall service: ${error}`);
throw error;
}
}
@ -235,11 +247,11 @@ WantedBy=multi-user.target
*/
private async stopService(): Promise<void> {
try {
console.log('Stopping NUPST service...');
logger.log('Stopping NUPST service...');
execSync('systemctl stop nupst.service');
} catch (error) {
// Service might not be running, that's okay
console.log('Service was not running or could not be stopped');
logger.log('Service was not running or could not be stopped');
}
}
@ -249,10 +261,10 @@ WantedBy=multi-user.target
*/
private async disableService(): Promise<void> {
try {
console.log('Disabling NUPST service...');
logger.log('Disabling NUPST service...');
execSync('systemctl disable nupst.service');
} catch (error) {
console.log('Service was not enabled or could not be disabled');
logger.log('Service was not enabled or could not be disabled');
}
}
@ -262,11 +274,11 @@ WantedBy=multi-user.target
*/
private async removeServiceFile(): Promise<void> {
if (await fs.stat(this.serviceFilePath).catch(() => null)) {
console.log(`Removing service file ${this.serviceFilePath}...`);
logger.log(`Removing service file ${this.serviceFilePath}...`);
await fs.unlink(this.serviceFilePath);
console.log('Service file removed');
logger.log('Service file removed');
} else {
console.log('Service file did not exist');
logger.log('Service file did not exist');
}
}
}