From e47f316d0a961beb9c31b680d7cab168ecf1db08 Mon Sep 17 00:00:00 2001 From: Philipp Kunz Date: Tue, 25 Mar 2025 11:49:50 +0000 Subject: [PATCH] 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 --- changelog.md | 7 ++++ ts/00_commitinfo_data.ts | 2 +- ts/daemon.ts | 88 +++++++++++++++++++++++++++++++++++++++- ts/snmp/manager.ts | 27 +----------- 4 files changed, 95 insertions(+), 29 deletions(-) diff --git a/changelog.md b/changelog.md index 12773ad..f654313 100644 --- a/changelog.md +++ b/changelog.md @@ -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 diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index ae94331..efe3da5 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.4.1', + version: '2.4.2', description: 'Node.js UPS Shutdown Tool for SNMP-enabled UPS devices' } diff --git a/ts/daemon.ts b/ts/daemon.ts index 661cff2..30073fb 100644 --- a/ts/daemon.ts +++ b/ts/daemon.ts @@ -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 { + 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 { + 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 diff --git a/ts/snmp/manager.ts b/ts/snmp/manager.ts index c36535c..6d03922 100644 --- a/ts/snmp/manager.ts +++ b/ts/snmp/manager.ts @@ -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 { - 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 } \ No newline at end of file