@push.rocks/smartdaemon 2.0.8 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartdaemon',
6
- version: '2.0.8',
6
+ version: '2.1.0',
7
7
  description: 'Start scripts as long running daemons and manage them.'
8
8
  };
9
9
  //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSx5QkFBeUI7SUFDL0IsT0FBTyxFQUFFLE9BQU87SUFDaEIsV0FBVyxFQUFFLHdEQUF3RDtDQUN0RSxDQUFBIn0=
@@ -1 +1,2 @@
1
1
  export * from './smartdaemon.classes.smartdaemon.js';
2
+ export * from './smartdaemon.classes.service.js';
package/dist_ts/index.js CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from './smartdaemon.classes.smartdaemon.js';
2
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxjQUFjLHNDQUFzQyxDQUFDIn0=
2
+ export * from './smartdaemon.classes.service.js';
3
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxjQUFjLHNDQUFzQyxDQUFDO0FBQ3JELGNBQWMsa0NBQWtDLENBQUMifQ==
@@ -5,6 +5,8 @@ export interface ISmartDaemonServiceConstructorOptions {
5
5
  command: string;
6
6
  workingDir: string;
7
7
  version: string;
8
+ user?: string;
9
+ group?: string;
8
10
  }
9
11
  /**
10
12
  * represents a service that is being spawned by SmartDaemon
@@ -18,6 +20,8 @@ export declare class SmartDaemonService implements ISmartDaemonServiceConstructo
18
20
  command: string;
19
21
  workingDir: string;
20
22
  description: string;
23
+ user?: string;
24
+ group?: string;
21
25
  smartdaemonRef: SmartDaemon;
22
26
  constructor(smartdaemonRegfArg: SmartDaemon);
23
27
  /**
@@ -54,4 +54,4 @@ export class SmartDaemonService {
54
54
  await this.smartdaemonRef.systemdManager.reload();
55
55
  }
56
56
  }
57
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRkYWVtb24uY2xhc3Nlcy5zZXJ2aWNlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvc21hcnRkYWVtb24uY2xhc3Nlcy5zZXJ2aWNlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sMEJBQTBCLENBQUM7QUFDcEQsT0FBTyxLQUFLLEtBQUssTUFBTSx3QkFBd0IsQ0FBQztBQUNoRCxPQUFPLEVBQUUsV0FBVyxFQUFFLE1BQU0sc0NBQXNDLENBQUM7QUFVbkU7O0dBRUc7QUFDSCxNQUFNLE9BQU8sa0JBQWtCO0lBQ3RCLE1BQU0sQ0FBQyxLQUFLLENBQUMsaUJBQWlCLENBQ25DLGNBQTJCLEVBQzNCLFVBQWlEO1FBRWpELE1BQU0sT0FBTyxHQUFHLElBQUksa0JBQWtCLENBQUMsY0FBYyxDQUFDLENBQUM7UUFDdkQsS0FBSyxNQUFNLEdBQUcsSUFBSSxNQUFNLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxFQUFFLENBQUM7WUFDMUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxHQUFHLFVBQVUsQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUNqQyxDQUFDO1FBQ0QsT0FBTyxPQUFPLENBQUM7SUFDakIsQ0FBQztJQWFELFlBQVksa0JBQStCO1FBVnBDLGtCQUFhLEdBQUcsS0FBSyxDQUFDO1FBVzNCLElBQUksQ0FBQyxjQUFjLEdBQUcsa0JBQWtCLENBQUM7SUFDM0MsQ0FBQztJQUVEOztPQUVHO0lBQ0ksS0FBSyxDQUFDLE1BQU07UUFDakIsTUFBTSxJQUFJLENBQUMsY0FBYyxDQUFDLGNBQWMsQ0FBQyxhQUFhLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDL0QsQ0FBQztJQUVEOztPQUVHO0lBQ0ksS0FBSyxDQUFDLE9BQU87UUFDbEIsTUFBTSxJQUFJLENBQUMsY0FBYyxDQUFDLGNBQWMsQ0FBQyxjQUFjLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDaEUsQ0FBQztJQUVEOztPQUVHO0lBQ0ksS0FBSyxDQUFDLEtBQUs7UUFDaEIsTUFBTSxJQUFJLENBQUMsY0FBYyxDQUFDLGNBQWMsQ0FBQyxZQUFZLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDOUQsQ0FBQztJQUVEOztPQUVHO0lBQ0ksS0FBSyxDQUFDLElBQUk7UUFDZixNQUFNLElBQUksQ0FBQyxjQUFjLENBQUMsY0FBYyxDQUFDLFdBQVcsQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUM3RCxDQUFDO0lBRUQsa0JBQWtCO0lBQ1gsS0FBSyxDQUFDLElBQUk7UUFDZixNQUFNLElBQUksQ0FBQyxjQUFjLENBQUMsY0FBYyxDQUFDLFdBQVcsQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUM3RCxDQUFDO0lBRUQ7O09BRUc7SUFDSSxLQUFLLENBQUMsTUFBTTtRQUNqQixNQUFNLElBQUksQ0FBQyxjQUFjLENBQUMsY0FBYyxDQUFDLGFBQWEsQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUMvRCxDQUFDO0lBRU0sS0FBSyxDQUFDLE1BQU07UUFDakIsTUFBTSxJQUFJLENBQUMsY0FBYyxDQUFDLGNBQWMsQ0FBQyxNQUFNLEVBQUUsQ0FBQztJQUNwRCxDQUFDO0NBQ0YifQ==
57
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRkYWVtb24uY2xhc3Nlcy5zZXJ2aWNlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvc21hcnRkYWVtb24uY2xhc3Nlcy5zZXJ2aWNlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sMEJBQTBCLENBQUM7QUFDcEQsT0FBTyxLQUFLLEtBQUssTUFBTSx3QkFBd0IsQ0FBQztBQUNoRCxPQUFPLEVBQUUsV0FBVyxFQUFFLE1BQU0sc0NBQXNDLENBQUM7QUFZbkU7O0dBRUc7QUFDSCxNQUFNLE9BQU8sa0JBQWtCO0lBQ3RCLE1BQU0sQ0FBQyxLQUFLLENBQUMsaUJBQWlCLENBQ25DLGNBQTJCLEVBQzNCLFVBQWlEO1FBRWpELE1BQU0sT0FBTyxHQUFHLElBQUksa0JBQWtCLENBQUMsY0FBYyxDQUFDLENBQUM7UUFDdkQsS0FBSyxNQUFNLEdBQUcsSUFBSSxNQUFNLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxFQUFFLENBQUM7WUFDMUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxHQUFHLFVBQVUsQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUNqQyxDQUFDO1FBQ0QsT0FBTyxPQUFPLENBQUM7SUFDakIsQ0FBQztJQWVELFlBQVksa0JBQStCO1FBWnBDLGtCQUFhLEdBQUcsS0FBSyxDQUFDO1FBYTNCLElBQUksQ0FBQyxjQUFjLEdBQUcsa0JBQWtCLENBQUM7SUFDM0MsQ0FBQztJQUVEOztPQUVHO0lBQ0ksS0FBSyxDQUFDLE1BQU07UUFDakIsTUFBTSxJQUFJLENBQUMsY0FBYyxDQUFDLGNBQWMsQ0FBQyxhQUFhLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDL0QsQ0FBQztJQUVEOztPQUVHO0lBQ0ksS0FBSyxDQUFDLE9BQU87UUFDbEIsTUFBTSxJQUFJLENBQUMsY0FBYyxDQUFDLGNBQWMsQ0FBQyxjQUFjLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDaEUsQ0FBQztJQUVEOztPQUVHO0lBQ0ksS0FBSyxDQUFDLEtBQUs7UUFDaEIsTUFBTSxJQUFJLENBQUMsY0FBYyxDQUFDLGNBQWMsQ0FBQyxZQUFZLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDOUQsQ0FBQztJQUVEOztPQUVHO0lBQ0ksS0FBSyxDQUFDLElBQUk7UUFDZixNQUFNLElBQUksQ0FBQyxjQUFjLENBQUMsY0FBYyxDQUFDLFdBQVcsQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUM3RCxDQUFDO0lBRUQsa0JBQWtCO0lBQ1gsS0FBSyxDQUFDLElBQUk7UUFDZixNQUFNLElBQUksQ0FBQyxjQUFjLENBQUMsY0FBYyxDQUFDLFdBQVcsQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUM3RCxDQUFDO0lBRUQ7O09BRUc7SUFDSSxLQUFLLENBQUMsTUFBTTtRQUNqQixNQUFNLElBQUksQ0FBQyxjQUFjLENBQUMsY0FBYyxDQUFDLGFBQWEsQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUMvRCxDQUFDO0lBRU0sS0FBSyxDQUFDLE1BQU07UUFDakIsTUFBTSxJQUFJLENBQUMsY0FBYyxDQUFDLGNBQWMsQ0FBQyxNQUFNLEVBQUUsQ0FBQztJQUNwRCxDQUFDO0NBQ0YifQ==
@@ -1,10 +1,13 @@
1
1
  import { SmartDaemonTemplateManager } from './smartdaemon.classes.templatemanager.js';
2
2
  import { type ISmartDaemonServiceConstructorOptions, SmartDaemonService } from './smartdaemon.classes.service.js';
3
3
  import { SmartDaemonSystemdManager } from './smartdaemon.classes.systemdmanager.js';
4
+ export interface ISmartDaemonOptions {
5
+ sudoPassword?: string;
6
+ }
4
7
  export declare class SmartDaemon {
5
8
  templateManager: SmartDaemonTemplateManager;
6
9
  systemdManager: SmartDaemonSystemdManager;
7
- constructor();
10
+ constructor(optionsArg?: ISmartDaemonOptions);
8
11
  /**
9
12
  * adds a service
10
13
  * @param nameArg
@@ -3,9 +3,9 @@ import { SmartDaemonTemplateManager } from './smartdaemon.classes.templatemanage
3
3
  import { SmartDaemonService, } from './smartdaemon.classes.service.js';
4
4
  import { SmartDaemonSystemdManager } from './smartdaemon.classes.systemdmanager.js';
5
5
  export class SmartDaemon {
6
- constructor() {
6
+ constructor(optionsArg) {
7
7
  this.templateManager = new SmartDaemonTemplateManager(this);
8
- this.systemdManager = new SmartDaemonSystemdManager(this);
8
+ this.systemdManager = new SmartDaemonSystemdManager(this, optionsArg?.sudoPassword);
9
9
  }
10
10
  /**
11
11
  * adds a service
@@ -30,4 +30,4 @@ export class SmartDaemon {
30
30
  return serviceToAdd;
31
31
  }
32
32
  }
33
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRkYWVtb24uY2xhc3Nlcy5zbWFydGRhZW1vbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL3NtYXJ0ZGFlbW9uLmNsYXNzZXMuc21hcnRkYWVtb24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSwwQkFBMEIsQ0FBQztBQUNwRCxPQUFPLEVBQUUsMEJBQTBCLEVBQUUsTUFBTSwwQ0FBMEMsQ0FBQztBQUN0RixPQUFPLEVBRUwsa0JBQWtCLEdBQ25CLE1BQU0sa0NBQWtDLENBQUM7QUFDMUMsT0FBTyxFQUFFLHlCQUF5QixFQUFFLE1BQU0seUNBQXlDLENBQUM7QUFFcEYsTUFBTSxPQUFPLFdBQVc7SUFJdEI7UUFDRSxJQUFJLENBQUMsZUFBZSxHQUFHLElBQUksMEJBQTBCLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDNUQsSUFBSSxDQUFDLGNBQWMsR0FBRyxJQUFJLHlCQUF5QixDQUFDLElBQUksQ0FBQyxDQUFDO0lBQzVELENBQUM7SUFFRDs7Ozs7T0FLRztJQUNJLEtBQUssQ0FBQyxVQUFVLENBQ3JCLFVBQWlEO1FBRWpELElBQUksWUFBZ0MsQ0FBQztRQUNyQyxNQUFNLGdCQUFnQixHQUFHLE1BQU0sSUFBSSxDQUFDLGNBQWMsQ0FBQyxXQUFXLEVBQUUsQ0FBQztRQUNqRSxNQUFNLGVBQWUsR0FBRyxnQkFBZ0IsQ0FBQyxJQUFJLENBQUMsQ0FBQyxVQUFVLEVBQUUsRUFBRTtZQUMzRCxPQUFPLFVBQVUsQ0FBQyxJQUFJLEtBQUssVUFBVSxDQUFDLElBQUksQ0FBQztRQUM3QyxDQUFDLENBQUMsQ0FBQztRQUNILElBQUksQ0FBQyxlQUFlLEVBQUUsQ0FBQztZQUNyQixZQUFZLEdBQUcsTUFBTSxrQkFBa0IsQ0FBQyxpQkFBaUIsQ0FBQyxJQUFJLEVBQUUsVUFBVSxDQUFDLENBQUM7UUFDOUUsQ0FBQzthQUFNLENBQUM7WUFDTixZQUFZLEdBQUcsZUFBZSxDQUFDO1lBQy9CLE1BQU0sQ0FBQyxNQUFNLENBQUMsWUFBWSxFQUFFLFVBQVUsQ0FBQyxDQUFDO1FBQzFDLENBQUM7UUFDRCxNQUFNLFlBQVksQ0FBQyxJQUFJLEVBQUUsQ0FBQztRQUMxQixPQUFPLFlBQVksQ0FBQztJQUN0QixDQUFDO0NBQ0YifQ==
33
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRkYWVtb24uY2xhc3Nlcy5zbWFydGRhZW1vbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL3NtYXJ0ZGFlbW9uLmNsYXNzZXMuc21hcnRkYWVtb24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSwwQkFBMEIsQ0FBQztBQUNwRCxPQUFPLEVBQUUsMEJBQTBCLEVBQUUsTUFBTSwwQ0FBMEMsQ0FBQztBQUN0RixPQUFPLEVBRUwsa0JBQWtCLEdBQ25CLE1BQU0sa0NBQWtDLENBQUM7QUFDMUMsT0FBTyxFQUFFLHlCQUF5QixFQUFFLE1BQU0seUNBQXlDLENBQUM7QUFNcEYsTUFBTSxPQUFPLFdBQVc7SUFJdEIsWUFBWSxVQUFnQztRQUMxQyxJQUFJLENBQUMsZUFBZSxHQUFHLElBQUksMEJBQTBCLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDNUQsSUFBSSxDQUFDLGNBQWMsR0FBRyxJQUFJLHlCQUF5QixDQUFDLElBQUksRUFBRSxVQUFVLEVBQUUsWUFBWSxDQUFDLENBQUM7SUFDdEYsQ0FBQztJQUVEOzs7OztPQUtHO0lBQ0ksS0FBSyxDQUFDLFVBQVUsQ0FDckIsVUFBaUQ7UUFFakQsSUFBSSxZQUFnQyxDQUFDO1FBQ3JDLE1BQU0sZ0JBQWdCLEdBQUcsTUFBTSxJQUFJLENBQUMsY0FBYyxDQUFDLFdBQVcsRUFBRSxDQUFDO1FBQ2pFLE1BQU0sZUFBZSxHQUFHLGdCQUFnQixDQUFDLElBQUksQ0FBQyxDQUFDLFVBQVUsRUFBRSxFQUFFO1lBQzNELE9BQU8sVUFBVSxDQUFDLElBQUksS0FBSyxVQUFVLENBQUMsSUFBSSxDQUFDO1FBQzdDLENBQUMsQ0FBQyxDQUFDO1FBQ0gsSUFBSSxDQUFDLGVBQWUsRUFBRSxDQUFDO1lBQ3JCLFlBQVksR0FBRyxNQUFNLGtCQUFrQixDQUFDLGlCQUFpQixDQUFDLElBQUksRUFBRSxVQUFVLENBQUMsQ0FBQztRQUM5RSxDQUFDO2FBQU0sQ0FBQztZQUNOLFlBQVksR0FBRyxlQUFlLENBQUM7WUFDL0IsTUFBTSxDQUFDLE1BQU0sQ0FBQyxZQUFZLEVBQUUsVUFBVSxDQUFDLENBQUM7UUFDMUMsQ0FBQztRQUNELE1BQU0sWUFBWSxDQUFDLElBQUksRUFBRSxDQUFDO1FBQzFCLE9BQU8sWUFBWSxDQUFDO0lBQ3RCLENBQUM7Q0FDRiJ9
@@ -10,9 +10,12 @@ export declare class SmartDaemonSystemdManager {
10
10
  smartshellInstance: plugins.smartshell.Smartshell;
11
11
  smartsystem: plugins.smartsystem.Smartsystem;
12
12
  shouldExecute: boolean;
13
- constructor(smartdaemonRefArg: SmartDaemon);
13
+ isRoot: boolean;
14
+ sudoPassword?: string;
15
+ constructor(smartdaemonRefArg: SmartDaemon, sudoPasswordArg?: string);
16
+ private checkIsRoot;
14
17
  checkElegibility(): Promise<boolean>;
15
- execute(commandArg: string): Promise<void>;
18
+ execute(commandArg: string): Promise<plugins.smartshell.IExecResult>;
16
19
  /**
17
20
  * gets all services that are already present
18
21
  */
@@ -14,13 +14,26 @@ export class SmartDaemonSystemdManager {
14
14
  static createFilePathFromServiceName(serviceNameArg) {
15
15
  return plugins.path.join(paths.systemdDir, SmartDaemonSystemdManager.createFileNameFromServiceName(serviceNameArg));
16
16
  }
17
- constructor(smartdaemonRefArg) {
17
+ constructor(smartdaemonRefArg, sudoPasswordArg) {
18
18
  this.shouldExecute = false;
19
+ this.isRoot = false;
19
20
  this.smartdaemonRef = smartdaemonRefArg;
20
21
  this.smartshellInstance = new plugins.smartshell.Smartshell({
21
22
  executor: 'bash',
22
23
  });
23
24
  this.smartsystem = new plugins.smartsystem.Smartsystem();
25
+ this.sudoPassword = sudoPasswordArg;
26
+ // Check if we're running as root
27
+ this.checkIsRoot().then(isRoot => {
28
+ this.isRoot = isRoot;
29
+ if (!isRoot) {
30
+ console.log('Not running as root. Sudo will be used for privileged operations.');
31
+ }
32
+ });
33
+ }
34
+ async checkIsRoot() {
35
+ const result = await this.smartshellInstance.exec('id -u');
36
+ return result.stdout.trim() === '0';
24
37
  }
25
38
  async checkElegibility() {
26
39
  if (await this.smartsystem.env.isLinuxAsync()) {
@@ -34,7 +47,27 @@ export class SmartDaemonSystemdManager {
34
47
  }
35
48
  async execute(commandArg) {
36
49
  if (await this.checkElegibility()) {
37
- await this.smartshellInstance.exec(commandArg);
50
+ // Only use sudo if we're not root and command doesn't already have sudo
51
+ if (!this.isRoot && !commandArg.startsWith('sudo')) {
52
+ commandArg = `sudo ${commandArg}`;
53
+ if (this.sudoPassword) {
54
+ // Use interactive PTY mode for password input
55
+ const interactiveExec = await this.smartshellInstance.execInteractiveControlPty(commandArg);
56
+ await interactiveExec.sendLine(this.sudoPassword);
57
+ const result = await interactiveExec.finalPromise;
58
+ return result;
59
+ }
60
+ }
61
+ // Execute command (with or without sudo)
62
+ try {
63
+ await this.smartshellInstance.exec(commandArg);
64
+ }
65
+ catch (error) {
66
+ if (!this.isRoot && error.message && error.message.includes('authentication')) {
67
+ throw new Error('Sudo authentication failed. Please configure passwordless sudo or provide a sudo password.');
68
+ }
69
+ throw error;
70
+ }
38
71
  }
39
72
  }
40
73
  /**
@@ -69,12 +102,30 @@ export class SmartDaemonSystemdManager {
69
102
  }
70
103
  async saveService(serviceArg) {
71
104
  if (await this.checkElegibility()) {
72
- await plugins.smartfile.memory.toFs(this.smartdaemonRef.templateManager.generateUnitFileForService(serviceArg), SmartDaemonSystemdManager.createFilePathFromServiceName(serviceArg.name));
105
+ const content = this.smartdaemonRef.templateManager.generateUnitFileForService(serviceArg);
106
+ const targetPath = SmartDaemonSystemdManager.createFilePathFromServiceName(serviceArg.name);
107
+ if (this.isRoot) {
108
+ // Direct write when running as root
109
+ await plugins.smartfile.memory.toFs(content, targetPath);
110
+ }
111
+ else {
112
+ // Use sudo to write when not root
113
+ const tempPath = `/tmp/smartdaemon_${serviceArg.name}.service`;
114
+ await plugins.smartfile.memory.toFs(content, tempPath);
115
+ await this.execute(`mv ${tempPath} ${targetPath}`); // execute() will add sudo
116
+ await this.execute(`chmod 644 ${targetPath}`); // execute() will add sudo
117
+ }
73
118
  }
74
119
  }
75
120
  async deleteService(serviceArg) {
76
121
  if (await this.checkElegibility()) {
77
- await plugins.smartfile.fs.remove(SmartDaemonSystemdManager.createServiceNameFromServiceName(serviceArg.name));
122
+ const filePath = SmartDaemonSystemdManager.createFilePathFromServiceName(serviceArg.name);
123
+ if (this.isRoot) {
124
+ await plugins.smartfile.fs.remove(filePath);
125
+ }
126
+ else {
127
+ await this.execute(`rm ${filePath}`); // execute() will add sudo
128
+ }
78
129
  }
79
130
  }
80
131
  async enableService(serviceArg) {
@@ -97,4 +148,4 @@ export class SmartDaemonSystemdManager {
97
148
  }
98
149
  }
99
150
  }
100
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRkYWVtb24uY2xhc3Nlcy5zeXN0ZW1kbWFuYWdlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL3NtYXJ0ZGFlbW9uLmNsYXNzZXMuc3lzdGVtZG1hbmFnZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSwwQkFBMEIsQ0FBQztBQUNwRCxPQUFPLEtBQUssS0FBSyxNQUFNLHdCQUF3QixDQUFDO0FBQ2hELE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSxzQ0FBc0MsQ0FBQztBQUNuRSxPQUFPLEVBRUwsa0JBQWtCLEdBQ25CLE1BQU0sa0NBQWtDLENBQUM7QUFFMUMsTUFBTSxPQUFPLHlCQUF5QjtJQUNwQyxTQUFTO2FBQ00seUJBQW9CLEdBQUcsYUFBYSxBQUFoQixDQUFpQjtJQUU3QyxNQUFNLENBQUMsZ0NBQWdDLENBQUMsY0FBc0I7UUFDbkUsT0FBTyxHQUFHLHlCQUF5QixDQUFDLG9CQUFvQixJQUFJLGNBQWMsRUFBRSxDQUFDO0lBQy9FLENBQUM7SUFFTSxNQUFNLENBQUMsNkJBQTZCLENBQUMsY0FBc0I7UUFDaEUsT0FBTyxHQUFHLHlCQUF5QixDQUFDLGdDQUFnQyxDQUFDLGNBQWMsQ0FBQyxVQUFVLENBQUM7SUFDakcsQ0FBQztJQUVNLE1BQU0sQ0FBQyw2QkFBNkIsQ0FBQyxjQUFzQjtRQUNoRSxPQUFPLE9BQU8sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUN0QixLQUFLLENBQUMsVUFBVSxFQUNoQix5QkFBeUIsQ0FBQyw2QkFBNkIsQ0FBQyxjQUFjLENBQUMsQ0FDeEUsQ0FBQztJQUNKLENBQUM7SUFTRCxZQUFZLGlCQUE4QjtRQUZuQyxrQkFBYSxHQUFZLEtBQUssQ0FBQztRQUdwQyxJQUFJLENBQUMsY0FBYyxHQUFHLGlCQUFpQixDQUFDO1FBQ3hDLElBQUksQ0FBQyxrQkFBa0IsR0FBRyxJQUFJLE9BQU8sQ0FBQyxVQUFVLENBQUMsVUFBVSxDQUFDO1lBQzFELFFBQVEs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
151
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRkYWVtb24uY2xhc3Nlcy5zeXN0ZW1kbWFuYWdlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL3NtYXJ0ZGFlbW9uLmNsYXNzZXMuc3lzdGVtZG1hbmFnZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSwwQkFBMEIsQ0FBQztBQUNwRCxPQUFPLEtBQUssS0FBSyxNQUFNLHdCQUF3QixDQUFDO0FBQ2hELE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSxzQ0FBc0MsQ0FBQztBQUNuRSxPQUFPLEVBRUwsa0JBQWtCLEdBQ25CLE1BQU0sa0NBQWtDLENBQUM7QUFFMUMsTUFBTSxPQUFPLHlCQUF5QjtJQUNwQyxTQUFTO2FBQ00seUJBQW9CLEdBQUcsYUFBYSxBQUFoQixDQUFpQjtJQUU3QyxNQUFNLENBQUMsZ0NBQWdDLENBQUMsY0FBc0I7UUFDbkUsT0FBTyxHQUFHLHlCQUF5QixDQUFDLG9CQUFvQixJQUFJLGNBQWMsRUFBRSxDQUFDO0lBQy9FLENBQUM7SUFFTSxNQUFNLENBQUMsNkJBQTZCLENBQUMsY0FBc0I7UUFDaEUsT0FBTyxHQUFHLHlCQUF5QixDQUFDLGdDQUFnQyxDQUFDLGNBQWMsQ0FBQyxVQUFVLENBQUM7SUFDakcsQ0FBQztJQUVNLE1BQU0sQ0FBQyw2QkFBNkIsQ0FBQyxjQUFzQjtRQUNoRSxPQUFPLE9BQU8sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUN0QixLQUFLLENBQUMsVUFBVSxFQUNoQix5QkFBeUIsQ0FBQyw2QkFBNkIsQ0FBQyxjQUFjLENBQUMsQ0FDeEUsQ0FBQztJQUNKLENBQUM7SUFXRCxZQUFZLGlCQUE4QixFQUFFLGVBQXdCO1FBSjdELGtCQUFhLEdBQVksS0FBSyxDQUFDO1FBQy9CLFdBQU0sR0FBWSxLQUFLLENBQUM7UUFJN0IsSUFBSSxDQUFDLGNBQWMsR0FBRyxpQkFBaUIsQ0FBQztRQUN4QyxJQUFJLENBQUMsa0JBQWtCLEdBQUcsSUFBSSxPQUFP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
@@ -1,4 +1,3 @@
1
- import * as plugins from './smartdaemon.plugins.js';
2
1
  import { SmartDaemon } from './smartdaemon.classes.smartdaemon.js';
3
2
  import { SmartDaemonService } from './smartdaemon.classes.service.js';
4
3
  export class SmartDaemonTemplateManager {
@@ -9,7 +8,9 @@ export class SmartDaemonTemplateManager {
9
8
  # version: ${serviceArg.version}
10
9
  # description: ${serviceArg.description}
11
10
  # command: ${serviceArg.command}
12
- # workingDir: ${serviceArg.workingDir}
11
+ # workingDir: ${serviceArg.workingDir}${serviceArg.user ? `
12
+ # user: ${serviceArg.user}` : ''}${serviceArg.group ? `
13
+ # group: ${serviceArg.group}` : ''}
13
14
  # ---
14
15
  [Unit]
15
16
  Description=${serviceArg.description}
@@ -17,7 +18,9 @@ Requires=network.target
17
18
  After=network.target
18
19
 
19
20
  [Service]
20
- Type=simple
21
+ Type=simple${serviceArg.user ? `
22
+ User=${serviceArg.user}` : ''}${serviceArg.group ? `
23
+ Group=${serviceArg.group}` : ''}
21
24
  Environment=NODE_OPTIONS="--max_old_space_size=100"
22
25
  ExecStart=/bin/bash -c "cd ${serviceArg.workingDir} && ${serviceArg.command}"
23
26
  WorkingDirectory=${serviceArg.workingDir}
@@ -35,4 +38,4 @@ WantedBy=multi-user.target
35
38
  this.smartdaemonRef = smartdaemonRefArg;
36
39
  }
37
40
  }
38
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRkYWVtb24uY2xhc3Nlcy50ZW1wbGF0ZW1hbmFnZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9zbWFydGRhZW1vbi5jbGFzc2VzLnRlbXBsYXRlbWFuYWdlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLDBCQUEwQixDQUFDO0FBQ3BELE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSxzQ0FBc0MsQ0FBQztBQUNuRSxPQUFPLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSxrQ0FBa0MsQ0FBQztBQUV0RSxNQUFNLE9BQU8sMEJBQTBCO0lBR3JDLFlBQVksaUJBQThCO1FBSW5DLCtCQUEwQixHQUFHLENBQUMsVUFBOEIsRUFBRSxFQUFFO1lBQ3JFLE9BQU87VUFDRCxVQUFVLENBQUMsSUFBSTthQUNaLFVBQVUsQ0FBQyxPQUFPO2lCQUNkLFVBQVUsQ0FBQyxXQUFXO2FBQzFCLFVBQVUsQ0FBQyxPQUFPO2dCQUNmLFVBQVUsQ0FBQyxVQUFVOzs7Y0FHdkIsVUFBVSxDQUFDLFdBQVc7Ozs7Ozs7NkJBT1AsVUFBVSxDQUFDLFVBQVUsT0FBTyxVQUFVLENBQUMsT0FBTzttQkFDeEQsVUFBVSxDQUFDLFVBQVU7Ozs7Ozs7Ozs7Q0FVdkMsQ0FBQztRQUNBLENBQUMsQ0FBQztRQS9CQSxJQUFJLENBQUMsY0FBYyxHQUFHLGlCQUFpQixDQUFDO0lBQzFDLENBQUM7Q0ErQkYifQ==
41
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRkYWVtb24uY2xhc3Nlcy50ZW1wbGF0ZW1hbmFnZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9zbWFydGRhZW1vbi5jbGFzc2VzLnRlbXBsYXRlbWFuYWdlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsV0FBVyxFQUFFLE1BQU0sc0NBQXNDLENBQUM7QUFDbkUsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0sa0NBQWtDLENBQUM7QUFFdEUsTUFBTSxPQUFPLDBCQUEwQjtJQUdyQyxZQUFZLGlCQUE4QjtRQUluQywrQkFBMEIsR0FBRyxDQUFDLFVBQThCLEVBQUUsRUFBRTtZQUNyRSxPQUFPO1VBQ0QsVUFBVSxDQUFDLElBQUk7YUFDWixVQUFVLENBQUMsT0FBTztpQkFDZCxVQUFVLENBQUMsV0FBVzthQUMxQixVQUFVLENBQUMsT0FBTztnQkFDZixVQUFVLENBQUMsVUFBVSxHQUFHLFVBQVUsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDO1VBQ2hELFVBQVUsQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxHQUFHLFVBQVUsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDO1dBQzNDLFVBQVUsQ0FBQyxLQUFLLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRTs7O2NBR3BCLFVBQVUsQ0FBQyxXQUFXOzs7OzthQUt2QixVQUFVLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQztPQUN4QixVQUFVLENBQUMsSUFBSSxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsR0FBRyxVQUFVLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQztRQUMzQyxVQUFVLENBQUMsS0FBSyxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUU7OzZCQUVGLFVBQVUsQ0FBQyxVQUFVLE9BQU8sVUFBVSxDQUFDLE9BQU87bUJBQ3hELFVBQVUsQ0FBQyxVQUFVOzs7Ozs7Ozs7O0NBVXZDLENBQUM7UUFDQSxDQUFDLENBQUM7UUFuQ0EsSUFBSSxDQUFDLGNBQWMsR0FBRyxpQkFBaUIsQ0FBQztJQUMxQyxDQUFDO0NBbUNGIn0=
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@push.rocks/smartdaemon",
3
- "version": "2.0.8",
3
+ "version": "2.1.0",
4
4
  "private": false,
5
5
  "description": "Start scripts as long running daemons and manage them.",
6
6
  "main": "dist_ts/index.js",
@@ -8,21 +8,20 @@
8
8
  "author": "Lossless GmbH",
9
9
  "license": "MIT",
10
10
  "devDependencies": {
11
- "@git.zone/tsbuild": "^2.1.66",
11
+ "@git.zone/tsbuild": "^2.6.7",
12
12
  "@git.zone/tsrun": "^1.2.44",
13
- "@git.zone/tstest": "^1.0.77",
14
- "@push.rocks/tapbundle": "^5.0.23",
13
+ "@git.zone/tstest": "^2.3.5",
15
14
  "@types/node": "^22.13.8"
16
15
  },
17
16
  "dependencies": {
18
- "@push.rocks/lik": "^6.0.15",
19
- "@push.rocks/smartfile": "^11.0.14",
17
+ "@push.rocks/lik": "^6.2.2",
18
+ "@push.rocks/smartfile": "^11.2.7",
20
19
  "@push.rocks/smartfm": "^2.0.4",
21
- "@push.rocks/smartlog": "^3.0.3",
20
+ "@push.rocks/smartlog": "^3.1.8",
22
21
  "@push.rocks/smartlog-destination-local": "^9.0.1",
23
- "@push.rocks/smartpath": "^5.0.14",
24
- "@push.rocks/smartshell": "^3.0.5",
25
- "@push.rocks/smartsystem": "^3.0.1"
22
+ "@push.rocks/smartpath": "^6.0.0",
23
+ "@push.rocks/smartshell": "^3.3.0",
24
+ "@push.rocks/smartsystem": "^3.0.7"
26
25
  },
27
26
  "files": [
28
27
  "ts/**/*",
@@ -55,7 +54,7 @@
55
54
  "url": "https://code.foss.global/push.rocks/smartdaemon.git"
56
55
  },
57
56
  "scripts": {
58
- "test": "(tstest test/ --web)",
57
+ "test": "(tstest test/ --verbose --logfile --timeout 60)",
59
58
  "build": "(tsbuild --web --allowimplicitany)",
60
59
  "buildDocs": "tsdoc"
61
60
  }
package/readme.md CHANGED
@@ -1,96 +1,411 @@
1
- # @push.rocks/smartdaemon
2
- start scripts as long running daemons and manage them
1
+ # @push.rocks/smartdaemon 🚀
2
+ **Turn your Node.js scripts into production-ready system daemons**
3
3
 
4
- ## Install
5
- To install `@push.rocks/smartdaemon`, run the following command in your terminal:
4
+ [![npm version](https://img.shields.io/npm/v/@push.rocks/smartdaemon.svg?style=flat-square)](https://www.npmjs.com/package/@push.rocks/smartdaemon)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+
7
+ > Seamlessly manage long-running processes, background services, and system daemons with smart root detection, automatic privilege escalation, and systemd integration.
8
+
9
+ ## 🎯 What is SmartDaemon?
10
+
11
+ `@push.rocks/smartdaemon` is your Swiss Army knife for turning Node.js applications into bulletproof system services. Whether you're deploying microservices, running scheduled tasks, or managing background workers, SmartDaemon handles the complex stuff so you can focus on your business logic.
12
+
13
+ ### ✨ Key Features
14
+
15
+ - 🔐 **Smart Privilege Management**: Automatically detects root status and handles sudo operations seamlessly
16
+ - 👤 **User/Group Control**: Run services under specific users for enhanced security
17
+ - 🔄 **Systemd Integration**: Full Linux systemd support for production-grade service management
18
+ - 🛡️ **Auto-Recovery**: Built-in service restart on failure
19
+ - 📝 **Declarative Service Definitions**: Simple, clear service configuration
20
+ - 🔌 **Interactive & Non-Interactive Modes**: Support for both passwordless sudo and interactive authentication
21
+ - 🎛️ **Complete Lifecycle Management**: Start, stop, enable, disable, and reload services with ease
22
+
23
+ ## 📦 Installation
6
24
 
7
25
  ```bash
26
+ # Using npm
8
27
  npm install @push.rocks/smartdaemon --save
9
- ```
10
- This will add `@push.rocks/smartdaemon` to your project's dependencies.
11
28
 
12
- ## Usage
29
+ # Using pnpm (recommended)
30
+ pnpm add @push.rocks/smartdaemon
13
31
 
14
- `@push.rocks/smartdaemon` is a powerful module designed to help you manage long-running daemons for your applications. Whether you're running background services, scheduled tasks, or any other continuous or long-term operations in a Node.js environment, `smartdaemon` provides an intuitive and flexible API to control these processes.
32
+ # Using yarn
33
+ yarn add @push.rocks/smartdaemon
34
+ ```
35
+
36
+ ## 🚀 Quick Start
15
37
 
16
- ### Getting Started
17
- First, ensure you import `SmartDaemon` from the package in your TypeScript files:
38
+ ### Basic Usage
18
39
 
19
40
  ```typescript
20
41
  import { SmartDaemon } from '@push.rocks/smartdaemon';
42
+
43
+ // Initialize SmartDaemon
44
+ const daemon = new SmartDaemon();
45
+
46
+ // Create and enable a service
47
+ const myService = await daemon.addService({
48
+ name: 'my-api-server',
49
+ description: 'My awesome API server',
50
+ command: 'node server.js',
51
+ workingDir: '/opt/myapp',
52
+ version: '1.0.0'
53
+ });
54
+
55
+ // Enable and start the service
56
+ await myService.enable(); // Creates systemd service file
57
+ await myService.start(); // Starts the service
21
58
  ```
22
59
 
23
- ### Initialize SmartDaemon
24
- Create an instance of `SmartDaemon`. This instance will be the central point for managing your services.
60
+ ### Running Services as Specific Users 👥
61
+
62
+ Security best practice: Never run services as root unless absolutely necessary!
25
63
 
26
64
  ```typescript
27
- const myDaemon = new SmartDaemon();
65
+ const webService = await daemon.addService({
66
+ name: 'web-server',
67
+ description: 'Production web server',
68
+ command: 'node app.js',
69
+ workingDir: '/var/www/app',
70
+ version: '2.1.0',
71
+ user: 'www-data', // Run as www-data user
72
+ group: 'www-data' // Run under www-data group
73
+ });
74
+
75
+ await webService.enable();
76
+ await webService.start();
28
77
  ```
29
78
 
30
- ### Adding a Service
31
- To manage a daemon, you first need to define it as a service. A service in `smartdaemon` is essentially a description of the daemon process you want to manage — including how it should be started, where it should run, and other operational metadata.
79
+ ## 🔐 Smart Privilege Escalation
32
80
 
33
- Here's an example of adding a simple service that executes a shell command:
81
+ SmartDaemon intelligently handles privilege escalation based on your execution context:
82
+
83
+ ### Automatic Root Detection
34
84
 
35
85
  ```typescript
36
- async function addSampleService() {
37
- const myService = await myDaemon.addService({
38
- name: 'mySampleService',
39
- description: 'A sample service running a long-lived process',
40
- command: 'node path/to/your/script.js',
41
- workingDir: '/absolute/path/to/working/directory',
42
- version: '1.0.0'
43
- });
86
+ // SmartDaemon automatically detects if running as root
87
+ const daemon = new SmartDaemon();
88
+
89
+ // If running as regular user, sudo will be used automatically
90
+ // If running as root, commands execute directly
91
+ ```
92
+
93
+ ### With Sudo Password
94
+
95
+ If you're not running as root and don't have passwordless sudo configured:
96
+
97
+ ```typescript
98
+ const daemon = new SmartDaemon({
99
+ sudoPassword: 'your-sudo-password' // Will be used for privilege escalation
100
+ });
101
+
102
+ // All privileged operations will now use the provided password
103
+ const service = await daemon.addService({
104
+ name: 'privileged-service',
105
+ description: 'Service requiring root privileges',
106
+ command: 'node admin-task.js',
107
+ workingDir: '/opt/admin',
108
+ version: '1.0.0'
109
+ });
110
+
111
+ await service.enable(); // Automatically uses sudo with password
112
+ ```
113
+
114
+ ### Passwordless Sudo (Recommended for Production)
115
+
116
+ For production environments, configure passwordless sudo for specific systemctl commands:
117
+
118
+ 1. Create a sudoers file: `/etc/sudoers.d/smartdaemon`
119
+ 2. Add the following content:
120
+ ```bash
121
+ # Allow smartdaemon to manage services without password
122
+ yourusername ALL=(ALL) NOPASSWD: /usr/bin/systemctl start smartdaemon_*
123
+ yourusername ALL=(ALL) NOPASSWD: /usr/bin/systemctl stop smartdaemon_*
124
+ yourusername ALL=(ALL) NOPASSWD: /usr/bin/systemctl enable smartdaemon_*
125
+ yourusername ALL=(ALL) NOPASSWD: /usr/bin/systemctl disable smartdaemon_*
126
+ yourusername ALL=(ALL) NOPASSWD: /usr/bin/systemctl daemon-reload
127
+ ```
128
+
129
+ ## 📚 Complete API
130
+
131
+ ### SmartDaemon Class
132
+
133
+ ```typescript
134
+ interface ISmartDaemonOptions {
135
+ sudoPassword?: string; // Optional sudo password for privilege escalation
136
+ }
137
+
138
+ class SmartDaemon {
139
+ constructor(options?: ISmartDaemonOptions);
140
+
141
+ // Add a new service or update existing one
142
+ async addService(options: ISmartDaemonServiceOptions): Promise<SmartDaemonService>;
143
+
144
+ // Direct access to managers (advanced usage)
145
+ systemdManager: SmartDaemonSystemdManager;
146
+ templateManager: SmartDaemonTemplateManager;
147
+ }
148
+ ```
149
+
150
+ ### SmartDaemonService Class
151
+
152
+ ```typescript
153
+ interface ISmartDaemonServiceOptions {
154
+ name: string; // Unique service identifier
155
+ description: string; // Human-readable description
156
+ command: string; // Command to execute
157
+ workingDir: string; // Working directory (absolute path)
158
+ version: string; // Service version
159
+ user?: string; // User to run service as (optional)
160
+ group?: string; // Group to run service as (optional)
161
+ }
162
+
163
+ class SmartDaemonService {
164
+ // Lifecycle management
165
+ async enable(): Promise<void>; // Install and enable service
166
+ async disable(): Promise<void>; // Disable service
167
+ async start(): Promise<void>; // Start service
168
+ async stop(): Promise<void>; // Stop service
44
169
 
45
- await myService.enable();
170
+ // Service management
171
+ async save(): Promise<void>; // Save service configuration
172
+ async delete(): Promise<void>; // Remove service
173
+ async reload(): Promise<void>; // Reload systemd daemon
46
174
  }
47
- addSampleService();
48
175
  ```
49
176
 
50
- In this example:
51
- - `name`: Unique identifier for the service.
52
- - `description`: A brief explanation of the service.
53
- - `command`: The command that starts your daemon (e.g., node application, shell script).
54
- - `workingDir`: The working directory from which the command will be executed.
55
- - `version`: The version of your service. This can be used for your own versioning and tracking.
177
+ ## 🎭 Real-World Examples
56
178
 
57
- ### Starting and Stopping Services
58
- After adding and enabling a service, you can control it using the start and stop methods.
179
+ ### Microservice Deployment
59
180
 
60
181
  ```typescript
61
- async function controlService() {
62
- const myServiceName = 'mySampleService'; // The name of the service you added before
63
- const myService = await myDaemon.getService(myServiceName);
182
+ import { SmartDaemon } from '@push.rocks/smartdaemon';
183
+
184
+ async function deployMicroservices() {
185
+ const daemon = new SmartDaemon();
186
+
187
+ // API Gateway
188
+ const apiGateway = await daemon.addService({
189
+ name: 'api-gateway',
190
+ description: 'API Gateway Service',
191
+ command: 'node --max-old-space-size=2048 gateway.js',
192
+ workingDir: '/opt/services/gateway',
193
+ version: '3.2.1',
194
+ user: 'apiuser',
195
+ group: 'apigroup'
196
+ });
197
+
198
+ // Auth Service
199
+ const authService = await daemon.addService({
200
+ name: 'auth-service',
201
+ description: 'Authentication Service',
202
+ command: 'node auth-service.js',
203
+ workingDir: '/opt/services/auth',
204
+ version: '2.1.0',
205
+ user: 'authuser',
206
+ group: 'authgroup'
207
+ });
208
+
209
+ // Database Sync Worker
210
+ const dbWorker = await daemon.addService({
211
+ name: 'db-sync-worker',
212
+ description: 'Database Synchronization Worker',
213
+ command: 'node workers/db-sync.js',
214
+ workingDir: '/opt/services/workers',
215
+ version: '1.5.3',
216
+ user: 'dbworker',
217
+ group: 'dbgroup'
218
+ });
64
219
 
65
- // To start the service
66
- await myService.start();
220
+ // Enable and start all services
221
+ const services = [apiGateway, authService, dbWorker];
222
+
223
+ for (const service of services) {
224
+ await service.enable();
225
+ await service.start();
226
+ console.log(`✅ Service ${service.name} is running`);
227
+ }
228
+ }
229
+
230
+ deployMicroservices();
231
+ ```
232
+
233
+ ### Scheduled Task Runner
234
+
235
+ ```typescript
236
+ async function setupScheduledTasks() {
237
+ const daemon = new SmartDaemon();
238
+
239
+ // Backup service that runs every night
240
+ const backupService = await daemon.addService({
241
+ name: 'nightly-backup',
242
+ description: 'Nightly database backup service',
243
+ command: 'node --require dotenv/config backup-runner.js',
244
+ workingDir: '/opt/backup',
245
+ version: '1.0.0',
246
+ user: 'backup',
247
+ group: 'backup'
248
+ });
249
+
250
+ await backupService.enable();
251
+ await backupService.start();
252
+ }
253
+ ```
254
+
255
+ ### Development vs Production Setup
67
256
 
68
- // To stop the service
69
- await myService.stop();
257
+ ```typescript
258
+ import { SmartDaemon } from '@push.rocks/smartdaemon';
259
+
260
+ async function setupService(environment: 'development' | 'production') {
261
+ let daemonOptions = {};
262
+
263
+ if (environment === 'development') {
264
+ // In development, you might use sudo password
265
+ daemonOptions = {
266
+ sudoPassword: process.env.SUDO_PASSWORD
267
+ };
268
+ }
269
+ // In production, rely on passwordless sudo configuration
270
+
271
+ const daemon = new SmartDaemon(daemonOptions);
272
+
273
+ const service = await daemon.addService({
274
+ name: `app-${environment}`,
275
+ description: `Application ${environment} server`,
276
+ command: environment === 'production'
277
+ ? 'node --production app.js'
278
+ : 'node --inspect app.js',
279
+ workingDir: '/opt/application',
280
+ version: '1.0.0',
281
+ user: environment === 'production' ? 'appuser' : process.env.USER,
282
+ group: environment === 'production' ? 'appgroup' : process.env.USER
283
+ });
284
+
285
+ await service.enable();
286
+ await service.start();
70
287
  }
71
- controlService();
72
288
  ```
73
289
 
74
- ### Advanced Management
75
- `smartdaemon` also provides advanced functionalities to manage your daemons effectively, including:
76
- - Checking the status of your services
77
- - Automatically restarting services on failure
78
- - Configuring environment variables or execution parameters
290
+ ## 🔧 Advanced Usage
291
+
292
+ ### Direct SystemdManager Access
293
+
294
+ For advanced users who need fine-grained control:
295
+
296
+ ```typescript
297
+ const daemon = new SmartDaemon();
298
+
299
+ // Access the SystemdManager directly
300
+ const systemdManager = daemon.systemdManager;
301
+
302
+ // Check if running on a compatible system
303
+ const canRun = await systemdManager.checkElegibility();
304
+
305
+ // Get all existing SmartDaemon services
306
+ const existingServices = await systemdManager.getServices();
307
+
308
+ // Reload systemd daemon
309
+ await systemdManager.reload();
310
+ ```
311
+
312
+ ### Custom Service Templates
79
313
 
80
- ### Working with Systemd
81
- If you're operating on a Linux environment, `smartdaemon` can generate and control services using `systemd`. This integration allows for robust management capabilities, utilizing system-level features for daemon management.
314
+ Access the template manager for custom service file generation:
82
315
 
83
316
  ```typescript
84
- // Example: Creating a systemd service
85
- await myDaemon.systemdManager.saveService(myService);
317
+ const daemon = new SmartDaemon();
318
+ const template = daemon.templateManager.generateUnitFileForService(myService);
319
+ console.log(template); // View generated systemd unit file
320
+ ```
321
+
322
+ ## 🏗️ System Requirements
323
+
324
+ - **Operating System**: Linux with systemd (Ubuntu 16.04+, Debian 9+, CentOS 7+, RHEL 7+, Fedora, Arch, etc.)
325
+ - **Node.js**: Version 14.x or higher
326
+ - **Permissions**: Either root access or properly configured sudo
327
+
328
+ ## ⚙️ Generated Systemd Service Files
329
+
330
+ SmartDaemon generates professional systemd unit files with:
331
+ - Automatic restart on failure
332
+ - Network target dependencies
333
+ - Proper working directory configuration
334
+ - User/Group assignment
335
+ - Resource limits configuration
336
+ - Syslog integration for logging
337
+ - Environment variable support
338
+
339
+ Example generated service file:
340
+ ```ini
341
+ [Unit]
342
+ Description=My API Server
343
+ Requires=network.target
344
+ After=network.target
345
+
346
+ [Service]
347
+ Type=simple
348
+ User=www-data
349
+ Group=www-data
350
+ Environment=NODE_OPTIONS="--max_old_space_size=100"
351
+ ExecStart=/bin/bash -c "cd /opt/myapp && node server.js"
352
+ WorkingDirectory=/opt/myapp
353
+ Restart=always
354
+ RestartSec=10
355
+ LimitNOFILE=infinity
356
+ LimitCORE=infinity
357
+ StandardOutput=syslog
358
+ StandardError=syslog
359
+
360
+ [Install]
361
+ WantedBy=multi-user.target
86
362
  ```
87
363
 
88
- ### Conclusion
89
- `@push.rocks/smartdaemon` streamlines the management of long-running daemons and background services in Node.js applications. By leveraging its comprehensive API, developers can efficiently control the lifecycle of their services, ensuring their applications run smoothly and reliably.
364
+ ## 🐛 Troubleshooting
365
+
366
+ ### Permission Denied Errors
367
+
368
+ If you encounter "Interactive authentication required" errors:
369
+
370
+ 1. **Option 1**: Run your application with sudo
371
+ ```bash
372
+ sudo node your-app.js
373
+ ```
374
+
375
+ 2. **Option 2**: Configure passwordless sudo (recommended for production)
376
+
377
+ 3. **Option 3**: Provide sudo password programmatically
378
+ ```typescript
379
+ const daemon = new SmartDaemon({ sudoPassword: 'your-password' });
380
+ ```
381
+
382
+ ### Service Not Found
383
+
384
+ If systemctl can't find your service:
385
+ - Check that the service was enabled: `await service.enable()`
386
+ - Verify the service name: Services are prefixed with `smartdaemon_`
387
+ - Reload systemd: `await service.reload()`
388
+
389
+ ### Service Fails to Start
390
+
391
+ Check logs using:
392
+ ```bash
393
+ journalctl -u smartdaemon_your-service-name -f
394
+ ```
395
+
396
+ ## 🔒 Security Best Practices
397
+
398
+ 1. **Never run services as root** unless absolutely necessary
399
+ 2. **Use dedicated service users** with minimal permissions
400
+ 3. **Configure passwordless sudo** for production instead of storing passwords
401
+ 4. **Limit sudo permissions** to only the specific systemctl commands needed
402
+ 5. **Regular security audits** of your service configurations
90
403
 
91
- This module opens up possibilities for more structured and system-integrated application architectures, especially in environments where long-running tasks are essential.
404
+ ## 🤝 Support
92
405
 
93
- **Note**: Ensure to check permissions and system compatibility, especially when integrating with system-level service managers like `systemd`.
406
+ - 📧 Email: hello@task.vc
407
+ - 🐛 Issues: [GitHub Issues](https://code.foss.global/push.rocks/smartdaemon/issues)
408
+ - 📖 Documentation: [https://code.foss.global/push.rocks/smartdaemon](https://code.foss.global/push.rocks/smartdaemon)
94
409
 
95
410
  ## License and Legal Information
96
411
 
@@ -109,4 +424,4 @@ Registered at District court Bremen HRB 35230 HB, Germany
109
424
 
110
425
  For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
111
426
 
112
- By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
427
+ By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartdaemon',
6
- version: '2.0.8',
6
+ version: '2.1.0',
7
7
  description: 'Start scripts as long running daemons and manage them.'
8
8
  }
package/ts/index.ts CHANGED
@@ -1 +1,2 @@
1
1
  export * from './smartdaemon.classes.smartdaemon.js';
2
+ export * from './smartdaemon.classes.service.js';
@@ -8,6 +8,8 @@ export interface ISmartDaemonServiceConstructorOptions {
8
8
  command: string;
9
9
  workingDir: string;
10
10
  version: string;
11
+ user?: string;
12
+ group?: string;
11
13
  }
12
14
 
13
15
  /**
@@ -33,6 +35,8 @@ export class SmartDaemonService implements ISmartDaemonServiceConstructorOptions
33
35
  public command: string;
34
36
  public workingDir: string;
35
37
  public description: string;
38
+ public user?: string;
39
+ public group?: string;
36
40
 
37
41
  public smartdaemonRef: SmartDaemon;
38
42
 
@@ -6,13 +6,17 @@ import {
6
6
  } from './smartdaemon.classes.service.js';
7
7
  import { SmartDaemonSystemdManager } from './smartdaemon.classes.systemdmanager.js';
8
8
 
9
+ export interface ISmartDaemonOptions {
10
+ sudoPassword?: string;
11
+ }
12
+
9
13
  export class SmartDaemon {
10
14
  public templateManager: SmartDaemonTemplateManager;
11
15
  public systemdManager: SmartDaemonSystemdManager;
12
16
 
13
- constructor() {
17
+ constructor(optionsArg?: ISmartDaemonOptions) {
14
18
  this.templateManager = new SmartDaemonTemplateManager(this);
15
- this.systemdManager = new SmartDaemonSystemdManager(this);
19
+ this.systemdManager = new SmartDaemonSystemdManager(this, optionsArg?.sudoPassword);
16
20
  }
17
21
 
18
22
  /**
@@ -31,13 +31,29 @@ export class SmartDaemonSystemdManager {
31
31
  public smartsystem: plugins.smartsystem.Smartsystem;
32
32
 
33
33
  public shouldExecute: boolean = false;
34
+ public isRoot: boolean = false;
35
+ public sudoPassword?: string;
34
36
 
35
- constructor(smartdaemonRefArg: SmartDaemon) {
37
+ constructor(smartdaemonRefArg: SmartDaemon, sudoPasswordArg?: string) {
36
38
  this.smartdaemonRef = smartdaemonRefArg;
37
39
  this.smartshellInstance = new plugins.smartshell.Smartshell({
38
40
  executor: 'bash',
39
41
  });
40
42
  this.smartsystem = new plugins.smartsystem.Smartsystem();
43
+ this.sudoPassword = sudoPasswordArg;
44
+
45
+ // Check if we're running as root
46
+ this.checkIsRoot().then(isRoot => {
47
+ this.isRoot = isRoot;
48
+ if (!isRoot) {
49
+ console.log('Not running as root. Sudo will be used for privileged operations.');
50
+ }
51
+ });
52
+ }
53
+
54
+ private async checkIsRoot(): Promise<boolean> {
55
+ const result = await this.smartshellInstance.exec('id -u');
56
+ return result.stdout.trim() === '0';
41
57
  }
42
58
 
43
59
  public async checkElegibility() {
@@ -52,7 +68,28 @@ export class SmartDaemonSystemdManager {
52
68
 
53
69
  public async execute(commandArg: string) {
54
70
  if (await this.checkElegibility()) {
55
- await this.smartshellInstance.exec(commandArg);
71
+ // Only use sudo if we're not root and command doesn't already have sudo
72
+ if (!this.isRoot && !commandArg.startsWith('sudo')) {
73
+ commandArg = `sudo ${commandArg}`;
74
+
75
+ if (this.sudoPassword) {
76
+ // Use interactive PTY mode for password input
77
+ const interactiveExec = await this.smartshellInstance.execInteractiveControlPty(commandArg);
78
+ await interactiveExec.sendLine(this.sudoPassword);
79
+ const result = await interactiveExec.finalPromise;
80
+ return result;
81
+ }
82
+ }
83
+
84
+ // Execute command (with or without sudo)
85
+ try {
86
+ await this.smartshellInstance.exec(commandArg);
87
+ } catch (error) {
88
+ if (!this.isRoot && error.message && error.message.includes('authentication')) {
89
+ throw new Error('Sudo authentication failed. Please configure passwordless sudo or provide a sudo password.');
90
+ }
91
+ throw error;
92
+ }
56
93
  }
57
94
  }
58
95
 
@@ -101,18 +138,31 @@ export class SmartDaemonSystemdManager {
101
138
 
102
139
  public async saveService(serviceArg: SmartDaemonService) {
103
140
  if (await this.checkElegibility()) {
104
- await plugins.smartfile.memory.toFs(
105
- this.smartdaemonRef.templateManager.generateUnitFileForService(serviceArg),
106
- SmartDaemonSystemdManager.createFilePathFromServiceName(serviceArg.name)
107
- );
141
+ const content = this.smartdaemonRef.templateManager.generateUnitFileForService(serviceArg);
142
+ const targetPath = SmartDaemonSystemdManager.createFilePathFromServiceName(serviceArg.name);
143
+
144
+ if (this.isRoot) {
145
+ // Direct write when running as root
146
+ await plugins.smartfile.memory.toFs(content, targetPath);
147
+ } else {
148
+ // Use sudo to write when not root
149
+ const tempPath = `/tmp/smartdaemon_${serviceArg.name}.service`;
150
+ await plugins.smartfile.memory.toFs(content, tempPath);
151
+ await this.execute(`mv ${tempPath} ${targetPath}`); // execute() will add sudo
152
+ await this.execute(`chmod 644 ${targetPath}`); // execute() will add sudo
153
+ }
108
154
  }
109
155
  }
110
156
 
111
157
  public async deleteService(serviceArg: SmartDaemonService) {
112
158
  if (await this.checkElegibility()) {
113
- await plugins.smartfile.fs.remove(
114
- SmartDaemonSystemdManager.createServiceNameFromServiceName(serviceArg.name)
115
- );
159
+ const filePath = SmartDaemonSystemdManager.createFilePathFromServiceName(serviceArg.name);
160
+
161
+ if (this.isRoot) {
162
+ await plugins.smartfile.fs.remove(filePath);
163
+ } else {
164
+ await this.execute(`rm ${filePath}`); // execute() will add sudo
165
+ }
116
166
  }
117
167
  }
118
168
 
@@ -1,4 +1,3 @@
1
- import * as plugins from './smartdaemon.plugins.js';
2
1
  import { SmartDaemon } from './smartdaemon.classes.smartdaemon.js';
3
2
  import { SmartDaemonService } from './smartdaemon.classes.service.js';
4
3
 
@@ -15,7 +14,9 @@ export class SmartDaemonTemplateManager {
15
14
  # version: ${serviceArg.version}
16
15
  # description: ${serviceArg.description}
17
16
  # command: ${serviceArg.command}
18
- # workingDir: ${serviceArg.workingDir}
17
+ # workingDir: ${serviceArg.workingDir}${serviceArg.user ? `
18
+ # user: ${serviceArg.user}` : ''}${serviceArg.group ? `
19
+ # group: ${serviceArg.group}` : ''}
19
20
  # ---
20
21
  [Unit]
21
22
  Description=${serviceArg.description}
@@ -23,7 +24,9 @@ Requires=network.target
23
24
  After=network.target
24
25
 
25
26
  [Service]
26
- Type=simple
27
+ Type=simple${serviceArg.user ? `
28
+ User=${serviceArg.user}` : ''}${serviceArg.group ? `
29
+ Group=${serviceArg.group}` : ''}
27
30
  Environment=NODE_OPTIONS="--max_old_space_size=100"
28
31
  ExecStart=/bin/bash -c "cd ${serviceArg.workingDir} && ${serviceArg.command}"
29
32
  WorkingDirectory=${serviceArg.workingDir}