@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.
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/index.d.ts +1 -0
- package/dist_ts/index.js +2 -1
- package/dist_ts/smartdaemon.classes.service.d.ts +4 -0
- package/dist_ts/smartdaemon.classes.service.js +1 -1
- package/dist_ts/smartdaemon.classes.smartdaemon.d.ts +4 -1
- package/dist_ts/smartdaemon.classes.smartdaemon.js +3 -3
- package/dist_ts/smartdaemon.classes.systemdmanager.d.ts +5 -2
- package/dist_ts/smartdaemon.classes.systemdmanager.js +56 -5
- package/dist_ts/smartdaemon.classes.templatemanager.js +7 -4
- package/package.json +10 -11
- package/readme.md +371 -56
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/index.ts +1 -0
- package/ts/smartdaemon.classes.service.ts +4 -0
- package/ts/smartdaemon.classes.smartdaemon.ts +6 -2
- package/ts/smartdaemon.classes.systemdmanager.ts +59 -9
- package/ts/smartdaemon.classes.templatemanager.ts +6 -3
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@push.rocks/smartdaemon',
|
|
6
|
-
version: '2.0
|
|
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=
|
package/dist_ts/index.d.ts
CHANGED
package/dist_ts/index.js
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
export * from './smartdaemon.classes.smartdaemon.js';
|
|
2
|
-
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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<
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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
|
|
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.
|
|
11
|
+
"@git.zone/tsbuild": "^2.6.7",
|
|
12
12
|
"@git.zone/tsrun": "^1.2.44",
|
|
13
|
-
"@git.zone/tstest": "^
|
|
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.
|
|
19
|
-
"@push.rocks/smartfile": "^11.
|
|
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.
|
|
20
|
+
"@push.rocks/smartlog": "^3.1.8",
|
|
22
21
|
"@push.rocks/smartlog-destination-local": "^9.0.1",
|
|
23
|
-
"@push.rocks/smartpath": "^
|
|
24
|
-
"@push.rocks/smartshell": "^3.0
|
|
25
|
-
"@push.rocks/smartsystem": "^3.0.
|
|
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/ --
|
|
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
|
-
|
|
1
|
+
# @push.rocks/smartdaemon 🚀
|
|
2
|
+
**Turn your Node.js scripts into production-ready system daemons**
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
[](https://www.npmjs.com/package/@push.rocks/smartdaemon)
|
|
5
|
+
[](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
|
-
|
|
29
|
+
# Using pnpm (recommended)
|
|
30
|
+
pnpm add @push.rocks/smartdaemon
|
|
13
31
|
|
|
14
|
-
|
|
32
|
+
# Using yarn
|
|
33
|
+
yarn add @push.rocks/smartdaemon
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## 🚀 Quick Start
|
|
15
37
|
|
|
16
|
-
###
|
|
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
|
-
###
|
|
24
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
81
|
+
SmartDaemon intelligently handles privilege escalation based on your execution context:
|
|
82
|
+
|
|
83
|
+
### Automatic Root Detection
|
|
34
84
|
|
|
35
85
|
```typescript
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
//
|
|
66
|
-
|
|
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
|
-
|
|
69
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
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
|
-
|
|
85
|
-
|
|
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
|
-
|
|
89
|
-
|
|
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
|
-
|
|
404
|
+
## 🤝 Support
|
|
92
405
|
|
|
93
|
-
|
|
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.
|
package/ts/00_commitinfo_data.ts
CHANGED
package/ts/index.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
114
|
-
|
|
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}
|