fix(daemon): Refactor shutdown initiation logic in daemon by moving the initiateShutdown and monitorDuringShutdown methods from the SNMP manager to the daemon, and update calls accordingly

This commit is contained in:
Philipp Kunz 2025-03-25 11:49:50 +00:00
parent 901127f784
commit e47f316d0a
4 changed files with 95 additions and 29 deletions

View File

@ -1,5 +1,12 @@
# Changelog
## 2025-03-25 - 2.4.2 - fix(daemon)
Refactor shutdown initiation logic in daemon by moving the initiateShutdown and monitorDuringShutdown methods from the SNMP manager to the daemon, and update calls accordingly
- Moved initiateShutdown and monitorDuringShutdown to the daemon class for improved cohesion
- Updated references in the daemon to call its own shutdown method instead of the SNMP manager
- Removed redundant initiateShutdown method from the SNMP manager
## 2025-03-25 - 2.4.1 - fix(docs)
Update readme with detailed legal and trademark guidance

View File

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

View File

@ -1,7 +1,11 @@
import * as fs from 'fs';
import * as path from 'path';
import { exec } from 'child_process';
import { promisify } from 'util';
import { NupstSnmp, type ISnmpConfig } from './snmp.js';
const execAsync = promisify(exec);
/**
* Configuration interface for the daemon
*/
@ -269,7 +273,7 @@ export class NupstDaemon {
if (status.batteryCapacity < this.config.thresholds.battery) {
console.log('⚠️ WARNING: Battery capacity below threshold');
console.log(`Current: ${status.batteryCapacity}% | Threshold: ${this.config.thresholds.battery}%`);
await this.snmp.initiateShutdown('Battery capacity below threshold');
await this.initiateShutdown('Battery capacity below threshold');
return;
}
@ -277,10 +281,90 @@ export class NupstDaemon {
if (status.batteryRuntime < this.config.thresholds.runtime) {
console.log('⚠️ WARNING: Runtime below threshold');
console.log(`Current: ${status.batteryRuntime} min | Threshold: ${this.config.thresholds.runtime} min`);
await this.snmp.initiateShutdown('Runtime below threshold');
await this.initiateShutdown('Runtime below threshold');
return;
}
}
/**
* Initiate system shutdown with UPS monitoring during shutdown
* @param reason Reason for shutdown
*/
public async initiateShutdown(reason: string): Promise<void> {
console.log(`Initiating system shutdown due to: ${reason}`);
// Set a longer delay for shutdown to allow VMs and services to close
const shutdownDelayMinutes = 5;
try {
// Execute shutdown command with delay to allow for VM graceful shutdown
const { stdout } = await execAsync(`shutdown -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`);
// Monitor UPS during shutdown and force immediate shutdown if battery gets too low
console.log('Monitoring UPS during shutdown process...');
await this.monitorDuringShutdown();
} catch (error) {
console.error('Failed to initiate shutdown:', error);
// Try a different method if first one fails
try {
console.log('Trying alternative shutdown method...');
await execAsync('poweroff --force');
} catch (innerError) {
console.error('All shutdown methods failed:', innerError);
}
}
}
/**
* Monitor UPS during system shutdown
* Force immediate shutdown if battery gets critically low
*/
private async monitorDuringShutdown(): Promise<void> {
const EMERGENCY_RUNTIME_THRESHOLD = 5; // 5 minutes remaining is critical
const CHECK_INTERVAL = 30000; // Check every 30 seconds during shutdown
const MAX_MONITORING_TIME = 5 * 60 * 1000; // Max 5 minutes of monitoring
const startTime = Date.now();
console.log(`Emergency shutdown threshold: ${EMERGENCY_RUNTIME_THRESHOLD} minutes remaining battery runtime`);
// Continue monitoring until max monitoring time is reached
while (Date.now() - startTime < MAX_MONITORING_TIME) {
try {
console.log('Checking UPS status during shutdown...');
const status = await this.snmp.getUpsStatus(this.config.snmp);
console.log(`Current battery: ${status.batteryCapacity}%, Runtime: ${status.batteryRuntime} minutes`);
// If battery runtime gets critically low, force immediate shutdown
if (status.batteryRuntime < EMERGENCY_RUNTIME_THRESHOLD) {
console.log('┌─ EMERGENCY SHUTDOWN ─────────────────────┐');
console.log(`│ Battery runtime critically low: ${status.batteryRuntime} minutes`);
console.log('│ Forcing immediate shutdown!');
console.log('└──────────────────────────────────────────┘');
try {
await execAsync('shutdown -h now "EMERGENCY: UPS battery critically low, shutting down NOW"');
} catch (error) {
console.error('Emergency shutdown failed, trying alternative method...');
await execAsync('poweroff --force');
}
// Stop monitoring after initiating emergency shutdown
return;
}
// Wait before checking again
await this.sleep(CHECK_INTERVAL);
} catch (error) {
console.error('Error monitoring UPS during shutdown:', error);
await this.sleep(CHECK_INTERVAL);
}
}
console.log('UPS monitoring during shutdown completed');
}
/**
* Sleep for the specified milliseconds

View File

@ -1,13 +1,9 @@
import { exec } from 'child_process';
import { promisify } from 'util';
import * as dgram from 'dgram';
import type { IOidSet, ISnmpConfig, TUpsModel, IUpsStatus } from './types.js';
import { UpsOidSets } from './oid-sets.js';
import { SnmpPacketCreator } from './packet-creator.js';
import { SnmpPacketParser } from './packet-parser.js';
const execAsync = promisify(exec);
/**
* Class for SNMP communication with UPS devices
* Main entry point for SNMP functionality
@ -507,26 +503,5 @@ export class NupstSnmp {
});
}
/**
* Initiate system shutdown
* @param reason Reason for shutdown
*/
public async initiateShutdown(reason: string): Promise<void> {
console.log(`Initiating system shutdown due to: ${reason}`);
try {
// Execute shutdown command with 5 minute delay to allow for VM graceful shutdown
const { stdout } = await execAsync('shutdown -h +5 "UPS battery critical, shutting down in 5 minutes"');
console.log('Shutdown initiated:', stdout);
console.log('Allowing 5 minutes for VMs to shut down safely');
} catch (error) {
console.error('Failed to initiate shutdown:', error);
// Try a different method if first one fails
try {
console.log('Trying alternative shutdown method...');
await execAsync('poweroff --force');
} catch (innerError) {
console.error('All shutdown methods failed:', innerError);
}
}
}
// initiateShutdown method has been moved to the NupstDaemon class
}