fix(installer): Improve Node.js binary detection, dependency management, and SNMPv3 fallback logic

This commit is contained in:
Philipp Kunz 2025-03-26 13:27:47 +00:00
parent 5a13e49803
commit 4de6081a74
7 changed files with 177 additions and 18 deletions

View File

@ -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

View File

@ -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.

View File

@ -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",

View File

@ -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

View File

@ -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"

View File

@ -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'
} }

View File

@ -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