Compare commits

...

6 Commits

13 changed files with 319 additions and 42 deletions

View File

@ -1,5 +1,27 @@
# Changelog # 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) ## 2025-03-25 - 1.9.0 - feat(cli)
Add update command to CLI to update NUPST from repository and refresh the systemd service Add update command to CLI to update NUPST from repository and refresh the systemd service

View File

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

View File

@ -4,10 +4,14 @@ NUPST is a command-line tool that monitors SNMP-enabled UPS devices and initiate
## Features ## 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 battery level falls below threshold
- Automatic shutdown when runtime remaining 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 - 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 - Self-contained - includes its own Node.js runtime
## Installation ## Installation
@ -66,12 +70,18 @@ Usage:
nupst enable - Install and enable the systemd service (requires root) nupst enable - Install and enable the systemd service (requires root)
nupst disable - Stop and uninstall the systemd service (requires root) nupst disable - Stop and uninstall the systemd service (requires root)
nupst daemon-start - Start the daemon process directly 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 stop - Stop the systemd service
nupst start - Start the systemd service nupst start - Start the systemd service
nupst status - Show status of the systemd service and UPS status nupst status - Show status of the systemd service and UPS status
nupst setup - Run the interactive setup to configure SNMP settings nupst setup - Run the interactive setup to configure SNMP settings
nupst test - Test the current configuration by connecting to the UPS
nupst update - Update NUPST from repository and refresh systemd service (requires root)
nupst help - Show this help message nupst help - Show this help message
Options:
--debug, -d - Enable debug mode for detailed SNMP logging
(Example: nupst test --debug)
``` ```
## Configuration ## Configuration
@ -93,11 +103,12 @@ Alternatively, you can manually edit the configuration file at `/etc/nupst/confi
```json ```json
{ {
"snmp": { "snmp": {
"host": "127.0.0.1", "host": "192.168.1.100",
"port": 161, "port": 161,
"community": "public", "community": "public",
"version": 1, "version": 1,
"timeout": 5000 "timeout": 5000,
"upsModel": "cyberpower"
}, },
"thresholds": { "thresholds": {
"battery": 60, "battery": 60,
@ -112,6 +123,7 @@ Alternatively, you can manually edit the configuration file at `/etc/nupst/confi
- `port`: SNMP port (default: 161) - `port`: SNMP port (default: 161)
- `version`: SNMP version (1, 2, or 3) - `version`: SNMP version (1, 2, or 3)
- `timeout`: Timeout in milliseconds (default: 5000) - `timeout`: Timeout in milliseconds (default: 5000)
- `upsModel`: The UPS model ('cyberpower', 'apc', 'eaton', 'tripplite', 'liebert', or 'custom')
- For SNMPv1/v2c: - For SNMPv1/v2c:
- `community`: SNMP community string (default: public) - `community`: SNMP community string (default: public)
- For SNMPv3: - For SNMPv3:
@ -121,6 +133,11 @@ Alternatively, you can manually edit the configuration file at `/etc/nupst/confi
- `authKey`: Authentication password/key - `authKey`: Authentication password/key
- `privProtocol`: Privacy/encryption protocol ('DES' or 'AES') - `privProtocol`: Privacy/encryption protocol ('DES' or 'AES')
- `privKey`: Privacy password/key - `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 - `thresholds`: When to trigger shutdown
- `battery`: Battery percentage threshold (default: 60%) - `battery`: Battery percentage threshold (default: 60%)
- `runtime`: Runtime minutes threshold (default: 20 minutes) - `runtime`: Runtime minutes threshold (default: 20 minutes)
@ -141,6 +158,56 @@ To check the status:
nupst 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 ## License
MIT MIT

View File

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

View File

@ -1,13 +1,13 @@
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import { NupstSnmp, type SnmpConfig } from './snmp.js'; import { NupstSnmp, type ISnmpConfig } from './snmp.js';
/** /**
* Configuration interface for the daemon * Configuration interface for the daemon
*/ */
export interface NupstConfig { export interface INupstConfig {
/** SNMP configuration settings */ /** SNMP configuration settings */
snmp: SnmpConfig; snmp: ISnmpConfig;
/** Threshold settings for initiating shutdown */ /** Threshold settings for initiating shutdown */
thresholds: { thresholds: {
/** Shutdown when battery below this percentage */ /** Shutdown when battery below this percentage */
@ -28,7 +28,7 @@ export class NupstDaemon {
private readonly CONFIG_PATH = '/etc/nupst/config.json'; private readonly CONFIG_PATH = '/etc/nupst/config.json';
/** Default configuration */ /** Default configuration */
private readonly DEFAULT_CONFIG: NupstConfig = { private readonly DEFAULT_CONFIG: INupstConfig = {
snmp: { snmp: {
host: '127.0.0.1', host: '127.0.0.1',
port: 161, port: 161,
@ -52,7 +52,7 @@ export class NupstDaemon {
checkInterval: 30000, // Check every 30 seconds checkInterval: 30000, // Check every 30 seconds
}; };
private config: NupstConfig; private config: INupstConfig;
private snmp: NupstSnmp; private snmp: NupstSnmp;
private isRunning: boolean = false; private isRunning: boolean = false;
@ -68,7 +68,7 @@ export class NupstDaemon {
* Load configuration from file * Load configuration from file
* @throws Error if configuration file doesn't exist * @throws Error if configuration file doesn't exist
*/ */
public async loadConfig(): Promise<NupstConfig> { public async loadConfig(): Promise<INupstConfig> {
try { try {
// Check if config file exists // Check if config file exists
const configExists = fs.existsSync(this.CONFIG_PATH); const configExists = fs.existsSync(this.CONFIG_PATH);
@ -95,7 +95,7 @@ export class NupstDaemon {
/** /**
* Save configuration to file * Save configuration to file
*/ */
public async saveConfig(config: NupstConfig): Promise<void> { public async saveConfig(config: INupstConfig): Promise<void> {
try { try {
const configDir = path.dirname(this.CONFIG_PATH); const configDir = path.dirname(this.CONFIG_PATH);
if (!fs.existsSync(configDir)) { if (!fs.existsSync(configDir)) {
@ -125,7 +125,7 @@ export class NupstDaemon {
/** /**
* Get the current configuration * Get the current configuration
*/ */
public getConfig(): NupstConfig { public getConfig(): INupstConfig {
return this.config; return this.config;
} }
@ -152,6 +152,21 @@ export class NupstDaemon {
await this.loadConfig(); await this.loadConfig();
this.logConfigLoaded(); 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 // Start UPS monitoring
this.isRunning = true; this.isRunning = true;
await this.monitor(); await this.monitor();

View File

@ -1,6 +1,9 @@
import { NupstSnmp } from './snmp.js'; import { NupstSnmp } from './snmp.js';
import { NupstDaemon } from './daemon.js'; import { NupstDaemon } from './daemon.js';
import { NupstSystemd } from './systemd.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 * Main Nupst class that coordinates all components
@ -10,12 +13,15 @@ export class Nupst {
private readonly snmp: NupstSnmp; private readonly snmp: NupstSnmp;
private readonly daemon: NupstDaemon; private readonly daemon: NupstDaemon;
private readonly systemd: NupstSystemd; private readonly systemd: NupstSystemd;
private updateAvailable: boolean = false;
private latestVersion: string = '';
/** /**
* Create a new Nupst instance with all necessary components * Create a new Nupst instance with all necessary components
*/ */
constructor() { constructor() {
this.snmp = new NupstSnmp(); this.snmp = new NupstSnmp();
this.snmp.setNupst(this); // Set up bidirectional reference
this.daemon = new NupstDaemon(this.snmp); this.daemon = new NupstDaemon(this.snmp);
this.systemd = new NupstSystemd(this.daemon); this.systemd = new NupstSystemd(this.daemon);
} }
@ -40,4 +46,144 @@ export class Nupst {
public getSystemd(): NupstSystemd { public getSystemd(): NupstSystemd {
return this.systemd; 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 // 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 // Re-export the SNMP manager class
export { NupstSnmp } from './manager.js'; export { NupstSnmp } from './manager.js';

View File

@ -1,7 +1,7 @@
import { exec } from 'child_process'; import { exec } from 'child_process';
import { promisify } from 'util'; import { promisify } from 'util';
import * as dgram from 'dgram'; 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 { UpsOidSets } from './oid-sets.js';
import { SnmpPacketCreator } from './packet-creator.js'; import { SnmpPacketCreator } from './packet-creator.js';
import { SnmpPacketParser } from './packet-parser.js'; import { SnmpPacketParser } from './packet-parser.js';
@ -14,10 +14,12 @@ const execAsync = promisify(exec);
*/ */
export class NupstSnmp { export class NupstSnmp {
// Active OID set // 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 // Default SNMP configuration
private readonly DEFAULT_CONFIG: SnmpConfig = { private readonly DEFAULT_CONFIG: ISnmpConfig = {
host: '127.0.0.1', // Default to localhost host: '127.0.0.1', // Default to localhost
port: 161, // Default SNMP port port: 161, // Default SNMP port
community: 'public', // Default community string for v1/v2c community: 'public', // Default community string for v1/v2c
@ -43,11 +45,26 @@ export class NupstSnmp {
this.activeOIDs = UpsOidSets.getOidSet('cyberpower'); 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 * Set active OID set based on UPS model
* @param config SNMP configuration * @param config SNMP configuration
*/ */
private setActiveOIDs(config: SnmpConfig): void { private setActiveOIDs(config: ISnmpConfig): void {
// If custom OIDs are provided, use them // If custom OIDs are provided, use them
if (config.upsModel === 'custom' && config.customOIDs) { if (config.upsModel === 'custom' && config.customOIDs) {
this.activeOIDs = config.customOIDs; this.activeOIDs = config.customOIDs;
@ -189,7 +206,7 @@ export class NupstSnmp {
* @param config SNMP configuration * @param config SNMP configuration
* @returns Promise resolving to the UPS status * @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 { try {
// Set active OID set based on UPS model in config // Set active OID set based on UPS model in config
this.setActiveOIDs(config); this.setActiveOIDs(config);
@ -391,12 +408,12 @@ export class NupstSnmp {
* @param config SNMP configuration * @param config SNMP configuration
* @returns Promise resolving to the discovered engine ID * @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) => { return new Promise((resolve, reject) => {
const socket = dgram.createSocket('udp4'); const socket = dgram.createSocket('udp4');
// Create a proper discovery message (SNMPv3 with noAuthNoPriv) // Create a proper discovery message (SNMPv3 with noAuthNoPriv)
const discoveryConfig: SnmpConfig = { const discoveryConfig: ISnmpConfig = {
...config, ...config,
securityLevel: 'noAuthNoPriv', securityLevel: 'noAuthNoPriv',
username: '', // Empty username for discovery 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 * OID sets for different UPS models
@ -8,7 +8,7 @@ export class UpsOidSets {
/** /**
* OID sets for different UPS models * 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 OIDs for RMCARD205 (based on CyberPower_MIB_v2.11)
cyberpower: { cyberpower: {
POWER_STATUS: '1.3.6.1.4.1.3808.1.1.1.4.1.1.0', // upsBaseOutputStatus (2=online, 3=on battery) 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 * @param model UPS model name
* @returns OID set for the model * @returns OID set for the model
*/ */
public static getOidSet(model: UpsModel): OIDSet { public static getOidSet(model: TUpsModel): IOidSet {
return this.UPS_OID_SETS[model]; return this.UPS_OID_SETS[model];
} }

View File

@ -1,5 +1,5 @@
import * as crypto from 'crypto'; import * as crypto from 'crypto';
import type { SnmpConfig, SnmpV3SecurityParams } from './types.js'; import type { ISnmpConfig, ISnmpV3SecurityParams } from './types.js';
import { SnmpEncoder } from './encoder.js'; import { SnmpEncoder } from './encoder.js';
/** /**
@ -118,7 +118,7 @@ export class SnmpPacketCreator {
*/ */
public static createSnmpV3GetRequest( public static createSnmpV3GetRequest(
oid: string, oid: string,
config: SnmpConfig, config: ISnmpConfig,
engineID: Buffer, engineID: Buffer,
engineBoots: number, engineBoots: number,
engineTime: number, engineTime: number,
@ -145,7 +145,7 @@ export class SnmpPacketCreator {
} }
// Create security parameters // Create security parameters
const securityParams: SnmpV3SecurityParams = { const securityParams: ISnmpV3SecurityParams = {
msgAuthoritativeEngineID: engineID, msgAuthoritativeEngineID: engineID,
msgAuthoritativeEngineBoots: engineBoots, msgAuthoritativeEngineBoots: engineBoots,
msgAuthoritativeEngineTime: engineTime, msgAuthoritativeEngineTime: engineTime,
@ -366,7 +366,7 @@ export class SnmpPacketCreator {
* @param config SNMP configuration * @param config SNMP configuration
* @returns Encrypted data * @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: // This is a placeholder - in a real implementation, you would:
// 1. Generate an initialization vector (IV) // 1. Generate an initialization vector (IV)
// 2. Use the privacy key derived from the privKey // 2. Use the privacy key derived from the privKey
@ -427,7 +427,7 @@ export class SnmpPacketCreator {
* @param authParamsBuf Authentication parameters buffer * @param authParamsBuf Authentication parameters buffer
* @returns Authenticated message * @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: // In a real implementation, this would:
// 1. Zero out the authentication parameters field // 1. Zero out the authentication parameters field
// 2. Calculate HMAC-MD5 or HMAC-SHA1 over the entire message // 2. Calculate HMAC-MD5 or HMAC-SHA1 over the entire message
@ -548,7 +548,7 @@ export class SnmpPacketCreator {
* @param requestID Request ID * @param requestID Request ID
* @returns Discovery message * @returns Discovery message
*/ */
public static createDiscoveryMessage(config: SnmpConfig, requestID: number): Buffer { public static createDiscoveryMessage(config: ISnmpConfig, requestID: number): Buffer {
// Basic SNMPv3 header for discovery // Basic SNMPv3 header for discovery
const msgIdBuf = Buffer.concat([ const msgIdBuf = Buffer.concat([
Buffer.from([0x02, 0x04]), // ASN.1 Integer, length 4 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'; import { SnmpEncoder } from './encoder.js';
/** /**
@ -13,7 +13,7 @@ export class SnmpPacketParser {
* @param debug Whether to enable debug output * @param debug Whether to enable debug output
* @returns Parsed value or null if parsing failed * @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 // Check if we have a response packet
if (buffer[0] !== 0x30) { if (buffer[0] !== 0x30) {
throw new Error('Invalid SNMP response format'); throw new Error('Invalid SNMP response format');

View File

@ -5,7 +5,7 @@
/** /**
* UPS status interface * UPS status interface
*/ */
export interface UpsStatus { export interface IUpsStatus {
/** Current power status */ /** Current power status */
powerStatus: 'online' | 'onBattery' | 'unknown'; powerStatus: 'online' | 'onBattery' | 'unknown';
/** Battery capacity percentage */ /** Battery capacity percentage */
@ -19,7 +19,7 @@ export interface UpsStatus {
/** /**
* SNMP OID Sets for different UPS brands * SNMP OID Sets for different UPS brands
*/ */
export interface OIDSet { export interface IOidSet {
/** OID for power status */ /** OID for power status */
POWER_STATUS: string; POWER_STATUS: string;
/** OID for battery capacity */ /** OID for battery capacity */
@ -31,12 +31,12 @@ export interface OIDSet {
/** /**
* Supported UPS model types * 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 * SNMP Configuration interface
*/ */
export interface SnmpConfig { export interface ISnmpConfig {
/** SNMP server host */ /** SNMP server host */
host: string; host: string;
/** SNMP server port (default 161) */ /** SNMP server port (default 161) */
@ -66,15 +66,15 @@ export interface SnmpConfig {
// UPS model and custom OIDs // UPS model and custom OIDs
/** UPS model for OID selection */ /** UPS model for OID selection */
upsModel?: UpsModel; upsModel?: TUpsModel;
/** Custom OIDs when using custom UPS model */ /** Custom OIDs when using custom UPS model */
customOIDs?: OIDSet; customOIDs?: IOidSet;
} }
/** /**
* SNMPv3 security parameters * SNMPv3 security parameters
*/ */
export interface SnmpV3SecurityParams { export interface ISnmpV3SecurityParams {
/** Engine ID for the SNMP server */ /** Engine ID for the SNMP server */
msgAuthoritativeEngineID: Buffer; msgAuthoritativeEngineID: Buffer;
/** Engine boots counter */ /** Engine boots counter */

View File

@ -129,6 +129,9 @@ WantedBy=multi-user.target
*/ */
public async getStatus(): Promise<void> { public async getStatus(): Promise<void> {
try { try {
// Display version information
this.daemon.getNupstSnmp().getNupst().logVersionInfo();
// Check if config exists first // Check if config exists first
try { try {
await this.checkConfigExists(); await this.checkConfigExists();
@ -167,9 +170,16 @@ WantedBy=multi-user.target
*/ */
private async displayUpsStatus(): Promise<void> { private async displayUpsStatus(): Promise<void> {
try { try {
const upsStatus = await this.daemon.getConfig().snmp; const config = this.daemon.getConfig();
const snmp = this.daemon.getNupstSnmp(); 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('┌─ UPS Status ───────────────────────────────┐');
console.log(`│ Power Status: ${status.powerStatus}`); console.log(`│ Power Status: ${status.powerStatus}`);