fix(installer): Improve Node.js binary detection, dependency management, and SNMPv3 fallback logic
This commit is contained in:
		
							
								
								
									
										55
									
								
								bin/nupst
									
									
									
									
									
								
							
							
						
						
									
										55
									
								
								bin/nupst
									
									
									
									
									
								
							| @@ -22,16 +22,63 @@ fi | |||||||
| # For debugging | # For debugging | ||||||
| # echo "Project root: $PROJECT_ROOT" | # echo "Project root: $PROJECT_ROOT" | ||||||
|  |  | ||||||
| # Set Node.js binary path directly | # Detect architecture and OS | ||||||
| NODE_BIN="$PROJECT_ROOT/vendor/node-linux-x64/bin/node" | ARCH=$(uname -m) | ||||||
|  | OS=$(uname -s) | ||||||
|  |  | ||||||
|  | # Determine Node.js binary location based on architecture and OS | ||||||
|  | NODE_BIN="" | ||||||
|  | case "$OS" in | ||||||
|  |   Linux) | ||||||
|  |     case "$ARCH" in | ||||||
|  |       x86_64) | ||||||
|  |         NODE_BIN="$PROJECT_ROOT/vendor/node-linux-x64/bin/node" | ||||||
|  |         ;; | ||||||
|  |       aarch64|arm64) | ||||||
|  |         NODE_BIN="$PROJECT_ROOT/vendor/node-linux-arm64/bin/node" | ||||||
|  |         ;; | ||||||
|  |       *) | ||||||
|  |         # Use system Node as fallback for other architectures | ||||||
|  |         if command -v node &> /dev/null; then | ||||||
|  |           NODE_BIN="node" | ||||||
|  |           echo "Using system Node.js installation for unsupported architecture: $ARCH" | ||||||
|  |         fi | ||||||
|  |         ;; | ||||||
|  |     esac | ||||||
|  |     ;; | ||||||
|  |   Darwin) | ||||||
|  |     case "$ARCH" in | ||||||
|  |       x86_64) | ||||||
|  |         NODE_BIN="$PROJECT_ROOT/vendor/node-darwin-x64/bin/node" | ||||||
|  |         ;; | ||||||
|  |       arm64) | ||||||
|  |         NODE_BIN="$PROJECT_ROOT/vendor/node-darwin-arm64/bin/node" | ||||||
|  |         ;; | ||||||
|  |       *) | ||||||
|  |         # Use system Node as fallback for other architectures | ||||||
|  |         if command -v node &> /dev/null; then | ||||||
|  |           NODE_BIN="node" | ||||||
|  |           echo "Using system Node.js installation for unsupported architecture: $ARCH" | ||||||
|  |         fi | ||||||
|  |         ;; | ||||||
|  |     esac | ||||||
|  |     ;; | ||||||
|  |   *) | ||||||
|  |     # Use system Node as fallback for other operating systems | ||||||
|  |     if command -v node &> /dev/null; then | ||||||
|  |       NODE_BIN="node" | ||||||
|  |       echo "Using system Node.js installation for unsupported OS: $OS" | ||||||
|  |     fi | ||||||
|  |     ;; | ||||||
|  | esac | ||||||
|  |  | ||||||
| # If binary doesn't exist, try system Node as fallback | # If binary doesn't exist, try system Node as fallback | ||||||
| if [ ! -f "$NODE_BIN" ]; then | if [ -z "$NODE_BIN" ] || [ ! -f "$NODE_BIN" ]; then | ||||||
|   if command -v node &> /dev/null; then |   if command -v node &> /dev/null; then | ||||||
|     NODE_BIN="node" |     NODE_BIN="node" | ||||||
|     echo "Using system Node.js installation" |     echo "Using system Node.js installation" | ||||||
|   else   |   else   | ||||||
|     echo "Error: Node.js binary not found at $NODE_BIN" |     echo "Error: Node.js binary not found for $OS-$ARCH" | ||||||
|     echo "Please run the setup script or install Node.js manually." |     echo "Please run the setup script or install Node.js manually." | ||||||
|     exit 1 |     exit 1 | ||||||
|   fi |   fi | ||||||
|   | |||||||
| @@ -1,5 +1,14 @@ | |||||||
| # Changelog | # Changelog | ||||||
|  |  | ||||||
|  | ## 2025-03-26 - 2.5.2 - fix(installer) | ||||||
|  | Improve Node.js binary detection, dependency management, and SNMPv3 fallback logic | ||||||
|  |  | ||||||
|  | - Enhanced bin/nupst to detect OS and architecture (Linux and Darwin) and fall back to system Node.js for unsupported platforms | ||||||
|  | - Moved net-snmp from devDependencies to dependencies in package.json | ||||||
|  | - Updated setup.sh to install production dependencies and handle installation errors gracefully | ||||||
|  | - Refined SNMPv3 user configuration and fallback mechanism in ts/snmp/manager.ts | ||||||
|  | - Revised README to clarify minimal runtime dependencies and secure SNMP features | ||||||
|  |  | ||||||
| ## 2025-03-25 - 2.5.1 - fix(snmp) | ## 2025-03-25 - 2.5.1 - fix(snmp) | ||||||
| Fix Eaton UPS support by updating power status OID and adjusting battery runtime conversion. | Fix Eaton UPS support by updating power status OID and adjusting battery runtime conversion. | ||||||
|  |  | ||||||
|   | |||||||
| @@ -37,9 +37,9 @@ | |||||||
|   "author": "", |   "author": "", | ||||||
|   "license": "MIT", |   "license": "MIT", | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|  |     "net-snmp": "3.20.0" | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "net-snmp": "3.20.0", |  | ||||||
|     "@git.zone/tsbuild": "^2.3.2", |     "@git.zone/tsbuild": "^2.3.2", | ||||||
|     "@git.zone/tsrun": "^1.3.3", |     "@git.zone/tsrun": "^1.3.3", | ||||||
|     "@git.zone/tstest": "^1.0.96", |     "@git.zone/tstest": "^1.0.96", | ||||||
|   | |||||||
							
								
								
									
										22
									
								
								readme.md
									
									
									
									
									
								
							
							
						
						
									
										22
									
								
								readme.md
									
									
									
									
									
								
							| @@ -236,10 +236,10 @@ NUPST was designed with security in mind: | |||||||
|  |  | ||||||
| ### Minimal Dependencies | ### Minimal Dependencies | ||||||
|  |  | ||||||
| - **Zero Runtime NPM Dependencies**: NUPST is built without any external NPM packages to minimize the attack surface and avoid supply chain risks. | - **Minimal Runtime Dependencies**: NUPST uses only one carefully selected NPM package (net-snmp) to minimize the attack surface and avoid supply chain risks while providing robust SNMP functionality. | ||||||
| - **Self-contained Node.js**: NUPST ships with its own Node.js binary, isolated from the system's Node.js installation. This ensures: | - **Self-contained Node.js**: NUPST ships with its own Node.js binary, isolated from the system's Node.js installation. This ensures: | ||||||
|   - No dependency on system Node.js versions |   - No dependency on system Node.js versions | ||||||
|   - Zero external libraries that could become compromised |   - Minimal external libraries that could become compromised | ||||||
|   - Consistent, tested environment for execution |   - Consistent, tested environment for execution | ||||||
|   - Reduced risk of dependency-based attacks |   - Reduced risk of dependency-based attacks | ||||||
|  |  | ||||||
| @@ -247,14 +247,30 @@ NUPST was designed with security in mind: | |||||||
|  |  | ||||||
| - **Privilege Separation**: Only specific commands that require elevated permissions (`enable`, `disable`, `update`) check for root access; all other functionality runs with minimal privileges. | - **Privilege Separation**: Only specific commands that require elevated permissions (`enable`, `disable`, `update`) check for root access; all other functionality runs with minimal privileges. | ||||||
| - **Limited Network Access**: NUPST only communicates with the UPS device over SNMP and contacts npmjs.org only to check for updates. | - **Limited Network Access**: NUPST only communicates with the UPS device over SNMP and contacts npmjs.org only to check for updates. | ||||||
| - **Secure SNMPv3 Support**: Supports encrypted authentication and privacy for secure communication with the UPS device. |  | ||||||
| - **Isolated Execution**: The application runs in its working directory (`/opt/nupst`) or specified installation location, minimizing the impact on the rest of the system. | - **Isolated Execution**: The application runs in its working directory (`/opt/nupst`) or specified installation location, minimizing the impact on the rest of the system. | ||||||
|  |  | ||||||
|  | ### SNMP Security Features | ||||||
|  |  | ||||||
|  | - **SNMPv3 Support with Secure Authentication and Privacy**: | ||||||
|  |   - Three security levels available: | ||||||
|  |     - `noAuthNoPriv`: No authentication or encryption (basic access) | ||||||
|  |     - `authNoPriv`: Authentication without encryption (verifies identity) | ||||||
|  |     - `authPriv`: Full authentication and encryption (most secure) | ||||||
|  |   - Authentication protocols: MD5 or SHA | ||||||
|  |   - Privacy/encryption protocols: DES or AES | ||||||
|  |   - Automatic fallback mechanisms for compatibility | ||||||
|  |   - Context support for segmented SNMP deployments | ||||||
|  |   - Configurable timeouts based on security level | ||||||
|  | - **Graceful degradation**: If authentication or privacy details are missing or invalid, NUPST will automatically fall back to a lower security level while logging appropriate warnings. | ||||||
|  | - **Interactive setup**: Guided setup process to properly configure SNMPv3 security settings with clear explanations of each security option. | ||||||
|  |  | ||||||
| ### Installation Security | ### Installation Security | ||||||
|  |  | ||||||
| - The installation script can be reviewed before execution (`curl -sSL [url] | less`) | - The installation script can be reviewed before execution (`curl -sSL [url] | less`) | ||||||
| - All setup scripts download only verified versions and check integrity | - All setup scripts download only verified versions and check integrity | ||||||
| - Installation is transparent and places files in standard locations (`/opt/nupst`, `/usr/local/bin`, `/etc/systemd/system`) | - Installation is transparent and places files in standard locations (`/opt/nupst`, `/usr/local/bin`, `/etc/systemd/system`) | ||||||
|  | - Automatically detects platform architecture and OS for proper binary selection | ||||||
|  | - Installs production dependencies locally without requiring global npm packages | ||||||
|  |  | ||||||
| ### Audit and Review | ### Audit and Review | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										19
									
								
								setup.sh
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								setup.sh
									
									
									
									
									
								
							| @@ -222,6 +222,25 @@ echo "dist_ts directory successfully downloaded from npm registry." | |||||||
| # Make launcher script executable | # Make launcher script executable | ||||||
| chmod +x "$SCRIPT_DIR/bin/nupst" | chmod +x "$SCRIPT_DIR/bin/nupst" | ||||||
|  |  | ||||||
|  | # Install production dependencies | ||||||
|  | echo "Installing production dependencies..." | ||||||
|  | "$SCRIPT_DIR/vendor/$NODE_DIR/bin/npm" --prefix "$SCRIPT_DIR" ci --only=production --no-audit --no-fund | ||||||
|  |  | ||||||
|  | if [ $? -ne 0 ]; then | ||||||
|  |   echo "Warning: Failed to install dependencies with 'npm ci'. Trying 'npm install'..." | ||||||
|  |   "$SCRIPT_DIR/vendor/$NODE_DIR/bin/npm" --prefix "$SCRIPT_DIR" install --only=production --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 --only=production" | ||||||
|  |   else | ||||||
|  |     echo "Dependencies installed successfully with 'npm install'." | ||||||
|  |   fi | ||||||
|  | else | ||||||
|  |   echo "Dependencies installed successfully with 'npm ci'." | ||||||
|  | fi | ||||||
|  |  | ||||||
| echo "NUPST setup completed successfully." | echo "NUPST setup completed successfully." | ||||||
| echo "You can now run NUPST using: $SCRIPT_DIR/bin/nupst" | echo "You can now run NUPST using: $SCRIPT_DIR/bin/nupst" | ||||||
| echo "To install NUPST globally, run: sudo ln -s $SCRIPT_DIR/bin/nupst /usr/local/bin/nupst" | echo "To install NUPST globally, run: sudo ln -s $SCRIPT_DIR/bin/nupst /usr/local/bin/nupst" | ||||||
|   | |||||||
| @@ -3,6 +3,6 @@ | |||||||
|  */ |  */ | ||||||
| export const commitinfo = { | export const commitinfo = { | ||||||
|   name: '@serve.zone/nupst', |   name: '@serve.zone/nupst', | ||||||
|   version: '2.5.1', |   version: '2.5.2', | ||||||
|   description: 'Node.js UPS Shutdown Tool for SNMP-enabled UPS devices' |   description: 'Node.js UPS Shutdown Tool for SNMP-enabled UPS devices' | ||||||
| } | } | ||||||
|   | |||||||
| @@ -105,13 +105,14 @@ export class NupstSnmp { | |||||||
|         retries: 2, // Number of retries |         retries: 2, // Number of retries | ||||||
|         timeout: config.timeout, |         timeout: config.timeout, | ||||||
|         transport: 'udp4', |         transport: 'udp4', | ||||||
|         idBitsSize: 32 |         idBitsSize: 32, | ||||||
|  |         context: config.context || '' | ||||||
|       }; |       }; | ||||||
|  |  | ||||||
|       // Set version based on config |       // Set version based on config | ||||||
|       if (config.version === 1) { |       if (config.version === 1) { | ||||||
|         options.version = snmp.Version1; |         options.version = snmp.Version1; | ||||||
|       } else if (config.version === 2 || config.version === 2) { |       } else if (config.version === 2) { | ||||||
|         options.version = snmp.Version2c; |         options.version = snmp.Version2c; | ||||||
|       } else { |       } else { | ||||||
|         options.version = snmp.Version3; |         options.version = snmp.Version3; | ||||||
| @@ -122,15 +123,82 @@ export class NupstSnmp { | |||||||
|        |        | ||||||
|       if (config.version === 3) { |       if (config.version === 3) { | ||||||
|         // For SNMPv3, we need to set up authentication and privacy |         // For SNMPv3, we need to set up authentication and privacy | ||||||
|         const user = { |         // For SNMPv3, we need a valid security level | ||||||
|           name: config.username || '', |         const securityLevel = config.securityLevel || 'noAuthNoPriv'; | ||||||
|           level: snmp.SecurityLevel[config.securityLevel || 'noAuthNoPriv'], |          | ||||||
|           authProtocol: config.authProtocol ? snmp.AuthProtocols[config.authProtocol] : undefined, |         // Create the user object with required structure for net-snmp | ||||||
|           authKey: config.authKey || '', |         const user: any = { | ||||||
|           privProtocol: config.privProtocol ? snmp.PrivProtocols[config.privProtocol] : undefined, |           name: config.username || '' | ||||||
|           privKey: config.privKey || '' |  | ||||||
|         }; |         }; | ||||||
|          |          | ||||||
|  |         // Set security level | ||||||
|  |         if (securityLevel === 'noAuthNoPriv') { | ||||||
|  |           user.level = snmp.SecurityLevel.noAuthNoPriv; | ||||||
|  |         } else if (securityLevel === 'authNoPriv') { | ||||||
|  |           user.level = snmp.SecurityLevel.authNoPriv; | ||||||
|  |            | ||||||
|  |           // Set auth protocol - must provide both protocol and key | ||||||
|  |           if (config.authProtocol && config.authKey) { | ||||||
|  |             if (config.authProtocol === 'MD5') { | ||||||
|  |               user.authProtocol = snmp.AuthProtocols.md5; | ||||||
|  |             } else if (config.authProtocol === 'SHA') { | ||||||
|  |               user.authProtocol = snmp.AuthProtocols.sha; | ||||||
|  |             } | ||||||
|  |             user.authKey = config.authKey; | ||||||
|  |           } else { | ||||||
|  |             // Fallback to noAuthNoPriv if auth details missing | ||||||
|  |             user.level = snmp.SecurityLevel.noAuthNoPriv; | ||||||
|  |             if (this.debug) { | ||||||
|  |               console.log('Warning: Missing authProtocol or authKey, falling back to noAuthNoPriv'); | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } else if (securityLevel === 'authPriv') { | ||||||
|  |           user.level = snmp.SecurityLevel.authPriv; | ||||||
|  |            | ||||||
|  |           // Set auth protocol - must provide both protocol and key | ||||||
|  |           if (config.authProtocol && config.authKey) { | ||||||
|  |             if (config.authProtocol === 'MD5') { | ||||||
|  |               user.authProtocol = snmp.AuthProtocols.md5; | ||||||
|  |             } else if (config.authProtocol === 'SHA') { | ||||||
|  |               user.authProtocol = snmp.AuthProtocols.sha; | ||||||
|  |             } | ||||||
|  |             user.authKey = config.authKey; | ||||||
|  |              | ||||||
|  |             // Set privacy protocol - must provide both protocol and key | ||||||
|  |             if (config.privProtocol && config.privKey) { | ||||||
|  |               if (config.privProtocol === 'DES') { | ||||||
|  |                 user.privProtocol = snmp.PrivProtocols.des; | ||||||
|  |               } else if (config.privProtocol === 'AES') { | ||||||
|  |                 user.privProtocol = snmp.PrivProtocols.aes; | ||||||
|  |               } | ||||||
|  |               user.privKey = config.privKey; | ||||||
|  |             } else { | ||||||
|  |               // Fallback to authNoPriv if priv details missing | ||||||
|  |               user.level = snmp.SecurityLevel.authNoPriv; | ||||||
|  |               if (this.debug) { | ||||||
|  |                 console.log('Warning: Missing privProtocol or privKey, falling back to authNoPriv'); | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } else { | ||||||
|  |             // Fallback to noAuthNoPriv if auth details missing | ||||||
|  |             user.level = snmp.SecurityLevel.noAuthNoPriv; | ||||||
|  |             if (this.debug) { | ||||||
|  |               console.log('Warning: Missing authProtocol or authKey, falling back to noAuthNoPriv'); | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         if (this.debug) { | ||||||
|  |           console.log('SNMPv3 user configuration:', { | ||||||
|  |             name: user.name, | ||||||
|  |             level: Object.keys(snmp.SecurityLevel).find(key => snmp.SecurityLevel[key] === user.level), | ||||||
|  |             authProtocol: user.authProtocol ? 'Set' : 'Not Set', | ||||||
|  |             authKey: user.authKey ? 'Set' : 'Not Set', | ||||||
|  |             privProtocol: user.privProtocol ? 'Set' : 'Not Set', | ||||||
|  |             privKey: user.privKey ? 'Set' : 'Not Set' | ||||||
|  |           }); | ||||||
|  |         } | ||||||
|  |          | ||||||
|         session = snmp.createV3Session(config.host, user, options); |         session = snmp.createV3Session(config.host, user, options); | ||||||
|       } else { |       } else { | ||||||
|         // For SNMPv1/v2c, we use the community string |         // For SNMPv1/v2c, we use the community string | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user