initial
This commit is contained in:
246
ts/systemd.ts
Normal file
246
ts/systemd.ts
Normal file
@@ -0,0 +1,246 @@
|
||||
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');
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user