initial
This commit is contained in:
261
ts/daemon.ts
Normal file
261
ts/daemon.ts
Normal file
@@ -0,0 +1,261 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { NupstSnmp, type SnmpConfig } from './snmp.js';
|
||||
|
||||
/**
|
||||
* Configuration interface for the daemon
|
||||
*/
|
||||
export interface NupstConfig {
|
||||
/** SNMP configuration settings */
|
||||
snmp: SnmpConfig;
|
||||
/** Threshold settings for initiating shutdown */
|
||||
thresholds: {
|
||||
/** Shutdown when battery below this percentage */
|
||||
battery: number;
|
||||
/** Shutdown when runtime below this minutes */
|
||||
runtime: number;
|
||||
};
|
||||
/** Check interval in milliseconds */
|
||||
checkInterval: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Daemon class for monitoring UPS and handling shutdown
|
||||
* Responsible for loading/saving config and monitoring the UPS status
|
||||
*/
|
||||
export class NupstDaemon {
|
||||
/** Default configuration path */
|
||||
private readonly CONFIG_PATH = '/etc/nupst/config.json';
|
||||
|
||||
/** Default configuration */
|
||||
private readonly DEFAULT_CONFIG: NupstConfig = {
|
||||
snmp: {
|
||||
host: '127.0.0.1',
|
||||
port: 161,
|
||||
community: 'public',
|
||||
version: 1,
|
||||
timeout: 5000,
|
||||
// SNMPv3 defaults (used only if version === 3)
|
||||
securityLevel: 'authPriv',
|
||||
username: '',
|
||||
authProtocol: 'SHA',
|
||||
authKey: '',
|
||||
privProtocol: 'AES',
|
||||
privKey: '',
|
||||
// UPS model for OID selection
|
||||
upsModel: 'cyberpower'
|
||||
},
|
||||
thresholds: {
|
||||
battery: 60, // Shutdown when battery below 60%
|
||||
runtime: 20, // Shutdown when runtime below 20 minutes
|
||||
},
|
||||
checkInterval: 30000, // Check every 30 seconds
|
||||
};
|
||||
|
||||
private config: NupstConfig;
|
||||
private snmp: NupstSnmp;
|
||||
private isRunning: boolean = false;
|
||||
|
||||
/**
|
||||
* Create a new daemon instance with the given SNMP manager
|
||||
*/
|
||||
constructor(snmp: NupstSnmp) {
|
||||
this.snmp = snmp;
|
||||
this.config = this.DEFAULT_CONFIG;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load configuration from file
|
||||
* @throws Error if configuration file doesn't exist
|
||||
*/
|
||||
public async loadConfig(): Promise<NupstConfig> {
|
||||
try {
|
||||
// Check if config file exists
|
||||
const configExists = fs.existsSync(this.CONFIG_PATH);
|
||||
if (!configExists) {
|
||||
const errorMsg = `No configuration found at ${this.CONFIG_PATH}`;
|
||||
this.logConfigError(errorMsg);
|
||||
throw new Error(errorMsg);
|
||||
}
|
||||
|
||||
// Read and parse config
|
||||
const configData = fs.readFileSync(this.CONFIG_PATH, 'utf8');
|
||||
this.config = JSON.parse(configData);
|
||||
return this.config;
|
||||
} catch (error) {
|
||||
if (error.message.includes('No configuration found')) {
|
||||
throw error; // Re-throw the no configuration error
|
||||
}
|
||||
|
||||
this.logConfigError(`Error loading configuration: ${error.message}`);
|
||||
throw new Error('Failed to load configuration');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save configuration to file
|
||||
*/
|
||||
public async saveConfig(config: NupstConfig): Promise<void> {
|
||||
try {
|
||||
const configDir = path.dirname(this.CONFIG_PATH);
|
||||
if (!fs.existsSync(configDir)) {
|
||||
fs.mkdirSync(configDir, { recursive: true });
|
||||
}
|
||||
fs.writeFileSync(this.CONFIG_PATH, JSON.stringify(config, null, 2));
|
||||
this.config = config;
|
||||
|
||||
console.log('┌─ Configuration Saved ─────────────────────┐');
|
||||
console.log(`│ Location: ${this.CONFIG_PATH}`);
|
||||
console.log('└──────────────────────────────────────────┘');
|
||||
} catch (error) {
|
||||
console.error('Error saving configuration:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to log configuration errors consistently
|
||||
*/
|
||||
private logConfigError(message: string): void {
|
||||
console.error('┌─ Configuration Error ─────────────────────┐');
|
||||
console.error(`│ ${message}`);
|
||||
console.error('│ Please run \'nupst setup\' first to create a configuration.');
|
||||
console.error('└──────────────────────────────────────────┘');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current configuration
|
||||
*/
|
||||
public getConfig(): NupstConfig {
|
||||
return this.config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the SNMP instance
|
||||
*/
|
||||
public getNupstSnmp(): NupstSnmp {
|
||||
return this.snmp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the monitoring daemon
|
||||
*/
|
||||
public async start(): Promise<void> {
|
||||
if (this.isRunning) {
|
||||
console.log('Daemon is already running');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Starting NUPST daemon...');
|
||||
|
||||
try {
|
||||
// Load configuration - this will throw an error if config doesn't exist
|
||||
await this.loadConfig();
|
||||
this.logConfigLoaded();
|
||||
|
||||
// Start UPS monitoring
|
||||
this.isRunning = true;
|
||||
await this.monitor();
|
||||
} catch (error) {
|
||||
this.isRunning = false;
|
||||
console.error(`Daemon failed to start: ${error.message}`);
|
||||
process.exit(1); // Exit with error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log the loaded configuration settings
|
||||
*/
|
||||
private logConfigLoaded(): void {
|
||||
console.log('┌─ Configuration Loaded ─────────────────────┐');
|
||||
console.log('│ SNMP Settings:');
|
||||
console.log(`│ Host: ${this.config.snmp.host}`);
|
||||
console.log(`│ Port: ${this.config.snmp.port}`);
|
||||
console.log(`│ Version: ${this.config.snmp.version}`);
|
||||
console.log('│ Thresholds:');
|
||||
console.log(`│ Battery: ${this.config.thresholds.battery}%`);
|
||||
console.log(`│ Runtime: ${this.config.thresholds.runtime} minutes`);
|
||||
console.log(`│ Check Interval: ${this.config.checkInterval / 1000} seconds`);
|
||||
console.log('└──────────────────────────────────────────┘');
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the monitoring daemon
|
||||
*/
|
||||
public stop(): void {
|
||||
console.log('Stopping NUPST daemon...');
|
||||
this.isRunning = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Monitor the UPS status and trigger shutdown when necessary
|
||||
*/
|
||||
private async monitor(): Promise<void> {
|
||||
console.log('Starting UPS monitoring...');
|
||||
|
||||
let lastStatus: 'online' | 'onBattery' | 'unknown' = 'unknown';
|
||||
|
||||
// Monitor continuously
|
||||
while (this.isRunning) {
|
||||
try {
|
||||
const status = await this.snmp.getUpsStatus(this.config.snmp);
|
||||
|
||||
// Log status changes
|
||||
if (status.powerStatus !== lastStatus) {
|
||||
console.log('┌──────────────────────────────────────────┐');
|
||||
console.log(`│ Power status changed: ${lastStatus} → ${status.powerStatus}`);
|
||||
console.log('└──────────────────────────────────────────┘');
|
||||
lastStatus = status.powerStatus;
|
||||
}
|
||||
|
||||
// Handle battery power status
|
||||
if (status.powerStatus === 'onBattery') {
|
||||
await this.handleOnBatteryStatus(status);
|
||||
}
|
||||
|
||||
// Wait before next check
|
||||
await this.sleep(this.config.checkInterval);
|
||||
} catch (error) {
|
||||
console.error('Error during UPS monitoring:', error);
|
||||
await this.sleep(this.config.checkInterval);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('UPS monitoring stopped');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle UPS status when running on battery
|
||||
*/
|
||||
private async handleOnBatteryStatus(status: {
|
||||
powerStatus: string,
|
||||
batteryCapacity: number,
|
||||
batteryRuntime: number
|
||||
}): Promise<void> {
|
||||
console.log('┌─ UPS Status ───────────────────────────────┐');
|
||||
console.log(`│ Battery: ${status.batteryCapacity}% | Runtime: ${status.batteryRuntime} min │`);
|
||||
console.log('└──────────────────────────────────────────┘');
|
||||
|
||||
// Check battery threshold
|
||||
if (status.batteryCapacity < this.config.thresholds.battery) {
|
||||
console.log('⚠️ WARNING: Battery capacity below threshold');
|
||||
console.log(`Current: ${status.batteryCapacity}% | Threshold: ${this.config.thresholds.battery}%`);
|
||||
await this.snmp.initiateShutdown('Battery capacity below threshold');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check runtime threshold
|
||||
if (status.batteryRuntime < this.config.thresholds.runtime) {
|
||||
console.log('⚠️ WARNING: Runtime below threshold');
|
||||
console.log(`Current: ${status.batteryRuntime} min | Threshold: ${this.config.thresholds.runtime} min`);
|
||||
await this.snmp.initiateShutdown('Runtime below threshold');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sleep for the specified milliseconds
|
||||
*/
|
||||
private sleep(ms: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user