Compare commits

..

4 Commits

7 changed files with 286 additions and 16 deletions

View File

@ -1,5 +1,18 @@
# 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)
Fix status command to pass debug flag and improve systemd status logging output

21
license Normal file
View 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.

View File

@ -1,6 +1,6 @@
{
"name": "@serve.zone/nupst",
"version": "2.0.1",
"version": "2.2.0",
"description": "Node.js UPS Shutdown Tool for SNMP-enabled UPS devices",
"main": "dist/index.js",
"bin": {

View File

@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@serve.zone/nupst',
version: '2.0.1',
version: '2.2.0',
description: 'Node.js UPS Shutdown Tool for SNMP-enabled UPS devices'
}

194
ts/cli.ts
View File

@ -1,4 +1,7 @@
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';
/**
@ -94,6 +97,14 @@ export class NupstCli {
case 'update':
await this.update();
break;
case 'uninstall':
await this.uninstall();
break;
case 'config':
await this.showConfig();
break;
case 'help':
default:
@ -365,7 +376,9 @@ Usage:
nupst status - Show status of the systemd service and UPS status
nupst setup - Run the interactive setup to configure SNMP settings
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 uninstall - Completely uninstall NUPST from the system (requires root)
nupst help - Show this help message
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);
}
}
}

View File

@ -514,9 +514,10 @@ export class NupstSnmp {
public async initiateShutdown(reason: string): Promise<void> {
console.log(`Initiating system shutdown due to: ${reason}`);
try {
// Execute shutdown command
const { stdout } = await execAsync('shutdown -h +1 "UPS battery critical, shutting down in 1 minute"');
// Execute shutdown command with 5 minute delay to allow for VM graceful shutdown
const { stdout } = await execAsync('shutdown -h +5 "UPS battery critical, shutting down in 5 minutes"');
console.log('Shutdown initiated:', stdout);
console.log('Allowing 5 minutes for VMs to shut down safely');
} catch (error) {
console.error('Failed to initiate shutdown:', error);
// Try a different method if first one fails

View File

@ -5,13 +5,22 @@
# Check if running as root
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
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 "================="
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
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
fi
# Step 3: Ask about removing configuration
read -p "Do you want to remove the NUPST configuration files? (y/N) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
# Step 3: Remove configuration if requested
if [ "$REMOVE_CONFIG" = "yes" ]; then
echo "Removing configuration files..."
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
# Step 4: Check if this was a git installation
if [ -d "$SCRIPT_DIR/.git" ]; then
echo
echo "This appears to be a git installation. The local repository will remain intact."
echo "If you wish to completely remove it, you can delete the directory:"
echo " rm -rf $SCRIPT_DIR"
# Step 4: Remove repository if requested
if [ "$REMOVE_REPO" = "yes" ]; then
if [ -d "$SCRIPT_DIR/.git" ]; then
echo "Removing NUPST repository directory..."
# 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
# Check for npm global installation