246 lines
8.3 KiB
TypeScript
246 lines
8.3 KiB
TypeScript
import { promises as fs } from 'fs';
|
|
import { execSync } from 'child_process';
|
|
import { NupstDaemon } from './daemon.js';
|
|
|
|
/**
|
|
* Class for managing systemd service
|
|
* Handles installation, removal, and control of the NUPST systemd service
|
|
*/
|
|
export class NupstSystemd {
|
|
/** Path to the systemd service file */
|
|
private readonly serviceFilePath = '/etc/systemd/system/nupst.service';
|
|
private readonly daemon: NupstDaemon;
|
|
|
|
/** Template for the systemd service file */
|
|
private readonly serviceTemplate = `[Unit]
|
|
Description=Node.js UPS Shutdown Tool
|
|
After=network.target
|
|
|
|
[Service]
|
|
ExecStart=/usr/bin/nupst daemon-start
|
|
Restart=always
|
|
User=root
|
|
Group=root
|
|
Environment=PATH=/usr/bin:/usr/local/bin
|
|
Environment=NODE_ENV=production
|
|
WorkingDirectory=/tmp
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
`;
|
|
|
|
/**
|
|
* Create a new systemd manager
|
|
* @param daemon The daemon instance to manage
|
|
*/
|
|
constructor(daemon: NupstDaemon) {
|
|
this.daemon = daemon;
|
|
}
|
|
|
|
/**
|
|
* Check if a configuration file exists
|
|
* @private
|
|
* @throws Error if configuration not found
|
|
*/
|
|
private async checkConfigExists(): Promise<void> {
|
|
const configPath = '/etc/nupst/config.json';
|
|
try {
|
|
await fs.access(configPath);
|
|
} catch (error) {
|
|
console.error('┌─ Configuration Error ─────────────────────┐');
|
|
console.error(`│ No configuration file found at ${configPath}`);
|
|
console.error('│ Please run \'nupst setup\' first to create a configuration.');
|
|
console.error('└──────────────────────────────────────────┘');
|
|
throw new Error('Configuration not found');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Install the systemd service file
|
|
* @throws Error if installation fails
|
|
*/
|
|
public async install(): Promise<void> {
|
|
try {
|
|
// Check if configuration exists before installing
|
|
await this.checkConfigExists();
|
|
|
|
// Write the service file
|
|
await fs.writeFile(this.serviceFilePath, this.serviceTemplate);
|
|
console.log('┌─ Service Installation ─────────────────────┐');
|
|
console.log(`│ Service file created at ${this.serviceFilePath}`);
|
|
|
|
// Reload systemd daemon
|
|
execSync('systemctl daemon-reload');
|
|
console.log('│ Systemd daemon reloaded');
|
|
|
|
// Enable the service
|
|
execSync('systemctl enable nupst.service');
|
|
console.log('│ Service enabled to start on boot');
|
|
console.log('└──────────────────────────────────────────┘');
|
|
} catch (error) {
|
|
if (error.message === 'Configuration not found') {
|
|
// Just rethrow the error as the message has already been displayed
|
|
throw error;
|
|
}
|
|
console.error('Failed to install systemd service:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Start the systemd service
|
|
* @throws Error if start fails
|
|
*/
|
|
public async start(): Promise<void> {
|
|
try {
|
|
// Check if configuration exists before starting
|
|
await this.checkConfigExists();
|
|
|
|
execSync('systemctl start nupst.service');
|
|
console.log('┌─ Service Status ─────────────────────────┐');
|
|
console.log('│ NUPST service started successfully');
|
|
console.log('└──────────────────────────────────────────┘');
|
|
} catch (error) {
|
|
if (error.message === 'Configuration not found') {
|
|
// Exit with error code since configuration is required
|
|
process.exit(1);
|
|
}
|
|
console.error('Failed to start service:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Stop the systemd service
|
|
* @throws Error if stop fails
|
|
*/
|
|
public async stop(): Promise<void> {
|
|
try {
|
|
execSync('systemctl stop nupst.service');
|
|
console.log('NUPST service stopped');
|
|
} catch (error) {
|
|
console.error('Failed to stop service:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get status of the systemd service and UPS
|
|
*/
|
|
public async getStatus(): Promise<void> {
|
|
try {
|
|
// Check if config exists first
|
|
try {
|
|
await this.checkConfigExists();
|
|
} catch (error) {
|
|
// Error message already displayed by checkConfigExists
|
|
return;
|
|
}
|
|
|
|
await this.displayServiceStatus();
|
|
await this.displayUpsStatus();
|
|
} catch (error) {
|
|
console.error(`Failed to get status: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Display the systemd service status
|
|
* @private
|
|
*/
|
|
private async displayServiceStatus(): Promise<void> {
|
|
try {
|
|
const serviceStatus = execSync('systemctl status nupst.service').toString();
|
|
console.log('┌─ Service Status ─────────────────────────┐');
|
|
console.log(serviceStatus.split('\n').map(line => `│ ${line}`).join('\n'));
|
|
console.log('└──────────────────────────────────────────┘');
|
|
} catch (error) {
|
|
console.error('┌─ Service Status ─────────────────────────┐');
|
|
console.error('│ Service is not running');
|
|
console.error('└──────────────────────────────────────────┘');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Display the UPS status
|
|
* @private
|
|
*/
|
|
private async displayUpsStatus(): Promise<void> {
|
|
try {
|
|
const upsStatus = await this.daemon.getConfig().snmp;
|
|
const snmp = this.daemon.getNupstSnmp();
|
|
const status = await snmp.getUpsStatus(upsStatus);
|
|
|
|
console.log('┌─ UPS Status ───────────────────────────────┐');
|
|
console.log(`│ Power Status: ${status.powerStatus}`);
|
|
console.log(`│ Battery Capacity: ${status.batteryCapacity}%`);
|
|
console.log(`│ Runtime Remaining: ${status.batteryRuntime} minutes`);
|
|
console.log('└──────────────────────────────────────────┘');
|
|
} catch (error) {
|
|
console.error('┌─ UPS Status ───────────────────────────────┐');
|
|
console.error(`│ Failed to retrieve UPS status: ${error.message}`);
|
|
console.error('└──────────────────────────────────────────┘');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Disable and uninstall the systemd service
|
|
* @throws Error if disabling fails
|
|
*/
|
|
public async disable(): Promise<void> {
|
|
try {
|
|
await this.stopService();
|
|
await this.disableService();
|
|
await this.removeServiceFile();
|
|
|
|
// Reload systemd daemon
|
|
execSync('systemctl daemon-reload');
|
|
console.log('Systemd daemon reloaded');
|
|
console.log('NUPST service has been successfully uninstalled');
|
|
} catch (error) {
|
|
console.error('Failed to disable and uninstall service:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Stop the service if it's running
|
|
* @private
|
|
*/
|
|
private async stopService(): Promise<void> {
|
|
try {
|
|
console.log('Stopping NUPST service...');
|
|
execSync('systemctl stop nupst.service');
|
|
} catch (error) {
|
|
// Service might not be running, that's okay
|
|
console.log('Service was not running or could not be stopped');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Disable the service
|
|
* @private
|
|
*/
|
|
private async disableService(): Promise<void> {
|
|
try {
|
|
console.log('Disabling NUPST service...');
|
|
execSync('systemctl disable nupst.service');
|
|
} catch (error) {
|
|
console.log('Service was not enabled or could not be disabled');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove the service file if it exists
|
|
* @private
|
|
*/
|
|
private async removeServiceFile(): Promise<void> {
|
|
if (await fs.stat(this.serviceFilePath).catch(() => null)) {
|
|
console.log(`Removing service file ${this.serviceFilePath}...`);
|
|
await fs.unlink(this.serviceFilePath);
|
|
console.log('Service file removed');
|
|
} else {
|
|
console.log('Service file did not exist');
|
|
}
|
|
}
|
|
} |