Compare commits
4 Commits
Author | SHA1 | Date | |
---|---|---|---|
c7db209da7 | |||
bbb8f4a22c | |||
ebc6f65fa9 | |||
0a459f9cd0 |
13
changelog.md
13
changelog.md
@ -1,5 +1,18 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2025-03-25 - 2.2.0 - feat(cli)
|
||||||
|
Add 'config' command to display current configuration and update CLI help
|
||||||
|
|
||||||
|
- Introduce new 'config' command to show SNMP settings, thresholds, and configuration file location
|
||||||
|
- Update help text to include details for 'nupst config' command
|
||||||
|
|
||||||
|
## 2025-03-25 - 2.1.0 - feat(cli)
|
||||||
|
Add uninstall command to CLI and update shutdown delay for graceful VM shutdown
|
||||||
|
|
||||||
|
- Implement uninstall command in ts/cli.ts that locates and executes uninstall.sh with user prompts
|
||||||
|
- Update uninstall.sh to support environment variables for configuration and repository removal
|
||||||
|
- Increase shutdown delay in ts/snmp/manager.ts from 1 minute to 5 minutes to allow VMs more time to shut down
|
||||||
|
|
||||||
## 2025-03-25 - 2.0.1 - fix(cli/systemd)
|
## 2025-03-25 - 2.0.1 - fix(cli/systemd)
|
||||||
Fix status command to pass debug flag and improve systemd status logging output
|
Fix status command to pass debug flag and improve systemd status logging output
|
||||||
|
|
||||||
|
21
license
Normal file
21
license
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2016 Task Venture Capital GmbH
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@serve.zone/nupst",
|
"name": "@serve.zone/nupst",
|
||||||
"version": "2.0.1",
|
"version": "2.2.0",
|
||||||
"description": "Node.js UPS Shutdown Tool for SNMP-enabled UPS devices",
|
"description": "Node.js UPS Shutdown Tool for SNMP-enabled UPS devices",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@serve.zone/nupst',
|
name: '@serve.zone/nupst',
|
||||||
version: '2.0.1',
|
version: '2.2.0',
|
||||||
description: 'Node.js UPS Shutdown Tool for SNMP-enabled UPS devices'
|
description: 'Node.js UPS Shutdown Tool for SNMP-enabled UPS devices'
|
||||||
}
|
}
|
||||||
|
194
ts/cli.ts
194
ts/cli.ts
@ -1,4 +1,7 @@
|
|||||||
import { execSync } from 'child_process';
|
import { execSync } from 'child_process';
|
||||||
|
import { promises as fs } from 'fs';
|
||||||
|
import { dirname, join } from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
import { Nupst } from './nupst.js';
|
import { Nupst } from './nupst.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -94,6 +97,14 @@ export class NupstCli {
|
|||||||
case 'update':
|
case 'update':
|
||||||
await this.update();
|
await this.update();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'uninstall':
|
||||||
|
await this.uninstall();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'config':
|
||||||
|
await this.showConfig();
|
||||||
|
break;
|
||||||
|
|
||||||
case 'help':
|
case 'help':
|
||||||
default:
|
default:
|
||||||
@ -365,7 +376,9 @@ Usage:
|
|||||||
nupst status - Show status of the systemd service and UPS status
|
nupst status - Show status of the systemd service and UPS status
|
||||||
nupst setup - Run the interactive setup to configure SNMP settings
|
nupst setup - Run the interactive setup to configure SNMP settings
|
||||||
nupst test - Test the current configuration by connecting to the UPS
|
nupst test - Test the current configuration by connecting to the UPS
|
||||||
|
nupst config - Display the current configuration
|
||||||
nupst update - Update NUPST from repository and refresh systemd service (requires root)
|
nupst update - Update NUPST from repository and refresh systemd service (requires root)
|
||||||
|
nupst uninstall - Completely uninstall NUPST from the system (requires root)
|
||||||
nupst help - Show this help message
|
nupst help - Show this help message
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
@ -835,4 +848,185 @@ Options:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the current configuration
|
||||||
|
*/
|
||||||
|
private async showConfig(): Promise<void> {
|
||||||
|
try {
|
||||||
|
// Try to load configuration
|
||||||
|
try {
|
||||||
|
await this.nupst.getDaemon().loadConfig();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('┌─ Configuration Error ─────────────────────┐');
|
||||||
|
console.error('│ No configuration found.');
|
||||||
|
console.error('│ Please run \'nupst setup\' first to create a configuration.');
|
||||||
|
console.error('└──────────────────────────────────────────┘');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current configuration
|
||||||
|
const config = this.nupst.getDaemon().getConfig();
|
||||||
|
|
||||||
|
console.log('┌─ NUPST Configuration ──────────────────────┐');
|
||||||
|
|
||||||
|
// SNMP Settings
|
||||||
|
console.log('│ SNMP Settings:');
|
||||||
|
console.log(`│ Host: ${config.snmp.host}`);
|
||||||
|
console.log(`│ Port: ${config.snmp.port}`);
|
||||||
|
console.log(`│ Version: ${config.snmp.version}`);
|
||||||
|
console.log(`│ UPS Model: ${config.snmp.upsModel || 'cyberpower'}`);
|
||||||
|
|
||||||
|
if (config.snmp.version === 1 || config.snmp.version === 2) {
|
||||||
|
console.log(`│ Community: ${config.snmp.community}`);
|
||||||
|
} else if (config.snmp.version === 3) {
|
||||||
|
console.log(`│ Security Level: ${config.snmp.securityLevel}`);
|
||||||
|
console.log(`│ Username: ${config.snmp.username}`);
|
||||||
|
|
||||||
|
// Show auth and privacy details based on security level
|
||||||
|
if (config.snmp.securityLevel === 'authNoPriv' || config.snmp.securityLevel === 'authPriv') {
|
||||||
|
console.log(`│ Auth Protocol: ${config.snmp.authProtocol || 'None'}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.snmp.securityLevel === 'authPriv') {
|
||||||
|
console.log(`│ Privacy Protocol: ${config.snmp.privProtocol || 'None'}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show timeout value
|
||||||
|
console.log(`│ Timeout: ${config.snmp.timeout / 1000} seconds`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show OIDs if custom model is selected
|
||||||
|
if (config.snmp.upsModel === 'custom' && config.snmp.customOIDs) {
|
||||||
|
console.log('│ Custom OIDs:');
|
||||||
|
console.log(`│ Power Status: ${config.snmp.customOIDs.POWER_STATUS || 'Not set'}`);
|
||||||
|
console.log(`│ Battery Capacity: ${config.snmp.customOIDs.BATTERY_CAPACITY || 'Not set'}`);
|
||||||
|
console.log(`│ Battery Runtime: ${config.snmp.customOIDs.BATTERY_RUNTIME || 'Not set'}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Thresholds
|
||||||
|
console.log('│ Thresholds:');
|
||||||
|
console.log(`│ Battery: ${config.thresholds.battery}%`);
|
||||||
|
console.log(`│ Runtime: ${config.thresholds.runtime} minutes`);
|
||||||
|
console.log(`│ Check Interval: ${config.checkInterval / 1000} seconds`);
|
||||||
|
|
||||||
|
// Configuration file location
|
||||||
|
console.log('│');
|
||||||
|
console.log('│ Configuration File Location:');
|
||||||
|
console.log('│ /etc/nupst/config.json');
|
||||||
|
|
||||||
|
console.log('└──────────────────────────────────────────┘');
|
||||||
|
|
||||||
|
// Show service status
|
||||||
|
try {
|
||||||
|
const isActive = execSync('systemctl is-active nupst.service || true').toString().trim() === 'active';
|
||||||
|
const isEnabled = execSync('systemctl is-enabled nupst.service || true').toString().trim() === 'enabled';
|
||||||
|
|
||||||
|
console.log('┌─ Service Status ─────────────────────────┐');
|
||||||
|
console.log(`│ Service Active: ${isActive ? 'Yes' : 'No'}`);
|
||||||
|
console.log(`│ Service Enabled: ${isEnabled ? 'Yes' : 'No'}`);
|
||||||
|
console.log('└──────────────────────────────────────────┘');
|
||||||
|
} catch (error) {
|
||||||
|
// Ignore errors checking service status
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to display configuration: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Completely uninstall NUPST from the system
|
||||||
|
*/
|
||||||
|
private async uninstall(): Promise<void> {
|
||||||
|
// Check if running as root
|
||||||
|
this.checkRootAccess('This command must be run as root.');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Import readline module for user input
|
||||||
|
const readline = await import('readline');
|
||||||
|
|
||||||
|
const rl = readline.createInterface({
|
||||||
|
input: process.stdin,
|
||||||
|
output: process.stdout
|
||||||
|
});
|
||||||
|
|
||||||
|
// Helper function to prompt for input
|
||||||
|
const prompt = (question: string): Promise<string> => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
rl.question(question, (answer: string) => {
|
||||||
|
resolve(answer);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('\nNUPST Uninstaller');
|
||||||
|
console.log('===============');
|
||||||
|
console.log('This will completely remove NUPST from your system.\n');
|
||||||
|
|
||||||
|
// Ask about removing configuration
|
||||||
|
const removeConfig = await prompt('Do you want to remove the NUPST configuration files? (y/N): ');
|
||||||
|
|
||||||
|
// Find the uninstall.sh script location
|
||||||
|
let uninstallScriptPath: string;
|
||||||
|
|
||||||
|
// Try to determine script location based on executable path
|
||||||
|
try {
|
||||||
|
// For ESM, we can use import.meta.url, but since we might be in CJS
|
||||||
|
// we'll use a more reliable approach based on process.argv[1]
|
||||||
|
const binPath = process.argv[1];
|
||||||
|
const modulePath = dirname(dirname(binPath));
|
||||||
|
uninstallScriptPath = join(modulePath, 'uninstall.sh');
|
||||||
|
|
||||||
|
// Check if the script exists
|
||||||
|
await fs.access(uninstallScriptPath);
|
||||||
|
} catch (error) {
|
||||||
|
// If we can't find it in the expected location, try common installation paths
|
||||||
|
const commonPaths = [
|
||||||
|
'/opt/nupst/uninstall.sh',
|
||||||
|
join(process.cwd(), 'uninstall.sh')
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const path of commonPaths) {
|
||||||
|
try {
|
||||||
|
await fs.access(path);
|
||||||
|
uninstallScriptPath = path;
|
||||||
|
break;
|
||||||
|
} catch {
|
||||||
|
// Continue to next path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!uninstallScriptPath) {
|
||||||
|
console.error('Could not locate uninstall.sh script. Aborting uninstall.');
|
||||||
|
rl.close();
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close readline before executing script
|
||||||
|
rl.close();
|
||||||
|
|
||||||
|
// Execute uninstall.sh with the appropriate option
|
||||||
|
console.log(`\nRunning uninstaller from ${uninstallScriptPath}...`);
|
||||||
|
|
||||||
|
// Pass the configuration removal option as an environment variable
|
||||||
|
const env = {
|
||||||
|
...process.env,
|
||||||
|
REMOVE_CONFIG: removeConfig.toLowerCase() === 'y' ? 'yes' : 'no',
|
||||||
|
REMOVE_REPO: 'yes', // Always remove repo as requested
|
||||||
|
NUPST_CLI_CALL: 'true' // Flag to indicate this is being called from CLI
|
||||||
|
};
|
||||||
|
|
||||||
|
// Run the uninstall script with sudo
|
||||||
|
execSync(`sudo bash ${uninstallScriptPath}`, {
|
||||||
|
env,
|
||||||
|
stdio: 'inherit' // Show output in the terminal
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Uninstall failed: ${error.message}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -514,9 +514,10 @@ export class NupstSnmp {
|
|||||||
public async initiateShutdown(reason: string): Promise<void> {
|
public async initiateShutdown(reason: string): Promise<void> {
|
||||||
console.log(`Initiating system shutdown due to: ${reason}`);
|
console.log(`Initiating system shutdown due to: ${reason}`);
|
||||||
try {
|
try {
|
||||||
// Execute shutdown command
|
// Execute shutdown command with 5 minute delay to allow for VM graceful shutdown
|
||||||
const { stdout } = await execAsync('shutdown -h +1 "UPS battery critical, shutting down in 1 minute"');
|
const { stdout } = await execAsync('shutdown -h +5 "UPS battery critical, shutting down in 5 minutes"');
|
||||||
console.log('Shutdown initiated:', stdout);
|
console.log('Shutdown initiated:', stdout);
|
||||||
|
console.log('Allowing 5 minutes for VMs to shut down safely');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to initiate shutdown:', error);
|
console.error('Failed to initiate shutdown:', error);
|
||||||
// Try a different method if first one fails
|
// Try a different method if first one fails
|
||||||
|
65
uninstall.sh
65
uninstall.sh
@ -5,13 +5,22 @@
|
|||||||
|
|
||||||
# Check if running as root
|
# Check if running as root
|
||||||
if [ "$EUID" -ne 0 ]; then
|
if [ "$EUID" -ne 0 ]; then
|
||||||
echo "Please run as root (sudo ./uninstall.sh)"
|
echo "Please run as root (sudo nupst uninstall or sudo ./uninstall.sh)"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# This script can be called directly or through the CLI
|
||||||
|
# When called through the CLI, environment variables are set
|
||||||
|
# REMOVE_CONFIG=yes|no - whether to remove configuration files
|
||||||
|
# REMOVE_REPO=yes|no - whether to remove the repository
|
||||||
|
|
||||||
|
# If not set through CLI, use defaults
|
||||||
|
REMOVE_CONFIG=${REMOVE_CONFIG:-"no"}
|
||||||
|
REMOVE_REPO=${REMOVE_REPO:-"no"}
|
||||||
|
|
||||||
echo "NUPST Uninstaller"
|
echo "NUPST Uninstaller"
|
||||||
echo "================="
|
echo "================="
|
||||||
echo "This script will completely remove NUPST from your system."
|
echo "This will completely remove NUPST from your system."
|
||||||
|
|
||||||
# Find the directory where this script is located
|
# Find the directory where this script is located
|
||||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
|
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
|
||||||
@ -37,20 +46,52 @@ if [ -L "/usr/local/bin/nupst" ]; then
|
|||||||
rm -f /usr/local/bin/nupst
|
rm -f /usr/local/bin/nupst
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Step 3: Ask about removing configuration
|
# Step 3: Remove configuration if requested
|
||||||
read -p "Do you want to remove the NUPST configuration files? (y/N) " -n 1 -r
|
if [ "$REMOVE_CONFIG" = "yes" ]; then
|
||||||
echo
|
|
||||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
|
||||||
echo "Removing configuration files..."
|
echo "Removing configuration files..."
|
||||||
rm -rf /etc/nupst
|
rm -rf /etc/nupst
|
||||||
|
else
|
||||||
|
# If not called through CLI, ask user
|
||||||
|
if [ -z "$NUPST_CLI_CALL" ]; then
|
||||||
|
read -p "Do you want to remove the NUPST configuration files? (y/N) " -n 1 -r
|
||||||
|
echo
|
||||||
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||||
|
echo "Removing configuration files..."
|
||||||
|
rm -rf /etc/nupst
|
||||||
|
fi
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Step 4: Check if this was a git installation
|
# Step 4: Remove repository if requested
|
||||||
if [ -d "$SCRIPT_DIR/.git" ]; then
|
if [ "$REMOVE_REPO" = "yes" ]; then
|
||||||
echo
|
if [ -d "$SCRIPT_DIR/.git" ]; then
|
||||||
echo "This appears to be a git installation. The local repository will remain intact."
|
echo "Removing NUPST repository directory..."
|
||||||
echo "If you wish to completely remove it, you can delete the directory:"
|
|
||||||
echo " rm -rf $SCRIPT_DIR"
|
# Get parent directory to remove it after the script exits
|
||||||
|
PARENT_DIR=$(dirname "$SCRIPT_DIR")
|
||||||
|
REPO_NAME=$(basename "$SCRIPT_DIR")
|
||||||
|
|
||||||
|
# Create a temporary cleanup script
|
||||||
|
CLEANUP_SCRIPT=$(mktemp)
|
||||||
|
echo "#!/bin/bash" > "$CLEANUP_SCRIPT"
|
||||||
|
echo "sleep 1" >> "$CLEANUP_SCRIPT"
|
||||||
|
echo "rm -rf \"$SCRIPT_DIR\"" >> "$CLEANUP_SCRIPT"
|
||||||
|
echo "echo \"NUPST repository has been removed.\"" >> "$CLEANUP_SCRIPT"
|
||||||
|
chmod +x "$CLEANUP_SCRIPT"
|
||||||
|
|
||||||
|
# Run the cleanup script in the background
|
||||||
|
nohup "$CLEANUP_SCRIPT" > /dev/null 2>&1 &
|
||||||
|
|
||||||
|
echo "NUPST repository will be removed after uninstaller exits."
|
||||||
|
else
|
||||||
|
echo "No git repository found."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# If not requested, just display info
|
||||||
|
if [ -d "$SCRIPT_DIR/.git" ]; then
|
||||||
|
echo
|
||||||
|
echo "NUPST repository at $SCRIPT_DIR will remain intact."
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check for npm global installation
|
# Check for npm global installation
|
||||||
|
Reference in New Issue
Block a user