import * as plugins from './smartdaemon.plugins.js'; import * as paths from './smartdaemon.paths.js'; import { SmartDaemon } from './smartdaemon.classes.smartdaemon.js'; import { type ISmartDaemonServiceConstructorOptions, SmartDaemonService, } from './smartdaemon.classes.service.js'; export class SmartDaemonSystemdManager { // STATIC private static smartDaemonNamespace = 'smartdaemon'; public static createServiceNameFromServiceName(serviceNameArg: string) { return `${SmartDaemonSystemdManager.smartDaemonNamespace}_${serviceNameArg}`; } public static createFileNameFromServiceName(serviceNameArg: string) { return `${SmartDaemonSystemdManager.createServiceNameFromServiceName(serviceNameArg)}.service`; } public static createFilePathFromServiceName(serviceNameArg: string) { return plugins.path.join( paths.systemdDir, SmartDaemonSystemdManager.createFileNameFromServiceName(serviceNameArg) ); } // INSTANCE public smartdaemonRef: SmartDaemon; public smartshellInstance: plugins.smartshell.Smartshell; public smartsystem: plugins.smartsystem.Smartsystem; public shouldExecute: boolean = false; public isRoot: boolean = false; public sudoPassword?: string; constructor(smartdaemonRefArg: SmartDaemon, sudoPasswordArg?: string) { this.smartdaemonRef = smartdaemonRefArg; this.smartshellInstance = new plugins.smartshell.Smartshell({ executor: 'bash', }); this.smartsystem = new plugins.smartsystem.Smartsystem(); this.sudoPassword = sudoPasswordArg; // Check if we're running as root this.checkIsRoot().then(isRoot => { this.isRoot = isRoot; if (!isRoot) { console.log('Not running as root. Sudo will be used for privileged operations.'); } }); } private async checkIsRoot(): Promise { const result = await this.smartshellInstance.exec('id -u'); return result.stdout.trim() === '0'; } public async checkElegibility() { if (await this.smartsystem.env.isLinuxAsync()) { this.shouldExecute = true; } else { console.log('Smartdaemon can only be used on Linux systems! Refusing to set up a service.'); this.shouldExecute = false; } return this.shouldExecute; } public async execute(commandArg: string) { if (await this.checkElegibility()) { // Only use sudo if we're not root and command doesn't already have sudo if (!this.isRoot && !commandArg.startsWith('sudo')) { commandArg = `sudo ${commandArg}`; if (this.sudoPassword) { // Use interactive PTY mode for password input const interactiveExec = await this.smartshellInstance.execInteractiveControlPty(commandArg); await interactiveExec.sendLine(this.sudoPassword); const result = await interactiveExec.finalPromise; return result; } } // Execute command (with or without sudo) try { await this.smartshellInstance.exec(commandArg); } catch (error) { if (!this.isRoot && error.message && error.message.includes('authentication')) { throw new Error('Sudo authentication failed. Please configure passwordless sudo or provide a sudo password.'); } throw error; } } } /** * gets all services that are already present */ public async getServices() { const existingServices: SmartDaemonService[] = []; if (await this.checkElegibility()) { const smartfmInstance = new plugins.smartfm.Smartfm({ fmType: 'yaml', }); const availableServices = await plugins.smartfile.fs.fileTreeToObject( paths.systemdDir, 'smartdaemon_*.service' ); for (const serviceFile of availableServices) { const data = smartfmInstance.parseFromComments('# ', serviceFile.contentBuffer.toString()) .data as ISmartDaemonServiceConstructorOptions; const service = await SmartDaemonService.createFromOptions(this.smartdaemonRef, data); service.alreadyExists = true; existingServices.push(service); } } return existingServices; } public async startService(serviceArg: SmartDaemonService) { if (await this.checkElegibility()) { await this.execute( `systemctl start ${SmartDaemonSystemdManager.createServiceNameFromServiceName( serviceArg.name )}` ); } } public async stopService(serviceArg: SmartDaemonService) { if (await this.checkElegibility()) { await this.execute( `systemctl stop ${SmartDaemonSystemdManager.createServiceNameFromServiceName( serviceArg.name )}` ); } } public async saveService(serviceArg: SmartDaemonService) { if (await this.checkElegibility()) { const content = this.smartdaemonRef.templateManager.generateUnitFileForService(serviceArg); const targetPath = SmartDaemonSystemdManager.createFilePathFromServiceName(serviceArg.name); if (this.isRoot) { // Direct write when running as root await plugins.smartfile.memory.toFs(content, targetPath); } else { // Use sudo to write when not root const tempPath = `/tmp/smartdaemon_${serviceArg.name}.service`; await plugins.smartfile.memory.toFs(content, tempPath); await this.execute(`mv ${tempPath} ${targetPath}`); // execute() will add sudo await this.execute(`chmod 644 ${targetPath}`); // execute() will add sudo } } } public async deleteService(serviceArg: SmartDaemonService) { if (await this.checkElegibility()) { const filePath = SmartDaemonSystemdManager.createFilePathFromServiceName(serviceArg.name); if (this.isRoot) { await plugins.smartfile.fs.remove(filePath); } else { await this.execute(`rm ${filePath}`); // execute() will add sudo } } } public async enableService(serviceArg: SmartDaemonService) { if (await this.checkElegibility()) { await this.saveService(serviceArg); if (serviceArg.alreadyExists) { await this.execute(`systemctl daemon-reload`); } await this.execute( `systemctl enable ${SmartDaemonSystemdManager.createServiceNameFromServiceName( serviceArg.name )}` ); } } public async disableService(serviceArg: SmartDaemonService) { if (await this.checkElegibility()) { await this.execute( `systemctl disable ${SmartDaemonSystemdManager.createServiceNameFromServiceName( serviceArg.name )}` ); } } public async reload() { if (await this.checkElegibility()) { await this.execute(`systemctl daemon-reload`); } } }