Compare commits

...

4 Commits

7 changed files with 194 additions and 18 deletions

View File

@ -1,5 +1,18 @@
# Changelog
## 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
- ts/cli.ts: Now extracts debug options from process arguments and passes debug mode to getStatus.
- ts/systemd.ts: Updated getStatus to accept a debugMode parameter, enabling detailed SNMP debug logging, explicitly reloading configuration, and printing connection details.
## 2025-03-25 - 2.0.0 - BREAKING CHANGE(snmp)
refactor: update SNMP type definitions and interface names for consistency

View File

@ -1,6 +1,6 @@
{
"name": "@serve.zone/nupst",
"version": "2.0.0",
"version": "2.1.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.0',
version: '2.1.0',
description: 'Node.js UPS Shutdown Tool for SNMP-enabled UPS devices'
}

107
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';
/**
@ -95,6 +98,10 @@ export class NupstCli {
await this.update();
break;
case 'uninstall':
await this.uninstall();
break;
case 'help':
default:
this.showHelp();
@ -182,7 +189,9 @@ export class NupstCli {
* Show status of the systemd service and UPS
*/
private async status(): Promise<void> {
await this.nupst.getSystemd().getStatus();
// Extract debug options from args array
const debugOptions = this.extractDebugOptions(process.argv);
await this.nupst.getSystemd().getStatus(debugOptions.debugMode);
}
/**
@ -364,6 +373,7 @@ Usage:
nupst setup - Run the interactive setup to configure SNMP settings
nupst test - Test the current configuration by connecting to the UPS
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:
@ -833,4 +843,99 @@ Options:
}
}
}
/**
* 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

@ -126,9 +126,18 @@ WantedBy=multi-user.target
/**
* Get status of the systemd service and UPS
* @param debugMode Whether to enable debug mode for SNMP
*/
public async getStatus(): Promise<void> {
public async getStatus(debugMode: boolean = false): Promise<void> {
try {
// Enable debug mode if requested
if (debugMode) {
console.log('┌─ Debug Mode ─────────────────────────────┐');
console.log('│ SNMP debugging enabled - detailed logs will be shown');
console.log('└──────────────────────────────────────────┘');
this.daemon.getNupstSnmp().enableDebug();
}
// Display version information
this.daemon.getNupstSnmp().getNupst().logVersionInfo();
@ -170,6 +179,8 @@ WantedBy=multi-user.target
*/
private async displayUpsStatus(): Promise<void> {
try {
// Explicitly load the configuration first to ensure it's up-to-date
await this.daemon.loadConfig();
const config = this.daemon.getConfig();
const snmp = this.daemon.getNupstSnmp();
@ -179,6 +190,11 @@ WantedBy=multi-user.target
timeout: Math.min(config.snmp.timeout, 10000) // Use at most 10 seconds for status check
};
console.log('┌─ Connecting to UPS... ────────────────────┐');
console.log(`│ Host: ${config.snmp.host}:${config.snmp.port}`);
console.log(`│ UPS Model: ${config.snmp.upsModel || 'cyberpower'}`);
console.log('└──────────────────────────────────────────┘');
const status = await snmp.getUpsStatus(snmpConfig);
console.log('┌─ UPS Status ───────────────────────────────┐');

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