Compare commits
14 Commits
Author | SHA1 | Date | |
---|---|---|---|
c7db209da7 | |||
bbb8f4a22c | |||
ebc6f65fa9 | |||
0a459f9cd0 | |||
cf231e9785 | |||
edce110c8a | |||
5eefe8cf40 | |||
ecfd171f97 | |||
70c16fa0a6 | |||
7ef38cf961 | |||
fce5a9bafd | |||
8ee21ea92b | |||
32f85aa46f | |||
0a8a52f334 |
48
changelog.md
48
changelog.md
@ -1,5 +1,53 @@
|
||||
# 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
|
||||
|
||||
- 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
|
||||
|
||||
- Renamed SnmpConfig to ISnmpConfig, OIDSet to IOidSet, UpsStatus to IUpsStatus, and UpsModel to TUpsModel in ts/snmp/types.ts.
|
||||
- Updated internal references in ts/daemon.ts, ts/snmp/index.ts, ts/snmp/manager.ts, ts/snmp/oid-sets.ts, ts/snmp/packet-creator.ts, and ts/snmp/packet-parser.ts to use the new interface names.
|
||||
|
||||
## 2025-03-25 - 1.10.1 - fix(systemd/readme)
|
||||
Improve README documentation and fix UPS status retrieval in systemd service
|
||||
|
||||
- Updated README features and installation instructions to clarify SNMP version support, UPS models, and configuration
|
||||
- Modified default SNMP host to '192.168.1.100' and added 'upsModel' property in configuration examples
|
||||
- Enhanced instructions for real-time log viewing and update process in README
|
||||
- Fixed systemd.ts to use a test configuration with an appropriate timeout when fetching UPS status
|
||||
|
||||
## 2025-03-25 - 1.10.0 - feat(core)
|
||||
Add update checking and version logging across startup components
|
||||
|
||||
- In daemon.ts, log version info on startup and check for updates in the background using npm registry response
|
||||
- In nupst.ts, implement getVersion, checkForUpdates, getUpdateStatus, and compareVersions functions with update notifications
|
||||
- Establish bidirectional reference between Nupst and NupstSnmp to support version logging
|
||||
- Update systemd service status output to include version information
|
||||
|
||||
## 2025-03-25 - 1.9.0 - feat(cli)
|
||||
Add update command to CLI to update NUPST from repository and refresh the systemd service
|
||||
|
||||
- Integrate 'update' subcommand in CLI command parser
|
||||
- Update documentation and help output to include new command
|
||||
- Implement update process that fetches changes from git, runs install.sh/setup.sh, and refreshes systemd service if installed
|
||||
|
||||
## 2025-03-25 - 1.8.2 - fix(cli)
|
||||
Refactor logs command to use child_process spawn for real-time log tailing
|
||||
|
||||
|
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",
|
||||
"version": "1.8.2",
|
||||
"version": "2.2.0",
|
||||
"description": "Node.js UPS Shutdown Tool for SNMP-enabled UPS devices",
|
||||
"main": "dist/index.js",
|
||||
"bin": {
|
||||
|
75
readme.md
75
readme.md
@ -4,10 +4,14 @@ NUPST is a command-line tool that monitors SNMP-enabled UPS devices and initiate
|
||||
|
||||
## Features
|
||||
|
||||
- Monitors UPS devices using SNMP
|
||||
- Monitors UPS devices using SNMP (v1, v2c, and v3 supported)
|
||||
- Automatic shutdown when battery level falls below threshold
|
||||
- Automatic shutdown when runtime remaining falls below threshold
|
||||
- Supports multiple UPS brands (CyberPower, APC, Eaton, TrippLite, Liebert/Vertiv)
|
||||
- Simple systemd service integration
|
||||
- Regular status logging for monitoring
|
||||
- Real-time log viewing with journalctl
|
||||
- Version checking and automatic updates
|
||||
- Self-contained - includes its own Node.js runtime
|
||||
|
||||
## Installation
|
||||
@ -66,12 +70,18 @@ Usage:
|
||||
nupst enable - Install and enable the systemd service (requires root)
|
||||
nupst disable - Stop and uninstall the systemd service (requires root)
|
||||
nupst daemon-start - Start the daemon process directly
|
||||
nupst logs - Show logs of the systemd service
|
||||
nupst logs - Show logs of the systemd service in real-time
|
||||
nupst stop - Stop the systemd service
|
||||
nupst start - Start the systemd service
|
||||
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 update - Update NUPST from repository and refresh systemd service (requires root)
|
||||
nupst help - Show this help message
|
||||
|
||||
Options:
|
||||
--debug, -d - Enable debug mode for detailed SNMP logging
|
||||
(Example: nupst test --debug)
|
||||
```
|
||||
|
||||
## Configuration
|
||||
@ -93,11 +103,12 @@ Alternatively, you can manually edit the configuration file at `/etc/nupst/confi
|
||||
```json
|
||||
{
|
||||
"snmp": {
|
||||
"host": "127.0.0.1",
|
||||
"host": "192.168.1.100",
|
||||
"port": 161,
|
||||
"community": "public",
|
||||
"version": 1,
|
||||
"timeout": 5000
|
||||
"timeout": 5000,
|
||||
"upsModel": "cyberpower"
|
||||
},
|
||||
"thresholds": {
|
||||
"battery": 60,
|
||||
@ -112,6 +123,7 @@ Alternatively, you can manually edit the configuration file at `/etc/nupst/confi
|
||||
- `port`: SNMP port (default: 161)
|
||||
- `version`: SNMP version (1, 2, or 3)
|
||||
- `timeout`: Timeout in milliseconds (default: 5000)
|
||||
- `upsModel`: The UPS model ('cyberpower', 'apc', 'eaton', 'tripplite', 'liebert', or 'custom')
|
||||
- For SNMPv1/v2c:
|
||||
- `community`: SNMP community string (default: public)
|
||||
- For SNMPv3:
|
||||
@ -121,6 +133,11 @@ Alternatively, you can manually edit the configuration file at `/etc/nupst/confi
|
||||
- `authKey`: Authentication password/key
|
||||
- `privProtocol`: Privacy/encryption protocol ('DES' or 'AES')
|
||||
- `privKey`: Privacy password/key
|
||||
- For custom UPS models:
|
||||
- `customOIDs`: Object containing custom OIDs for your UPS:
|
||||
- `POWER_STATUS`: OID for power status
|
||||
- `BATTERY_CAPACITY`: OID for battery capacity percentage
|
||||
- `BATTERY_RUNTIME`: OID for runtime remaining in minutes
|
||||
- `thresholds`: When to trigger shutdown
|
||||
- `battery`: Battery percentage threshold (default: 60%)
|
||||
- `runtime`: Runtime minutes threshold (default: 20 minutes)
|
||||
@ -141,6 +158,56 @@ To check the status:
|
||||
nupst status
|
||||
```
|
||||
|
||||
To view logs in real-time:
|
||||
|
||||
```bash
|
||||
nupst logs
|
||||
```
|
||||
|
||||
## Updating NUPST
|
||||
|
||||
NUPST checks for updates automatically and will notify you when an update is available. To update to the latest version:
|
||||
|
||||
```bash
|
||||
sudo nupst update
|
||||
```
|
||||
|
||||
This will:
|
||||
1. Pull the latest changes from the git repository
|
||||
2. Run the installation scripts
|
||||
3. Refresh the systemd service configuration
|
||||
4. Restart the service if it was running
|
||||
|
||||
## Security
|
||||
|
||||
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.
|
||||
- **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
|
||||
- Consistent, tested environment for execution
|
||||
- Reduced risk of dependency-based attacks
|
||||
|
||||
### Implementation Security
|
||||
|
||||
- **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.
|
||||
|
||||
### 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`)
|
||||
|
||||
### Audit and Review
|
||||
|
||||
The codebase is small, focused, and designed to be easily auditable. All code is open source and available for review.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@serve.zone/nupst',
|
||||
version: '1.8.2',
|
||||
version: '2.2.0',
|
||||
description: 'Node.js UPS Shutdown Tool for SNMP-enabled UPS devices'
|
||||
}
|
||||
|
280
ts/cli.ts
280
ts/cli.ts
@ -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';
|
||||
|
||||
/**
|
||||
@ -90,6 +93,18 @@ export class NupstCli {
|
||||
case 'test':
|
||||
await this.test(debugMode);
|
||||
break;
|
||||
|
||||
case 'update':
|
||||
await this.update();
|
||||
break;
|
||||
|
||||
case 'uninstall':
|
||||
await this.uninstall();
|
||||
break;
|
||||
|
||||
case 'config':
|
||||
await this.showConfig();
|
||||
break;
|
||||
|
||||
case 'help':
|
||||
default:
|
||||
@ -178,7 +193,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -359,6 +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:
|
||||
@ -367,6 +387,83 @@ Options:
|
||||
`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update NUPST from repository and refresh systemd service
|
||||
*/
|
||||
private async update(): Promise<void> {
|
||||
try {
|
||||
// Check if running as root
|
||||
this.checkRootAccess('This command must be run as root to update NUPST and refresh the systemd service.');
|
||||
|
||||
console.log('┌─ NUPST Update Process ──────────────────┐');
|
||||
console.log('│ Updating NUPST from repository...');
|
||||
|
||||
// Determine the installation directory (assuming it's either /opt/nupst or the current directory)
|
||||
const { existsSync } = await import('fs');
|
||||
let installDir = '/opt/nupst';
|
||||
|
||||
if (!existsSync(installDir)) {
|
||||
// If not installed in /opt/nupst, use the current directory
|
||||
const { dirname } = await import('path');
|
||||
installDir = dirname(dirname(process.argv[1])); // Go up two levels from the executable
|
||||
console.log(`│ Using local installation directory: ${installDir}`);
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. Update the repository
|
||||
console.log('│ Pulling latest changes from git repository...');
|
||||
execSync(`cd ${installDir} && git fetch origin && git reset --hard origin/main`, { stdio: 'pipe' });
|
||||
|
||||
// 2. Run the install.sh script
|
||||
console.log('│ Running install.sh to update NUPST...');
|
||||
execSync(`cd ${installDir} && bash ./install.sh`, { stdio: 'pipe' });
|
||||
|
||||
// 3. Run the setup.sh script
|
||||
console.log('│ Running setup.sh to update dependencies...');
|
||||
execSync(`cd ${installDir} && bash ./setup.sh`, { stdio: 'pipe' });
|
||||
|
||||
// 4. Refresh the systemd service
|
||||
console.log('│ Refreshing systemd service...');
|
||||
|
||||
// First check if service exists
|
||||
const serviceExists = execSync('systemctl list-unit-files | grep nupst.service').toString().includes('nupst.service');
|
||||
|
||||
if (serviceExists) {
|
||||
// Stop the service if it's running
|
||||
const isRunning = execSync('systemctl is-active nupst.service || true').toString().trim() === 'active';
|
||||
if (isRunning) {
|
||||
console.log('│ Stopping nupst service...');
|
||||
execSync('systemctl stop nupst.service');
|
||||
}
|
||||
|
||||
// Reinstall the service
|
||||
console.log('│ Reinstalling systemd service...');
|
||||
await this.nupst.getSystemd().install();
|
||||
|
||||
// Restart the service if it was running
|
||||
if (isRunning) {
|
||||
console.log('│ Restarting nupst service...');
|
||||
execSync('systemctl start nupst.service');
|
||||
}
|
||||
} else {
|
||||
console.log('│ Systemd service not installed, skipping service refresh.');
|
||||
console.log('│ Run "nupst enable" to install the service.');
|
||||
}
|
||||
|
||||
console.log('│ Update completed successfully!');
|
||||
console.log('└──────────────────────────────────────────┘');
|
||||
} catch (error) {
|
||||
console.error('│ Error during update process:');
|
||||
console.error(`│ ${error.message}`);
|
||||
console.error('└──────────────────────────────────────────┘');
|
||||
process.exit(1);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Update failed: ${error.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interactive setup for configuring SNMP settings
|
||||
*/
|
||||
@ -751,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);
|
||||
}
|
||||
}
|
||||
}
|
31
ts/daemon.ts
31
ts/daemon.ts
@ -1,13 +1,13 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { NupstSnmp, type SnmpConfig } from './snmp.js';
|
||||
import { NupstSnmp, type ISnmpConfig } from './snmp.js';
|
||||
|
||||
/**
|
||||
* Configuration interface for the daemon
|
||||
*/
|
||||
export interface NupstConfig {
|
||||
export interface INupstConfig {
|
||||
/** SNMP configuration settings */
|
||||
snmp: SnmpConfig;
|
||||
snmp: ISnmpConfig;
|
||||
/** Threshold settings for initiating shutdown */
|
||||
thresholds: {
|
||||
/** Shutdown when battery below this percentage */
|
||||
@ -28,7 +28,7 @@ export class NupstDaemon {
|
||||
private readonly CONFIG_PATH = '/etc/nupst/config.json';
|
||||
|
||||
/** Default configuration */
|
||||
private readonly DEFAULT_CONFIG: NupstConfig = {
|
||||
private readonly DEFAULT_CONFIG: INupstConfig = {
|
||||
snmp: {
|
||||
host: '127.0.0.1',
|
||||
port: 161,
|
||||
@ -52,7 +52,7 @@ export class NupstDaemon {
|
||||
checkInterval: 30000, // Check every 30 seconds
|
||||
};
|
||||
|
||||
private config: NupstConfig;
|
||||
private config: INupstConfig;
|
||||
private snmp: NupstSnmp;
|
||||
private isRunning: boolean = false;
|
||||
|
||||
@ -68,7 +68,7 @@ export class NupstDaemon {
|
||||
* Load configuration from file
|
||||
* @throws Error if configuration file doesn't exist
|
||||
*/
|
||||
public async loadConfig(): Promise<NupstConfig> {
|
||||
public async loadConfig(): Promise<INupstConfig> {
|
||||
try {
|
||||
// Check if config file exists
|
||||
const configExists = fs.existsSync(this.CONFIG_PATH);
|
||||
@ -95,7 +95,7 @@ export class NupstDaemon {
|
||||
/**
|
||||
* Save configuration to file
|
||||
*/
|
||||
public async saveConfig(config: NupstConfig): Promise<void> {
|
||||
public async saveConfig(config: INupstConfig): Promise<void> {
|
||||
try {
|
||||
const configDir = path.dirname(this.CONFIG_PATH);
|
||||
if (!fs.existsSync(configDir)) {
|
||||
@ -125,7 +125,7 @@ export class NupstDaemon {
|
||||
/**
|
||||
* Get the current configuration
|
||||
*/
|
||||
public getConfig(): NupstConfig {
|
||||
public getConfig(): INupstConfig {
|
||||
return this.config;
|
||||
}
|
||||
|
||||
@ -152,6 +152,21 @@ export class NupstDaemon {
|
||||
await this.loadConfig();
|
||||
this.logConfigLoaded();
|
||||
|
||||
// Log version information
|
||||
this.snmp.getNupst().logVersionInfo(false); // Don't check for updates immediately on startup
|
||||
|
||||
// Check for updates in the background
|
||||
this.snmp.getNupst().checkForUpdates().then(updateAvailable => {
|
||||
if (updateAvailable) {
|
||||
const updateStatus = this.snmp.getNupst().getUpdateStatus();
|
||||
console.log('┌─ Update Available ───────────────────────┐');
|
||||
console.log(`│ Current Version: ${updateStatus.currentVersion}`);
|
||||
console.log(`│ Latest Version: ${updateStatus.latestVersion}`);
|
||||
console.log('│ Run "sudo nupst update" to update');
|
||||
console.log('└──────────────────────────────────────────┘');
|
||||
}
|
||||
}).catch(() => {}); // Ignore errors checking for updates
|
||||
|
||||
// Start UPS monitoring
|
||||
this.isRunning = true;
|
||||
await this.monitor();
|
||||
|
146
ts/nupst.ts
146
ts/nupst.ts
@ -1,6 +1,9 @@
|
||||
import { NupstSnmp } from './snmp.js';
|
||||
import { NupstDaemon } from './daemon.js';
|
||||
import { NupstSystemd } from './systemd.js';
|
||||
import { commitinfo } from './00_commitinfo_data.js';
|
||||
import { spawn } from 'child_process';
|
||||
import * as https from 'https';
|
||||
|
||||
/**
|
||||
* Main Nupst class that coordinates all components
|
||||
@ -10,12 +13,15 @@ export class Nupst {
|
||||
private readonly snmp: NupstSnmp;
|
||||
private readonly daemon: NupstDaemon;
|
||||
private readonly systemd: NupstSystemd;
|
||||
private updateAvailable: boolean = false;
|
||||
private latestVersion: string = '';
|
||||
|
||||
/**
|
||||
* Create a new Nupst instance with all necessary components
|
||||
*/
|
||||
constructor() {
|
||||
this.snmp = new NupstSnmp();
|
||||
this.snmp.setNupst(this); // Set up bidirectional reference
|
||||
this.daemon = new NupstDaemon(this.snmp);
|
||||
this.systemd = new NupstSystemd(this.daemon);
|
||||
}
|
||||
@ -40,4 +46,144 @@ export class Nupst {
|
||||
public getSystemd(): NupstSystemd {
|
||||
return this.systemd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current version of NUPST
|
||||
* @returns The current version string
|
||||
*/
|
||||
public getVersion(): string {
|
||||
return commitinfo.version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an update is available
|
||||
* @returns Promise resolving to true if an update is available
|
||||
*/
|
||||
public async checkForUpdates(): Promise<boolean> {
|
||||
try {
|
||||
const latestVersion = await this.getLatestVersion();
|
||||
const currentVersion = this.getVersion();
|
||||
|
||||
// Compare versions
|
||||
this.updateAvailable = this.compareVersions(latestVersion, currentVersion) > 0;
|
||||
this.latestVersion = latestVersion;
|
||||
|
||||
return this.updateAvailable;
|
||||
} catch (error) {
|
||||
console.error(`Error checking for updates: ${error.message}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get update status information
|
||||
* @returns Object with update status information
|
||||
*/
|
||||
public getUpdateStatus(): {
|
||||
currentVersion: string,
|
||||
latestVersion: string,
|
||||
updateAvailable: boolean
|
||||
} {
|
||||
return {
|
||||
currentVersion: this.getVersion(),
|
||||
latestVersion: this.latestVersion || this.getVersion(),
|
||||
updateAvailable: this.updateAvailable
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the latest version from npm registry
|
||||
* @returns Promise resolving to the latest version string
|
||||
*/
|
||||
private async getLatestVersion(): Promise<string> {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
const options = {
|
||||
hostname: 'registry.npmjs.org',
|
||||
path: '/@serve.zone/nupst',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'User-Agent': `nupst/${this.getVersion()}`
|
||||
}
|
||||
};
|
||||
|
||||
const req = https.request(options, (res) => {
|
||||
let data = '';
|
||||
|
||||
res.on('data', (chunk) => {
|
||||
data += chunk;
|
||||
});
|
||||
|
||||
res.on('end', () => {
|
||||
try {
|
||||
const response = JSON.parse(data);
|
||||
if (response['dist-tags'] && response['dist-tags'].latest) {
|
||||
resolve(response['dist-tags'].latest);
|
||||
} else {
|
||||
reject(new Error('Failed to parse version from npm registry response'));
|
||||
}
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', (error) => {
|
||||
reject(error);
|
||||
});
|
||||
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two semantic version strings
|
||||
* @param versionA First version
|
||||
* @param versionB Second version
|
||||
* @returns -1 if versionA < versionB, 0 if equal, 1 if versionA > versionB
|
||||
*/
|
||||
private compareVersions(versionA: string, versionB: string): number {
|
||||
const partsA = versionA.split('.').map(part => parseInt(part, 10));
|
||||
const partsB = versionB.split('.').map(part => parseInt(part, 10));
|
||||
|
||||
for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
|
||||
const partA = i < partsA.length ? partsA[i] : 0;
|
||||
const partB = i < partsB.length ? partsB[i] : 0;
|
||||
|
||||
if (partA > partB) return 1;
|
||||
if (partA < partB) return -1;
|
||||
}
|
||||
|
||||
return 0; // Versions are equal
|
||||
}
|
||||
|
||||
/**
|
||||
* Log the current version and update status
|
||||
*/
|
||||
public logVersionInfo(checkForUpdates: boolean = true): void {
|
||||
const version = this.getVersion();
|
||||
console.log('┌─ NUPST Version ────────────────────────┐');
|
||||
console.log(`│ Current Version: ${version}`);
|
||||
|
||||
if (this.updateAvailable && this.latestVersion) {
|
||||
console.log(`│ Update Available: ${this.latestVersion}`);
|
||||
console.log('│ Run "sudo nupst update" to update');
|
||||
} else if (checkForUpdates) {
|
||||
console.log('│ Checking for updates...');
|
||||
this.checkForUpdates().then(updateAvailable => {
|
||||
if (updateAvailable) {
|
||||
console.log(`│ Update Available: ${this.latestVersion}`);
|
||||
console.log('│ Run "sudo nupst update" to update');
|
||||
} else {
|
||||
console.log('│ You are running the latest version');
|
||||
}
|
||||
console.log('└──────────────────────────────────────────┘');
|
||||
}).catch(() => {
|
||||
console.log('│ Could not check for updates');
|
||||
console.log('└──────────────────────────────────────────┘');
|
||||
});
|
||||
} else {
|
||||
console.log('└──────────────────────────────────────────┘');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
// Re-export all public types
|
||||
export type { UpsStatus, OIDSet, UpsModel, SnmpConfig } from './types.js';
|
||||
export type { IUpsStatus, IOidSet, TUpsModel, ISnmpConfig } from './types.js';
|
||||
|
||||
// Re-export the SNMP manager class
|
||||
export { NupstSnmp } from './manager.js';
|
@ -1,7 +1,7 @@
|
||||
import { exec } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
import * as dgram from 'dgram';
|
||||
import type { OIDSet, SnmpConfig, UpsModel, UpsStatus } from './types.js';
|
||||
import type { IOidSet, ISnmpConfig, TUpsModel, IUpsStatus } from './types.js';
|
||||
import { UpsOidSets } from './oid-sets.js';
|
||||
import { SnmpPacketCreator } from './packet-creator.js';
|
||||
import { SnmpPacketParser } from './packet-parser.js';
|
||||
@ -14,10 +14,12 @@ const execAsync = promisify(exec);
|
||||
*/
|
||||
export class NupstSnmp {
|
||||
// Active OID set
|
||||
private activeOIDs: OIDSet;
|
||||
private activeOIDs: IOidSet;
|
||||
// Reference to the parent Nupst instance
|
||||
private nupst: any; // Type 'any' to avoid circular dependency
|
||||
|
||||
// Default SNMP configuration
|
||||
private readonly DEFAULT_CONFIG: SnmpConfig = {
|
||||
private readonly DEFAULT_CONFIG: ISnmpConfig = {
|
||||
host: '127.0.0.1', // Default to localhost
|
||||
port: 161, // Default SNMP port
|
||||
community: 'public', // Default community string for v1/v2c
|
||||
@ -43,11 +45,26 @@ export class NupstSnmp {
|
||||
this.activeOIDs = UpsOidSets.getOidSet('cyberpower');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set reference to the main Nupst instance
|
||||
* @param nupst Reference to the main Nupst instance
|
||||
*/
|
||||
public setNupst(nupst: any): void {
|
||||
this.nupst = nupst;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get reference to the main Nupst instance
|
||||
*/
|
||||
public getNupst(): any {
|
||||
return this.nupst;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set active OID set based on UPS model
|
||||
* @param config SNMP configuration
|
||||
*/
|
||||
private setActiveOIDs(config: SnmpConfig): void {
|
||||
private setActiveOIDs(config: ISnmpConfig): void {
|
||||
// If custom OIDs are provided, use them
|
||||
if (config.upsModel === 'custom' && config.customOIDs) {
|
||||
this.activeOIDs = config.customOIDs;
|
||||
@ -189,7 +206,7 @@ export class NupstSnmp {
|
||||
* @param config SNMP configuration
|
||||
* @returns Promise resolving to the UPS status
|
||||
*/
|
||||
public async getUpsStatus(config = this.DEFAULT_CONFIG): Promise<UpsStatus> {
|
||||
public async getUpsStatus(config = this.DEFAULT_CONFIG): Promise<IUpsStatus> {
|
||||
try {
|
||||
// Set active OID set based on UPS model in config
|
||||
this.setActiveOIDs(config);
|
||||
@ -391,12 +408,12 @@ export class NupstSnmp {
|
||||
* @param config SNMP configuration
|
||||
* @returns Promise resolving to the discovered engine ID
|
||||
*/
|
||||
public async discoverEngineId(config: SnmpConfig): Promise<Buffer> {
|
||||
public async discoverEngineId(config: ISnmpConfig): Promise<Buffer> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const socket = dgram.createSocket('udp4');
|
||||
|
||||
// Create a proper discovery message (SNMPv3 with noAuthNoPriv)
|
||||
const discoveryConfig: SnmpConfig = {
|
||||
const discoveryConfig: ISnmpConfig = {
|
||||
...config,
|
||||
securityLevel: 'noAuthNoPriv',
|
||||
username: '', // Empty username for discovery
|
||||
@ -497,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
|
||||
|
@ -1,4 +1,4 @@
|
||||
import type { OIDSet, UpsModel } from './types.js';
|
||||
import type { IOidSet, TUpsModel } from './types.js';
|
||||
|
||||
/**
|
||||
* OID sets for different UPS models
|
||||
@ -8,7 +8,7 @@ export class UpsOidSets {
|
||||
/**
|
||||
* OID sets for different UPS models
|
||||
*/
|
||||
private static readonly UPS_OID_SETS: Record<UpsModel, OIDSet> = {
|
||||
private static readonly UPS_OID_SETS: Record<TUpsModel, IOidSet> = {
|
||||
// Cyberpower OIDs for RMCARD205 (based on CyberPower_MIB_v2.11)
|
||||
cyberpower: {
|
||||
POWER_STATUS: '1.3.6.1.4.1.3808.1.1.1.4.1.1.0', // upsBaseOutputStatus (2=online, 3=on battery)
|
||||
@ -57,7 +57,7 @@ export class UpsOidSets {
|
||||
* @param model UPS model name
|
||||
* @returns OID set for the model
|
||||
*/
|
||||
public static getOidSet(model: UpsModel): OIDSet {
|
||||
public static getOidSet(model: TUpsModel): IOidSet {
|
||||
return this.UPS_OID_SETS[model];
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import * as crypto from 'crypto';
|
||||
import type { SnmpConfig, SnmpV3SecurityParams } from './types.js';
|
||||
import type { ISnmpConfig, ISnmpV3SecurityParams } from './types.js';
|
||||
import { SnmpEncoder } from './encoder.js';
|
||||
|
||||
/**
|
||||
@ -118,7 +118,7 @@ export class SnmpPacketCreator {
|
||||
*/
|
||||
public static createSnmpV3GetRequest(
|
||||
oid: string,
|
||||
config: SnmpConfig,
|
||||
config: ISnmpConfig,
|
||||
engineID: Buffer,
|
||||
engineBoots: number,
|
||||
engineTime: number,
|
||||
@ -145,7 +145,7 @@ export class SnmpPacketCreator {
|
||||
}
|
||||
|
||||
// Create security parameters
|
||||
const securityParams: SnmpV3SecurityParams = {
|
||||
const securityParams: ISnmpV3SecurityParams = {
|
||||
msgAuthoritativeEngineID: engineID,
|
||||
msgAuthoritativeEngineBoots: engineBoots,
|
||||
msgAuthoritativeEngineTime: engineTime,
|
||||
@ -366,7 +366,7 @@ export class SnmpPacketCreator {
|
||||
* @param config SNMP configuration
|
||||
* @returns Encrypted data
|
||||
*/
|
||||
private static simulateEncryption(data: Buffer, config: SnmpConfig): Buffer {
|
||||
private static simulateEncryption(data: Buffer, config: ISnmpConfig): Buffer {
|
||||
// This is a placeholder - in a real implementation, you would:
|
||||
// 1. Generate an initialization vector (IV)
|
||||
// 2. Use the privacy key derived from the privKey
|
||||
@ -427,7 +427,7 @@ export class SnmpPacketCreator {
|
||||
* @param authParamsBuf Authentication parameters buffer
|
||||
* @returns Authenticated message
|
||||
*/
|
||||
private static addAuthentication(message: Buffer, config: SnmpConfig, authParamsBuf: Buffer): Buffer {
|
||||
private static addAuthentication(message: Buffer, config: ISnmpConfig, authParamsBuf: Buffer): Buffer {
|
||||
// In a real implementation, this would:
|
||||
// 1. Zero out the authentication parameters field
|
||||
// 2. Calculate HMAC-MD5 or HMAC-SHA1 over the entire message
|
||||
@ -548,7 +548,7 @@ export class SnmpPacketCreator {
|
||||
* @param requestID Request ID
|
||||
* @returns Discovery message
|
||||
*/
|
||||
public static createDiscoveryMessage(config: SnmpConfig, requestID: number): Buffer {
|
||||
public static createDiscoveryMessage(config: ISnmpConfig, requestID: number): Buffer {
|
||||
// Basic SNMPv3 header for discovery
|
||||
const msgIdBuf = Buffer.concat([
|
||||
Buffer.from([0x02, 0x04]), // ASN.1 Integer, length 4
|
||||
|
@ -1,4 +1,4 @@
|
||||
import type { SnmpConfig } from './types.js';
|
||||
import type { ISnmpConfig } from './types.js';
|
||||
import { SnmpEncoder } from './encoder.js';
|
||||
|
||||
/**
|
||||
@ -13,7 +13,7 @@ export class SnmpPacketParser {
|
||||
* @param debug Whether to enable debug output
|
||||
* @returns Parsed value or null if parsing failed
|
||||
*/
|
||||
public static parseSnmpResponse(buffer: Buffer, config: SnmpConfig, debug: boolean = false): any {
|
||||
public static parseSnmpResponse(buffer: Buffer, config: ISnmpConfig, debug: boolean = false): any {
|
||||
// Check if we have a response packet
|
||||
if (buffer[0] !== 0x30) {
|
||||
throw new Error('Invalid SNMP response format');
|
||||
|
@ -5,7 +5,7 @@
|
||||
/**
|
||||
* UPS status interface
|
||||
*/
|
||||
export interface UpsStatus {
|
||||
export interface IUpsStatus {
|
||||
/** Current power status */
|
||||
powerStatus: 'online' | 'onBattery' | 'unknown';
|
||||
/** Battery capacity percentage */
|
||||
@ -19,7 +19,7 @@ export interface UpsStatus {
|
||||
/**
|
||||
* SNMP OID Sets for different UPS brands
|
||||
*/
|
||||
export interface OIDSet {
|
||||
export interface IOidSet {
|
||||
/** OID for power status */
|
||||
POWER_STATUS: string;
|
||||
/** OID for battery capacity */
|
||||
@ -31,12 +31,12 @@ export interface OIDSet {
|
||||
/**
|
||||
* Supported UPS model types
|
||||
*/
|
||||
export type UpsModel = 'cyberpower' | 'apc' | 'eaton' | 'tripplite' | 'liebert' | 'custom';
|
||||
export type TUpsModel = 'cyberpower' | 'apc' | 'eaton' | 'tripplite' | 'liebert' | 'custom';
|
||||
|
||||
/**
|
||||
* SNMP Configuration interface
|
||||
*/
|
||||
export interface SnmpConfig {
|
||||
export interface ISnmpConfig {
|
||||
/** SNMP server host */
|
||||
host: string;
|
||||
/** SNMP server port (default 161) */
|
||||
@ -66,15 +66,15 @@ export interface SnmpConfig {
|
||||
|
||||
// UPS model and custom OIDs
|
||||
/** UPS model for OID selection */
|
||||
upsModel?: UpsModel;
|
||||
upsModel?: TUpsModel;
|
||||
/** Custom OIDs when using custom UPS model */
|
||||
customOIDs?: OIDSet;
|
||||
customOIDs?: IOidSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* SNMPv3 security parameters
|
||||
*/
|
||||
export interface SnmpV3SecurityParams {
|
||||
export interface ISnmpV3SecurityParams {
|
||||
/** Engine ID for the SNMP server */
|
||||
msgAuthoritativeEngineID: Buffer;
|
||||
/** Engine boots counter */
|
||||
|
@ -126,9 +126,21 @@ 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();
|
||||
|
||||
// Check if config exists first
|
||||
try {
|
||||
await this.checkConfigExists();
|
||||
@ -167,9 +179,23 @@ WantedBy=multi-user.target
|
||||
*/
|
||||
private async displayUpsStatus(): Promise<void> {
|
||||
try {
|
||||
const upsStatus = await this.daemon.getConfig().snmp;
|
||||
// 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();
|
||||
const status = await snmp.getUpsStatus(upsStatus);
|
||||
|
||||
// Create a test config with appropriate timeout, similar to the test command
|
||||
const snmpConfig = {
|
||||
...config.snmp,
|
||||
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 ───────────────────────────────┐');
|
||||
console.log(`│ Power Status: ${status.powerStatus}`);
|
||||
|
65
uninstall.sh
65
uninstall.sh
@ -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
|
||||
|
Reference in New Issue
Block a user