Compare commits

..

12 Commits

14 changed files with 455 additions and 45 deletions

View File

@ -1,5 +1,47 @@
# Changelog
## 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
- Replaced execSync call with spawn to properly follow logs
- Forward SIGINT to the spawned process for graceful termination
- Await the child process exit to ensure clean shutdown of the CLI log command
## 2025-03-25 - 1.8.1 - fix(systemd)
Update ExecStart in systemd service template to use /opt/nupst/bin/nupst for daemon startup
- Changed ExecStart from '/usr/bin/nupst daemon-start' to '/opt/nupst/bin/nupst daemon-start' in the systemd service file
- Ensures the service uses the correct binary installation path
## 2025-03-25 - 1.8.0 - feat(core)
Enhance SNMP module and interactive CLI setup for UPS shutdown

View File

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

View File

@ -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

View File

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

102
ts/cli.ts
View File

@ -90,6 +90,10 @@ export class NupstCli {
case 'test':
await this.test(debugMode);
break;
case 'update':
await this.update();
break;
case 'help':
default:
@ -131,8 +135,24 @@ export class NupstCli {
*/
private async logs(): Promise<void> {
try {
const logs = execSync('journalctl -u nupst.service -n 50 -f').toString();
console.log(logs);
// Use exec with spawn to properly follow logs in real-time
const { spawn } = await import('child_process');
console.log('Tailing nupst service logs (Ctrl+C to exit)...\n');
const journalctl = spawn('journalctl', ['-u', 'nupst.service', '-n', '50', '-f'], {
stdio: ['ignore', 'inherit', 'inherit']
});
// Forward signals to child process
process.on('SIGINT', () => {
journalctl.kill('SIGINT');
process.exit(0);
});
// Wait for process to exit
await new Promise<void>((resolve) => {
journalctl.on('exit', () => resolve());
});
} catch (error) {
console.error('Failed to retrieve logs:', error);
process.exit(1);
@ -343,6 +363,7 @@ 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 update - Update NUPST from repository and refresh systemd service (requires root)
nupst help - Show this help message
Options:
@ -351,6 +372,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
*/

View File

@ -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();
@ -193,11 +208,15 @@ export class NupstDaemon {
console.log('Starting UPS monitoring...');
let lastStatus: 'online' | 'onBattery' | 'unknown' = 'unknown';
let lastLogTime = 0; // Track when we last logged status
const LOG_INTERVAL = 5 * 60 * 1000; // Log at least every 5 minutes (300000ms)
// Monitor continuously
while (this.isRunning) {
try {
const status = await this.snmp.getUpsStatus(this.config.snmp);
const currentTime = Date.now();
const shouldLogStatus = (currentTime - lastLogTime) >= LOG_INTERVAL;
// Log status changes
if (status.powerStatus !== lastStatus) {
@ -205,6 +224,17 @@ export class NupstDaemon {
console.log(`│ Power status changed: ${lastStatus}${status.powerStatus}`);
console.log('└──────────────────────────────────────────┘');
lastStatus = status.powerStatus;
lastLogTime = currentTime; // Reset log timer when status changes
}
// Log status periodically (at least every 5 minutes)
else if (shouldLogStatus) {
const timestamp = new Date().toISOString();
console.log('┌──────────────────────────────────────────┐');
console.log(`│ [${timestamp}] Periodic Status Update`);
console.log(`│ Power Status: ${status.powerStatus}`);
console.log(`│ Battery: ${status.batteryCapacity}% | Runtime: ${status.batteryRuntime} min`);
console.log('└──────────────────────────────────────────┘');
lastLogTime = currentTime;
}
// Handle battery power status

View File

@ -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('└──────────────────────────────────────────┘');
}
}
}

View File

@ -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';

View File

@ -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

View File

@ -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];
}

View File

@ -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

View File

@ -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');

View File

@ -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 */

View File

@ -17,7 +17,7 @@ Description=Node.js UPS Shutdown Tool
After=network.target
[Service]
ExecStart=/usr/bin/nupst daemon-start
ExecStart=/opt/nupst/bin/nupst daemon-start
Restart=always
User=root
Group=root
@ -129,6 +129,9 @@ WantedBy=multi-user.target
*/
public async getStatus(): Promise<void> {
try {
// Display version information
this.daemon.getNupstSnmp().getNupst().logVersionInfo();
// Check if config exists first
try {
await this.checkConfigExists();
@ -167,9 +170,16 @@ WantedBy=multi-user.target
*/
private async displayUpsStatus(): Promise<void> {
try {
const upsStatus = await this.daemon.getConfig().snmp;
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
};
const status = await snmp.getUpsStatus(snmpConfig);
console.log('┌─ UPS Status ───────────────────────────────┐');
console.log(`│ Power Status: ${status.powerStatus}`);