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
# echo "Project root: $PROJECT_ROOT"
# Set Node.js binary path directly
NODE_BIN="$PROJECT_ROOT/vendor/node-linux-x64/bin/node"
# Detect architecture and OS
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 [ ! -f "$NODE_BIN" ]; then
if [ -z "$NODE_BIN" ] || [ ! -f "$NODE_BIN" ]; then
if command -v node &> /dev/null; then
NODE_BIN="node"
echo "Using system Node.js installation"
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."
exit 1
fi

View File

@ -1,5 +1,14 @@
# 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)
Fix Eaton UPS support by updating power status OID and adjusting battery runtime conversion.

View File

@ -37,9 +37,9 @@
"author": "",
"license": "MIT",
"dependencies": {
"net-snmp": "3.20.0"
},
"devDependencies": {
"net-snmp": "3.20.0",
"@git.zone/tsbuild": "^2.3.2",
"@git.zone/tsrun": "^1.3.3",
"@git.zone/tstest": "^1.0.96",

View File

@ -236,10 +236,10 @@ NUPST was designed with security in mind:
### 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:
- 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
- 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.
- **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.
### 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
- The installation script can be reviewed before execution (`curl -sSL [url] | less`)
- 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`)
- Automatically detects platform architecture and OS for proper binary selection
- Installs production dependencies locally without requiring global npm packages
### Audit and Review

View File

@ -222,6 +222,25 @@ echo "dist_ts directory successfully downloaded from npm registry."
# Make launcher script executable
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 "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"

View File

@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@serve.zone/nupst',
version: '2.5.1',
version: '2.5.2',
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
timeout: config.timeout,
transport: 'udp4',
idBitsSize: 32
idBitsSize: 32,
context: config.context || ''
};
// Set version based on config
if (config.version === 1) {
options.version = snmp.Version1;
} else if (config.version === 2 || config.version === 2) {
} else if (config.version === 2) {
options.version = snmp.Version2c;
} else {
options.version = snmp.Version3;
@ -122,15 +123,82 @@ export class NupstSnmp {
if (config.version === 3) {
// For SNMPv3, we need to set up authentication and privacy
const user = {
name: config.username || '',
level: snmp.SecurityLevel[config.securityLevel || 'noAuthNoPriv'],
authProtocol: config.authProtocol ? snmp.AuthProtocols[config.authProtocol] : undefined,
authKey: config.authKey || '',
privProtocol: config.privProtocol ? snmp.PrivProtocols[config.privProtocol] : undefined,
privKey: config.privKey || ''
// For SNMPv3, we need a valid security level
const securityLevel = config.securityLevel || 'noAuthNoPriv';
// Create the user object with required structure for net-snmp
const user: any = {
name: config.username || ''
};
// 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);
} else {
// For SNMPv1/v2c, we use the community string