feat(systemd-manager): Support sudo password and root detection in SystemdManager; add user/group support in services and templates; add tests and expand README

This commit is contained in:
2025-09-03 11:16:45 +00:00
parent 0ae3b9a5ec
commit 972688b8be
9 changed files with 552 additions and 78 deletions

View File

@@ -31,13 +31,29 @@ export class SmartDaemonSystemdManager {
public smartsystem: plugins.smartsystem.Smartsystem;
public shouldExecute: boolean = false;
public isRoot: boolean = false;
public sudoPassword?: string;
constructor(smartdaemonRefArg: SmartDaemon) {
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<boolean> {
const result = await this.smartshellInstance.exec('id -u');
return result.stdout.trim() === '0';
}
public async checkElegibility() {
@@ -52,7 +68,28 @@ export class SmartDaemonSystemdManager {
public async execute(commandArg: string) {
if (await this.checkElegibility()) {
await this.smartshellInstance.exec(commandArg);
// 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;
}
}
}
@@ -101,18 +138,31 @@ export class SmartDaemonSystemdManager {
public async saveService(serviceArg: SmartDaemonService) {
if (await this.checkElegibility()) {
await plugins.smartfile.memory.toFs(
this.smartdaemonRef.templateManager.generateUnitFileForService(serviceArg),
SmartDaemonSystemdManager.createFilePathFromServiceName(serviceArg.name)
);
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()) {
await plugins.smartfile.fs.remove(
SmartDaemonSystemdManager.createServiceNameFromServiceName(serviceArg.name)
);
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
}
}
}