nupst/ts/cli/service-handler.ts

320 lines
10 KiB
TypeScript

import { execSync } from 'child_process';
import { Nupst } from '../nupst.js';
import { logger } from '../logger.js';
/**
* Class for handling service-related CLI commands
* Provides interface for managing systemd service
*/
export class ServiceHandler {
private readonly nupst: Nupst;
/**
* Create a new Service handler
* @param nupst Reference to the main Nupst instance
*/
constructor(nupst: Nupst) {
this.nupst = nupst;
}
/**
* Enable the service (requires root)
*/
public async enable(): Promise<void> {
this.checkRootAccess('This command must be run as root.');
await this.nupst.getSystemd().install();
logger.log('NUPST service has been installed. Use "nupst start" to start the service.');
}
/**
* Start the daemon directly
* @param debugMode Whether to enable debug mode
*/
public async daemonStart(debugMode: boolean = false): Promise<void> {
logger.log('Starting NUPST daemon...');
try {
// Enable debug mode for SNMP if requested
if (debugMode) {
this.nupst.getSnmp().enableDebug();
logger.log('SNMP debug mode enabled');
}
await this.nupst.getDaemon().start();
} catch (error) {
// Error is already logged and process.exit is called in daemon.start()
// No need to handle it here
}
}
/**
* Show logs of the systemd service
*/
public async logs(): Promise<void> {
try {
// Use exec with spawn to properly follow logs in real-time
const { spawn } = await import('child_process');
logger.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) {
logger.error(`Failed to retrieve logs: ${error}`);
process.exit(1);
}
}
/**
* Stop the systemd service
*/
public async stop(): Promise<void> {
await this.nupst.getSystemd().stop();
}
/**
* Start the systemd service
*/
public async start(): Promise<void> {
try {
await this.nupst.getSystemd().start();
} catch (error) {
// Error will be displayed by systemd.start()
process.exit(1);
}
}
/**
* Show status of the systemd service and UPS
*/
public async status(): Promise<void> {
// Extract debug options from args array
const debugOptions = this.extractDebugOptions(process.argv);
await this.nupst.getSystemd().getStatus(debugOptions.debugMode);
}
/**
* Disable the service (requires root)
*/
public async disable(): Promise<void> {
this.checkRootAccess('This command must be run as root.');
await this.nupst.getSystemd().disable();
}
/**
* Check if the user has root access
* @param errorMessage Error message to display if not root
*/
private checkRootAccess(errorMessage: string): void {
if (process.getuid && process.getuid() !== 0) {
logger.error(errorMessage);
process.exit(1);
}
}
/**
* Update NUPST from repository and refresh systemd service
*/
public 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.'
);
const boxWidth = 45;
logger.logBoxTitle('NUPST Update Process', boxWidth);
logger.logBoxLine('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
logger.logBoxLine(`Using local installation directory: ${installDir}`);
}
try {
// 1. Update the repository
logger.logBoxLine('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
logger.logBoxLine('Running install.sh to update NUPST...');
execSync(`cd ${installDir} && bash ./install.sh`, { stdio: 'pipe' });
// 3. Run the setup.sh script with force flag to update Node.js and dependencies
logger.logBoxLine('Running setup.sh to update Node.js and dependencies...');
execSync(`cd ${installDir} && bash ./setup.sh --force`, { stdio: 'pipe' });
// 4. Refresh the systemd service
logger.logBoxLine('Refreshing systemd service...');
// First check if service exists
let serviceExists = false;
try {
const output = execSync('systemctl list-unit-files | grep nupst.service').toString();
serviceExists = output.includes('nupst.service');
} catch (error) {
// If grep fails (service not found), serviceExists remains false
serviceExists = false;
}
if (serviceExists) {
// Stop the service if it's running
const isRunning =
execSync('systemctl is-active nupst.service || true').toString().trim() === 'active';
if (isRunning) {
logger.logBoxLine('Stopping nupst service...');
execSync('systemctl stop nupst.service');
}
// Reinstall the service
logger.logBoxLine('Reinstalling systemd service...');
await this.nupst.getSystemd().install();
// Restart the service if it was running
if (isRunning) {
logger.logBoxLine('Restarting nupst service...');
execSync('systemctl start nupst.service');
}
} else {
logger.logBoxLine('Systemd service not installed, skipping service refresh.');
logger.logBoxLine('Run "nupst enable" to install the service.');
}
logger.logBoxLine('Update completed successfully!');
logger.logBoxEnd();
} catch (error) {
logger.logBoxLine('Error during update process:');
logger.logBoxLine(`${error.message}`);
logger.logBoxEnd();
process.exit(1);
}
} catch (error) {
logger.error(`Update failed: ${error.message}`);
process.exit(1);
}
}
/**
* Completely uninstall NUPST from the system
*/
public async uninstall(): Promise<void> {
// Check if running as root
this.checkRootAccess('This command must be run as root.');
try {
// Import readline module for user input
const readline = await import('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
// Helper function to prompt for input
const prompt = (question: string): Promise<string> => {
return new Promise((resolve) => {
rl.question(question, (answer: string) => {
resolve(answer);
});
});
};
console.log('\nNUPST Uninstaller');
console.log('===============');
console.log('This will completely remove NUPST from your system.\n');
// Ask about removing configuration
const removeConfig = await prompt(
'Do you want to remove the NUPST configuration files? (y/N): '
);
// Find the uninstall.sh script location
let uninstallScriptPath: string;
// Try to determine script location based on executable path
try {
// For ESM, we can use import.meta.url, but since we might be in CJS
// we'll use a more reliable approach based on process.argv[1]
const binPath = process.argv[1];
const { dirname, join } = await import('path');
const modulePath = dirname(dirname(binPath));
uninstallScriptPath = join(modulePath, 'uninstall.sh');
// Check if the script exists
const { access } = await import('fs/promises');
await access(uninstallScriptPath);
} catch (error) {
// If we can't find it in the expected location, try common installation paths
const commonPaths = ['/opt/nupst/uninstall.sh', `${process.cwd()}/uninstall.sh`];
const { existsSync } = await import('fs');
uninstallScriptPath = '';
for (const path of commonPaths) {
if (existsSync(path)) {
uninstallScriptPath = path;
break;
}
}
if (!uninstallScriptPath) {
console.error('Could not locate uninstall.sh script. Aborting uninstall.');
rl.close();
process.exit(1);
}
}
// Close readline before executing script
rl.close();
// Execute uninstall.sh with the appropriate option
console.log(`\nRunning uninstaller from ${uninstallScriptPath}...`);
// Pass the configuration removal option as an environment variable
const env = {
...process.env,
REMOVE_CONFIG: removeConfig.toLowerCase() === 'y' ? 'yes' : 'no',
REMOVE_REPO: 'yes', // Always remove repo as requested
NUPST_CLI_CALL: 'true', // Flag to indicate this is being called from CLI
};
// Run the uninstall script with sudo
execSync(`sudo bash ${uninstallScriptPath}`, {
env,
stdio: 'inherit', // Show output in the terminal
});
} catch (error) {
console.error(`Uninstall failed: ${error.message}`);
process.exit(1);
}
}
/**
* Extract and remove debug options from args array
* @param args Command line arguments
* @returns Object with debug flags and cleaned args
*/
private extractDebugOptions(args: string[]): { debugMode: boolean; cleanedArgs: string[] } {
const debugMode = args.includes('--debug') || args.includes('-d');
// Remove debug flags from args
const cleanedArgs = args.filter((arg) => arg !== '--debug' && arg !== '-d');
return { debugMode, cleanedArgs };
}
}