320 lines
10 KiB
TypeScript
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 };
|
|
}
|
|
} |