Compare commits
	
		
			10 Commits
		
	
	
		
			v2.6.2
			...
			87005e72f1
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 87005e72f1 | |||
| f799c2ee66 | |||
| 1a029ba493 | |||
| 5b756dd223 | |||
| 4cac599a58 | |||
| be6a7314c3 | |||
| 83ba9c2611 | |||
| 22ab472e58 | |||
| 9a77030377 | |||
| ceff285ff5 | 
							
								
								
									
										36
									
								
								changelog.md
									
									
									
									
									
								
							
							
						
						
									
										36
									
								
								changelog.md
									
									
									
									
									
								
							@@ -1,5 +1,41 @@
 | 
			
		||||
# Changelog
 | 
			
		||||
 | 
			
		||||
## 2025-03-26 - 2.6.7 - fix(setup.sh)
 | 
			
		||||
Clarify net-snmp dependency installation message in setup.sh
 | 
			
		||||
 | 
			
		||||
- Updated echo statement to indicate installation of net-snmp along with 2 subdependencies
 | 
			
		||||
- Improves clarity on dependency installation during setup
 | 
			
		||||
 | 
			
		||||
## 2025-03-26 - 2.6.6 - fix(setup.sh)
 | 
			
		||||
Improve setup script to detect and execute npm-cli.js directly using the Node.js binary
 | 
			
		||||
 | 
			
		||||
- Replace use of the npm binary with direct execution of npm-cli.js
 | 
			
		||||
- Add fallback logic to locate npm-cli.js when not found at the expected path
 | 
			
		||||
- Simplify cleanup by removing unnecessary PATH modifications
 | 
			
		||||
 | 
			
		||||
## 2025-03-26 - 2.6.5 - fix(daemon, setup)
 | 
			
		||||
Improve shutdown command detection and fallback logic; update setup script to use absolute Node/npm paths
 | 
			
		||||
 | 
			
		||||
- Use execFileAsync to execute shutdown commands reliably
 | 
			
		||||
- Add multiple fallback alternatives for shutdown and emergency shutdown handling
 | 
			
		||||
- Update setup.sh to log the Node and NPM versions using absolute paths without modifying PATH
 | 
			
		||||
 | 
			
		||||
## 2025-03-26 - 2.6.4 - fix(setup)
 | 
			
		||||
Improve installation process in setup script by cleaning up package files and ensuring a minimal net-snmp dependency installation.
 | 
			
		||||
 | 
			
		||||
- Remove existing package-lock.json along with node_modules to prevent stale artifacts.
 | 
			
		||||
- Back up the original package.json before modifying it.
 | 
			
		||||
- Create a minimal package.json with only the net-snmp dependency based on the backed-up version.
 | 
			
		||||
- Use a clean install to guarantee that only net-snmp is installed.
 | 
			
		||||
- Restore the original package.json if the installation fails.
 | 
			
		||||
 | 
			
		||||
## 2025-03-26 - 2.6.3 - fix(setup)
 | 
			
		||||
Update setup script to install only net-snmp dependency and create a minimal package-lock.json for better dependency control.
 | 
			
		||||
 | 
			
		||||
- Removed full production dependency install in favor of installing only net-snmp@3.20.0
 | 
			
		||||
- Added verification step to confirm net-snmp installation
 | 
			
		||||
- Generate a minimal package-lock.json if one does not exist
 | 
			
		||||
 | 
			
		||||
## 2025-03-26 - 2.6.2 - fix(setup/readme)
 | 
			
		||||
Improve force update instructions and dependency installation process in setup.sh and readme.md
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "@serve.zone/nupst",
 | 
			
		||||
  "version": "2.6.2",
 | 
			
		||||
  "version": "2.6.7",
 | 
			
		||||
  "description": "Node.js UPS Shutdown Tool for SNMP-enabled UPS devices",
 | 
			
		||||
  "main": "dist/index.js",
 | 
			
		||||
  "bin": {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										98
									
								
								setup.sh
									
									
									
									
									
								
							
							
						
						
									
										98
									
								
								setup.sh
									
									
									
									
									
								
							@@ -239,33 +239,89 @@ echo "dist_ts directory successfully downloaded from npm registry."
 | 
			
		||||
# Make launcher script executable
 | 
			
		||||
chmod +x "$SCRIPT_DIR/bin/nupst"
 | 
			
		||||
 | 
			
		||||
# Add our Node.js bin directory to the PATH temporarily
 | 
			
		||||
# Set up Node.js binary path
 | 
			
		||||
NODE_BIN_DIR="$SCRIPT_DIR/vendor/$NODE_DIR/bin"
 | 
			
		||||
OLD_PATH="$PATH"
 | 
			
		||||
export PATH="$NODE_BIN_DIR:$PATH"
 | 
			
		||||
NODE_BIN="$NODE_BIN_DIR/node"
 | 
			
		||||
NPM_CLI_JS="$NODE_BIN_DIR/../lib/node_modules/npm/bin/npm-cli.js"
 | 
			
		||||
 | 
			
		||||
# Remove existing node_modules directory
 | 
			
		||||
echo "Removing existing node_modules directory..."
 | 
			
		||||
rm -rf "$SCRIPT_DIR/node_modules"
 | 
			
		||||
# Ensure we have executable permissions
 | 
			
		||||
chmod +x "$NODE_BIN"
 | 
			
		||||
 | 
			
		||||
# Install production dependencies
 | 
			
		||||
echo "Installing production dependencies..."
 | 
			
		||||
echo "Using Node.js binary from: $NODE_BIN_DIR"
 | 
			
		||||
echo "Node version: $(node --version)"
 | 
			
		||||
echo "NPM version: $(npm --version)"
 | 
			
		||||
# Make sure the npm-cli.js exists
 | 
			
		||||
if [ ! -f "$NPM_CLI_JS" ]; then
 | 
			
		||||
  # Try to find npm-cli.js
 | 
			
		||||
  NPM_CLI_JS=$(find "$NODE_BIN_DIR/.." -name "npm-cli.js" | head -1)
 | 
			
		||||
  
 | 
			
		||||
npm --prefix "$SCRIPT_DIR" install --omit=dev --no-audit --no-fund
 | 
			
		||||
 | 
			
		||||
if [ $? -ne 0 ]; then
 | 
			
		||||
  echo "Error: Failed to install dependencies. NUPST may not function correctly."
 | 
			
		||||
  echo "You can try to install dependencies manually by running:"
 | 
			
		||||
  echo "cd $SCRIPT_DIR && npm install --omit=dev"
 | 
			
		||||
else
 | 
			
		||||
  echo "Dependencies installed successfully."
 | 
			
		||||
  if [ -z "$NPM_CLI_JS" ]; then
 | 
			
		||||
    echo "Warning: Could not find npm-cli.js, npm commands may fail"
 | 
			
		||||
    # Set to a fallback value so code can continue
 | 
			
		||||
    NPM_CLI_JS="$NODE_BIN_DIR/npm"
 | 
			
		||||
  else
 | 
			
		||||
    echo "Found npm-cli.js at: $NPM_CLI_JS"
 | 
			
		||||
  fi
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# Restore the original PATH
 | 
			
		||||
export PATH="$OLD_PATH"
 | 
			
		||||
# Display which binaries we're using
 | 
			
		||||
echo "Using Node binary: $NODE_BIN"
 | 
			
		||||
echo "Using NPM CLI JS: $NPM_CLI_JS"
 | 
			
		||||
 | 
			
		||||
# Remove existing node_modules directory and package files
 | 
			
		||||
echo "Cleaning up existing installation..."
 | 
			
		||||
rm -rf "$SCRIPT_DIR/node_modules"
 | 
			
		||||
rm -f "$SCRIPT_DIR/package-lock.json"
 | 
			
		||||
 | 
			
		||||
# Back up existing package.json if it exists
 | 
			
		||||
if [ -f "$SCRIPT_DIR/package.json" ]; then
 | 
			
		||||
  echo "Backing up existing package.json..."
 | 
			
		||||
  cp "$SCRIPT_DIR/package.json" "$SCRIPT_DIR/package.json.bak"
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# Create a clean minimal package.json with ONLY net-snmp dependency
 | 
			
		||||
echo "Creating minimal package.json with only net-snmp dependency..."
 | 
			
		||||
VERSION=$(grep -o '"version": "[^"]*"' "$SCRIPT_DIR/package.json.bak" | head -1 | cut -d'"' -f4 || echo "2.6.3")
 | 
			
		||||
echo '{
 | 
			
		||||
  "name": "@serve.zone/nupst",
 | 
			
		||||
  "version": "'$VERSION'",
 | 
			
		||||
  "description": "Node.js UPS Shutdown Tool for SNMP-enabled UPS devices",
 | 
			
		||||
  "main": "dist_ts/index.js",
 | 
			
		||||
  "type": "module",
 | 
			
		||||
  "bin": {
 | 
			
		||||
    "nupst": "bin/nupst"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "net-snmp": "3.20.0"
 | 
			
		||||
  },
 | 
			
		||||
  "engines": {
 | 
			
		||||
    "node": ">=16.0.0"
 | 
			
		||||
  },
 | 
			
		||||
  "private": true
 | 
			
		||||
}' > "$SCRIPT_DIR/package.json"
 | 
			
		||||
 | 
			
		||||
# Install ONLY net-snmp
 | 
			
		||||
echo "Installing ONLY net-snmp dependency (+ 2 subdependencies)..."
 | 
			
		||||
echo "Node version: $("$NODE_BIN" --version)"
 | 
			
		||||
echo "Executing NPM directly with Node.js"
 | 
			
		||||
 | 
			
		||||
# Execute npm-cli.js directly with our Node.js binary
 | 
			
		||||
"$NODE_BIN" "$NPM_CLI_JS" --prefix "$SCRIPT_DIR" install --no-audit --no-fund
 | 
			
		||||
 | 
			
		||||
INSTALL_STATUS=$?
 | 
			
		||||
if [ $INSTALL_STATUS -ne 0 ]; then
 | 
			
		||||
  echo "Error: Failed to install net-snmp dependency. NUPST may not function correctly."
 | 
			
		||||
  echo "Restoring original package.json..."
 | 
			
		||||
  mv "$SCRIPT_DIR/package.json.bak" "$SCRIPT_DIR/package.json"
 | 
			
		||||
  exit 1
 | 
			
		||||
else
 | 
			
		||||
  echo "net-snmp dependency installed successfully."
 | 
			
		||||
  # Show what's actually installed
 | 
			
		||||
  echo "Installed modules:"
 | 
			
		||||
  find "$SCRIPT_DIR/node_modules" -maxdepth 1 -type d | grep -v "^$SCRIPT_DIR/node_modules$" | sort
 | 
			
		||||
  
 | 
			
		||||
  # Remove backup if successful
 | 
			
		||||
  rm -f "$SCRIPT_DIR/package.json.bak"
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# No temporary files to clean up
 | 
			
		||||
 | 
			
		||||
echo "NUPST setup completed successfully."
 | 
			
		||||
echo "You can now run NUPST using: $SCRIPT_DIR/bin/nupst"
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,6 @@
 | 
			
		||||
 */
 | 
			
		||||
export const commitinfo = {
 | 
			
		||||
  name: '@serve.zone/nupst',
 | 
			
		||||
  version: '2.6.2',
 | 
			
		||||
  version: '2.6.7',
 | 
			
		||||
  description: 'Node.js UPS Shutdown Tool for SNMP-enabled UPS devices'
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										168
									
								
								ts/daemon.ts
									
									
									
									
									
								
							
							
						
						
									
										168
									
								
								ts/daemon.ts
									
									
									
									
									
								
							@@ -1,11 +1,12 @@
 | 
			
		||||
import * as fs from 'fs';
 | 
			
		||||
import * as path from 'path';
 | 
			
		||||
import { exec } from 'child_process';
 | 
			
		||||
import { exec, execFile } from 'child_process';
 | 
			
		||||
import { promisify } from 'util';
 | 
			
		||||
import { NupstSnmp } from './snmp/manager.js';
 | 
			
		||||
import type { ISnmpConfig } from './snmp/types.js';
 | 
			
		||||
 | 
			
		||||
const execAsync = promisify(exec);
 | 
			
		||||
const execFileAsync = promisify(execFile);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Configuration interface for the daemon
 | 
			
		||||
@@ -298,24 +299,102 @@ export class NupstDaemon {
 | 
			
		||||
    const shutdownDelayMinutes = 5;
 | 
			
		||||
    
 | 
			
		||||
    try {
 | 
			
		||||
      // Find shutdown command in common system paths
 | 
			
		||||
      const shutdownPaths = [
 | 
			
		||||
        '/sbin/shutdown',
 | 
			
		||||
        '/usr/sbin/shutdown',
 | 
			
		||||
        '/bin/shutdown',
 | 
			
		||||
        '/usr/bin/shutdown'
 | 
			
		||||
      ];
 | 
			
		||||
      
 | 
			
		||||
      let shutdownCmd = '';
 | 
			
		||||
      for (const path of shutdownPaths) {
 | 
			
		||||
        try {
 | 
			
		||||
          if (fs.existsSync(path)) {
 | 
			
		||||
            shutdownCmd = path;
 | 
			
		||||
            console.log(`Found shutdown command at: ${shutdownCmd}`);
 | 
			
		||||
            break;
 | 
			
		||||
          }
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
          // Continue checking other paths
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      if (shutdownCmd) {
 | 
			
		||||
        // 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(`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`);
 | 
			
		||||
      } else {
 | 
			
		||||
        // Try using the PATH to find shutdown
 | 
			
		||||
        try {
 | 
			
		||||
          console.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);
 | 
			
		||||
        } 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...');
 | 
			
		||||
      await this.monitorDuringShutdown();
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      console.error('Failed to initiate shutdown:', error);
 | 
			
		||||
      // Try a different method if first one fails
 | 
			
		||||
      
 | 
			
		||||
      // Try alternative shutdown methods
 | 
			
		||||
      const alternatives = [
 | 
			
		||||
        { cmd: 'poweroff', args: ['--force'] },
 | 
			
		||||
        { cmd: 'halt', args: ['-p'] },
 | 
			
		||||
        { cmd: 'systemctl', args: ['poweroff'] },
 | 
			
		||||
        { cmd: 'reboot', args: ['-p'] } // Some systems allow reboot -p for power off
 | 
			
		||||
      ];
 | 
			
		||||
      
 | 
			
		||||
      for (const alt of alternatives) {
 | 
			
		||||
        try {
 | 
			
		||||
        console.log('Trying alternative shutdown method...');
 | 
			
		||||
        await execAsync('poweroff --force');
 | 
			
		||||
      } catch (innerError) {
 | 
			
		||||
        console.error('All shutdown methods failed:', innerError);
 | 
			
		||||
          // First check if command exists in common system paths
 | 
			
		||||
          const paths = [
 | 
			
		||||
            `/sbin/${alt.cmd}`,
 | 
			
		||||
            `/usr/sbin/${alt.cmd}`,
 | 
			
		||||
            `/bin/${alt.cmd}`,
 | 
			
		||||
            `/usr/bin/${alt.cmd}`
 | 
			
		||||
          ];
 | 
			
		||||
          
 | 
			
		||||
          let cmdPath = '';
 | 
			
		||||
          for (const path of paths) {
 | 
			
		||||
            if (fs.existsSync(path)) {
 | 
			
		||||
              cmdPath = path;
 | 
			
		||||
              break;
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
          
 | 
			
		||||
          if (cmdPath) {
 | 
			
		||||
            console.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(' ')}`);
 | 
			
		||||
            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);
 | 
			
		||||
          // Continue to next method
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      console.error('All shutdown methods failed');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  /**
 | 
			
		||||
@@ -346,10 +425,79 @@ export class NupstDaemon {
 | 
			
		||||
          console.log('└──────────────────────────────────────────┘');
 | 
			
		||||
          
 | 
			
		||||
          try {
 | 
			
		||||
            await execAsync('shutdown -h now "EMERGENCY: UPS battery critically low, shutting down NOW"');
 | 
			
		||||
            // Find shutdown command in common system paths
 | 
			
		||||
            const shutdownPaths = [
 | 
			
		||||
              '/sbin/shutdown',
 | 
			
		||||
              '/usr/sbin/shutdown',
 | 
			
		||||
              '/bin/shutdown',
 | 
			
		||||
              '/usr/bin/shutdown'
 | 
			
		||||
            ];
 | 
			
		||||
            
 | 
			
		||||
            let shutdownCmd = '';
 | 
			
		||||
            for (const path of shutdownPaths) {
 | 
			
		||||
              if (fs.existsSync(path)) {
 | 
			
		||||
                shutdownCmd = path;
 | 
			
		||||
                console.log(`Found shutdown command at: ${shutdownCmd}`);
 | 
			
		||||
                break;
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            if (shutdownCmd) {
 | 
			
		||||
              console.log(`Executing emergency shutdown: ${shutdownCmd} -h now`);
 | 
			
		||||
              await execFileAsync(shutdownCmd, ['-h', 'now', 'EMERGENCY: UPS battery critically low, shutting down NOW']);
 | 
			
		||||
            } else {
 | 
			
		||||
              // Try using the PATH to find shutdown
 | 
			
		||||
              console.log('Shutdown command not found in common paths, trying via PATH...');
 | 
			
		||||
              await execAsync('shutdown -h now "EMERGENCY: UPS battery critically low, shutting down NOW"', {
 | 
			
		||||
                env: process.env // Pass the current environment
 | 
			
		||||
              });
 | 
			
		||||
            }
 | 
			
		||||
          } catch (error) {
 | 
			
		||||
            console.error('Emergency shutdown failed, trying alternative method...');
 | 
			
		||||
            await execAsync('poweroff --force');
 | 
			
		||||
            console.error('Emergency shutdown failed, trying alternative methods...');
 | 
			
		||||
            
 | 
			
		||||
            // Try alternative shutdown methods in sequence
 | 
			
		||||
            const alternatives = [
 | 
			
		||||
              { cmd: 'poweroff', args: ['--force'] },
 | 
			
		||||
              { cmd: 'halt', args: ['-p'] },
 | 
			
		||||
              { cmd: 'systemctl', args: ['poweroff'] }
 | 
			
		||||
            ];
 | 
			
		||||
            
 | 
			
		||||
            for (const alt of alternatives) {
 | 
			
		||||
              try {
 | 
			
		||||
                // Check common paths
 | 
			
		||||
                const paths = [
 | 
			
		||||
                  `/sbin/${alt.cmd}`,
 | 
			
		||||
                  `/usr/sbin/${alt.cmd}`,
 | 
			
		||||
                  `/bin/${alt.cmd}`,
 | 
			
		||||
                  `/usr/bin/${alt.cmd}`
 | 
			
		||||
                ];
 | 
			
		||||
                
 | 
			
		||||
                let cmdPath = '';
 | 
			
		||||
                for (const path of paths) {
 | 
			
		||||
                  if (fs.existsSync(path)) {
 | 
			
		||||
                    cmdPath = path;
 | 
			
		||||
                    break;
 | 
			
		||||
                  }
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                if (cmdPath) {
 | 
			
		||||
                  console.log(`Emergency: using ${cmdPath} ${alt.args.join(' ')}`);
 | 
			
		||||
                  await execFileAsync(cmdPath, alt.args);
 | 
			
		||||
                  return; // Exit if successful
 | 
			
		||||
                } else {
 | 
			
		||||
                  // Try using PATH
 | 
			
		||||
                  console.log(`Emergency: trying ${alt.cmd} via PATH`);
 | 
			
		||||
                  await execAsync(`${alt.cmd} ${alt.args.join(' ')}`, {
 | 
			
		||||
                    env: process.env
 | 
			
		||||
                  });
 | 
			
		||||
                  return; // Exit if successful
 | 
			
		||||
                }
 | 
			
		||||
              } catch (altError) {
 | 
			
		||||
                // Continue to next method
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            console.error('All emergency shutdown methods failed');
 | 
			
		||||
          }
 | 
			
		||||
          
 | 
			
		||||
          // Stop monitoring after initiating emergency shutdown
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user