Compare commits
	
		
			8 Commits
		
	
	
		
			v2.4.1
			...
			897e86ad60
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 897e86ad60 | |||
| ed78db20e2 | |||
| bd00dfe02c | |||
| 55c040df82 | |||
| e68654a022 | |||
| 89a5d23d2f | |||
| f9aa1cfd2f | |||
| e47f316d0a | 
							
								
								
									
										26
									
								
								changelog.md
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								changelog.md
									
									
									
									
									
								
							| @@ -1,5 +1,31 @@ | ||||
| # Changelog | ||||
|  | ||||
| ## 2025-03-25 - 2.4.5 - fix(install) | ||||
| Improve interactive terminal detection and update installation instructions | ||||
|  | ||||
| - Enhanced install.sh to better detect non-interactive environments and provide clearer guidance for both interactive and non-interactive installations | ||||
| - Updated README.md quick install instructions to recommend process substitution and clarify auto-yes usage | ||||
|  | ||||
| ## 2025-03-25 - 2.4.4 - fix(install) | ||||
| Improve interactive mode detection and non-interactive installation handling in install.sh | ||||
|  | ||||
| - Detect and warn when running without a controlling terminal | ||||
| - Attempt to use /dev/tty for user input when possible | ||||
| - Update prompts and error messages for auto-installation of dependencies | ||||
| - Clarify installation instructions in readme for interactive and non-interactive modes | ||||
|  | ||||
| ## 2025-03-25 - 2.4.3 - fix(readme) | ||||
| Update Quick Install command syntax in readme for auto-yes installation | ||||
|  | ||||
| - Changed installation command to use: curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh | sudo bash -c "bash -s -- -y" | ||||
|  | ||||
| ## 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 | ||||
|  | ||||
|   | ||||
							
								
								
									
										41
									
								
								install.sh
									
									
									
									
									
								
							
							
						
						
									
										41
									
								
								install.sh
									
									
									
									
									
								
							| @@ -50,11 +50,40 @@ fi | ||||
|  | ||||
| # Detect if script is being piped or run directly | ||||
| PIPED=0 | ||||
| INTERACTIVE=1 | ||||
| if [ ! -t 0 ]; then | ||||
|   # Being piped, need to clone the repo | ||||
|   PIPED=1 | ||||
| fi | ||||
|  | ||||
| # Check if stdin is a terminal | ||||
| if [ ! -t 0 ] || [ ! -t 1 ]; then | ||||
|   # Either stdin or stdout is not a terminal, check if -y was provided | ||||
|   if [ $AUTO_YES -ne 1 ]; then | ||||
|     echo "Script detected it's running in a non-interactive environment without -y flag." | ||||
|     echo "Attempting to find a controlling terminal for interactive prompts..." | ||||
|     # Try to use a controlling terminal for user input | ||||
|     if [ -t 1 ]; then | ||||
|       # Stdout is a terminal, use it | ||||
|       exec < /dev/tty 2>/dev/null || INTERACTIVE=0 | ||||
|     else | ||||
|       # Try to find controlling terminal | ||||
|       exec < /dev/tty 2>/dev/null || INTERACTIVE=0 | ||||
|     fi | ||||
|      | ||||
|     if [ $INTERACTIVE -eq 0 ]; then | ||||
|       echo "ERROR: No controlling terminal available for interactive prompts." | ||||
|       echo "For interactive installation (RECOMMENDED):" | ||||
|       echo "  sudo bash <(curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh)" | ||||
|       echo "For non-interactive installation with automatic dependency installation:" | ||||
|       echo "  sudo bash <(curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh) -y" | ||||
|       exit 1 | ||||
|     else | ||||
|       echo "Interactive terminal found, continuing with prompts..." | ||||
|     fi | ||||
|   fi | ||||
| fi | ||||
|  | ||||
| # Helper function to detect OS type | ||||
| detect_os() { | ||||
|   if [ -f /etc/os-release ]; then | ||||
| @@ -141,8 +170,10 @@ if [ $PIPED -eq 1 ]; then | ||||
|     if [ $AUTO_YES -eq 1 ]; then | ||||
|       echo "Auto-installing git (-y flag provided)..." | ||||
|       install_git | ||||
|     else | ||||
|       read -p "Would you like to install git now? (y/N): " install_git_prompt | ||||
|     elif [ $INTERACTIVE -eq 1 ]; then | ||||
|       # If interactive and no -y flag, ask the user | ||||
|       echo "Would you like to install git now? (y/N): " | ||||
|       read -r install_git_prompt | ||||
|        | ||||
|       if [[ "$install_git_prompt" =~ ^[Yy]$ ]]; then | ||||
|         install_git | ||||
| @@ -152,6 +183,12 @@ if [ $PIPED -eq 1 ]; then | ||||
|         echo "  sudo bash install.sh -y" | ||||
|         exit 1 | ||||
|       fi | ||||
|     else | ||||
|       # Non-interactive mode without -y flag | ||||
|       echo "Error: Git is required but not installed." | ||||
|       echo "In non-interactive mode, use -y flag to auto-install dependencies:" | ||||
|       echo "  curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh | sudo bash -s -- -y" | ||||
|       exit 1 | ||||
|     fi | ||||
|   fi | ||||
|    | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "@serve.zone/nupst", | ||||
|   "version": "2.4.1", | ||||
|   "version": "2.4.5", | ||||
|   "description": "Node.js UPS Shutdown Tool for SNMP-enabled UPS devices", | ||||
|   "main": "dist/index.js", | ||||
|   "bin": { | ||||
|   | ||||
							
								
								
									
										11
									
								
								readme.md
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								readme.md
									
									
									
									
									
								
							| @@ -19,10 +19,15 @@ NUPST is a command-line tool that monitors SNMP-enabled UPS devices and initiate | ||||
| ### Quick Install (One-line command) | ||||
|  | ||||
| ```bash | ||||
| # Install directly without cloning the repository (requires root privileges) | ||||
| curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh | sudo bash | ||||
| # RECOMMENDED: Install interactively using process substitution (requires root privileges) | ||||
| # This ensures proper interactive prompts for dependencies | ||||
| sudo bash <(curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh) | ||||
|  | ||||
| # Install with auto-yes for dependencies (will install git automatically if needed) | ||||
| # Alternative: Install with auto-yes for dependencies (will install git automatically if needed) | ||||
| # Use this for automated/non-interactive installations | ||||
| sudo bash <(curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh) -y | ||||
|  | ||||
| # For systems where process substitution is not available (e.g., some older shells) | ||||
| curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh | sudo bash -s -- -y | ||||
| ``` | ||||
|  | ||||
|   | ||||
| @@ -3,6 +3,6 @@ | ||||
|  */ | ||||
| export const commitinfo = { | ||||
|   name: '@serve.zone/nupst', | ||||
|   version: '2.4.1', | ||||
|   version: '2.4.5', | ||||
|   description: 'Node.js UPS Shutdown Tool for SNMP-enabled UPS devices' | ||||
| } | ||||
|   | ||||
							
								
								
									
										88
									
								
								ts/daemon.ts
									
									
									
									
									
								
							
							
						
						
									
										88
									
								
								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<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 | ||||
|   | ||||
| @@ -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 | ||||
| } | ||||
		Reference in New Issue
	
	Block a user