@push.rocks/smartdaemon 2.22.0 → 3.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 +2 -2
- package/dist_ts/classes.systemdunit.d.ts +58 -2
- package/dist_ts/classes.systemdunit.js +87 -6
- package/dist_ts/index.d.ts +1 -0
- package/dist_ts/index.js +2 -1
- package/dist_ts/smartdaemon.classes.commanderror.d.ts +25 -0
- package/dist_ts/smartdaemon.classes.commanderror.js +27 -0
- package/dist_ts/smartdaemon.classes.systemdmanager.d.ts +12 -0
- package/dist_ts/smartdaemon.classes.systemdmanager.js +63 -30
- package/dist_ts/smartdaemon.plugins.d.ts +3 -4
- package/dist_ts/smartdaemon.plugins.js +4 -5
- package/dist_ts/systemd.managerversion.d.ts +2 -0
- package/dist_ts/systemd.managerversion.js +3 -1
- package/package.json +7 -10
- package/readme.md +60 -2
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.systemdunit.ts +120 -4
- package/ts/index.ts +1 -0
- package/ts/smartdaemon.classes.commanderror.ts +23 -0
- package/ts/smartdaemon.classes.systemdmanager.ts +78 -34
- package/ts/smartdaemon.plugins.ts +2 -5
- package/ts/systemd.managerversion.ts +3 -0
- package/dist_ts/smartdamon.logging.d.ts +0 -2
- package/dist_ts/smartdamon.logging.js +0 -14
- package/ts/smartdamon.logging.ts +0 -15
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as plugins from './smartdaemon.plugins.js';
|
|
2
2
|
import * as paths from './smartdaemon.paths.js';
|
|
3
3
|
import { SmartDaemon } from './smartdaemon.classes.smartdaemon.js';
|
|
4
|
+
import { SmartDaemonCommandError } from './smartdaemon.classes.commanderror.js';
|
|
4
5
|
import {
|
|
5
6
|
type ISmartDaemonServiceConstructorOptions,
|
|
6
7
|
SmartDaemonService,
|
|
@@ -31,8 +32,10 @@ export class SmartDaemonSystemdManager {
|
|
|
31
32
|
public smartFs: plugins.smartfs.SmartFs;
|
|
32
33
|
|
|
33
34
|
public shouldExecute: boolean = false;
|
|
35
|
+
/** Whether this process runs as root; set by the first privileged operation's check. */
|
|
34
36
|
public isRoot: boolean = false;
|
|
35
37
|
public sudoPassword?: string;
|
|
38
|
+
private rootStatus?: Promise<void>;
|
|
36
39
|
|
|
37
40
|
constructor(smartdaemonRefArg: SmartDaemon, sudoPasswordArg?: string) {
|
|
38
41
|
this.smartdaemonRef = smartdaemonRefArg;
|
|
@@ -41,14 +44,6 @@ export class SmartDaemonSystemdManager {
|
|
|
41
44
|
});
|
|
42
45
|
this.smartFs = new plugins.smartfs.SmartFs(new plugins.smartfs.SmartFsProviderNode());
|
|
43
46
|
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
47
|
}
|
|
53
48
|
|
|
54
49
|
private async checkIsRoot(): Promise<boolean> {
|
|
@@ -56,6 +51,23 @@ export class SmartDaemonSystemdManager {
|
|
|
56
51
|
return result.stdout.trim() === '0';
|
|
57
52
|
}
|
|
58
53
|
|
|
54
|
+
/** Checks once whether this process runs as root and sets `isRoot`; later calls reuse that check. */
|
|
55
|
+
private ensureRootStatus(): Promise<void> {
|
|
56
|
+
if (!this.rootStatus) {
|
|
57
|
+
this.rootStatus = this.checkIsRoot().then((isRoot) => {
|
|
58
|
+
this.isRoot = isRoot;
|
|
59
|
+
if (!isRoot) {
|
|
60
|
+
console.log('Not running as root. Sudo will be used for privileged operations.');
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
// A failed check is not memoized, so the next privileged operation asks again.
|
|
64
|
+
this.rootStatus.catch(() => {
|
|
65
|
+
this.rootStatus = undefined;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
return this.rootStatus;
|
|
69
|
+
}
|
|
70
|
+
|
|
59
71
|
public async checkElegibility() {
|
|
60
72
|
if (plugins.os.platform() === 'linux') {
|
|
61
73
|
this.shouldExecute = true;
|
|
@@ -66,31 +78,61 @@ export class SmartDaemonSystemdManager {
|
|
|
66
78
|
return this.shouldExecute;
|
|
67
79
|
}
|
|
68
80
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
81
|
+
/**
|
|
82
|
+
* Runs a command, through sudo when this process is not root and the command does not start with
|
|
83
|
+
* `sudo` already. With a `sudoPassword`, sudo reads it from standard input (`sudo -k -S -p ''`):
|
|
84
|
+
* it never reaches argv, the environment or a terminal. Rejects with `SmartDaemonCommandError`
|
|
85
|
+
* when the command does not exit 0.
|
|
86
|
+
*/
|
|
87
|
+
public async execute(commandArg: string): Promise<plugins.smartshell.IExecResult | undefined> {
|
|
88
|
+
if (!(await this.checkElegibility())) {
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
await this.ensureRootStatus();
|
|
92
|
+
if (this.isRoot || commandArg.startsWith('sudo')) {
|
|
93
|
+
return await this.runChecked(commandArg);
|
|
94
|
+
}
|
|
95
|
+
if (!this.sudoPassword) {
|
|
96
|
+
return await this.runChecked(`sudo ${commandArg}`);
|
|
97
|
+
}
|
|
98
|
+
// -k ignores cached credentials, so sudo reads the password line whenever its policy asks for
|
|
99
|
+
// one; ending stdin right after it makes a rejected password fail instead of waiting for a retry.
|
|
100
|
+
const sudoCommand = `sudo -k -S -p '' -- ${commandArg}`;
|
|
101
|
+
const interactiveExec = await this.smartshellInstance.execInteractiveControl(sudoCommand);
|
|
102
|
+
let writeError: unknown;
|
|
103
|
+
try {
|
|
104
|
+
await interactiveExec.sendLine(this.sudoPassword);
|
|
105
|
+
} catch (error) {
|
|
106
|
+
// sudo can exit before reading stdin (EPIPE); its exit status below decides the outcome.
|
|
107
|
+
writeError = error;
|
|
108
|
+
}
|
|
109
|
+
interactiveExec.endInput();
|
|
110
|
+
const result = await interactiveExec.finalPromise;
|
|
111
|
+
this.assertSucceeded(sudoCommand, result, writeError);
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private async runChecked(commandArg: string): Promise<plugins.smartshell.IExecResult> {
|
|
116
|
+
const result = await this.smartshellInstance.exec(commandArg);
|
|
117
|
+
this.assertSucceeded(commandArg, result);
|
|
118
|
+
return result;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private assertSucceeded(
|
|
122
|
+
commandArg: string,
|
|
123
|
+
resultArg: plugins.smartshell.IExecResult,
|
|
124
|
+
causeArg?: unknown,
|
|
125
|
+
): void {
|
|
126
|
+
if (resultArg.exitCode === 0 && !resultArg.signal) {
|
|
127
|
+
return;
|
|
93
128
|
}
|
|
129
|
+
throw new SmartDaemonCommandError(
|
|
130
|
+
commandArg,
|
|
131
|
+
resultArg.exitCode,
|
|
132
|
+
resultArg.signal,
|
|
133
|
+
resultArg.stderr,
|
|
134
|
+
causeArg === undefined ? undefined : { cause: causeArg },
|
|
135
|
+
);
|
|
94
136
|
}
|
|
95
137
|
|
|
96
138
|
/**
|
|
@@ -136,7 +178,8 @@ export class SmartDaemonSystemdManager {
|
|
|
136
178
|
if (await this.checkElegibility()) {
|
|
137
179
|
const content = this.smartdaemonRef.templateManager.generateUnitFileForService(serviceArg);
|
|
138
180
|
const targetPath = SmartDaemonSystemdManager.createFilePathFromServiceName(serviceArg.name);
|
|
139
|
-
|
|
181
|
+
|
|
182
|
+
await this.ensureRootStatus();
|
|
140
183
|
if (this.isRoot) {
|
|
141
184
|
// Direct write when running as root
|
|
142
185
|
await this.smartFs.file(targetPath).encoding('utf8').mode(0o644).write(content);
|
|
@@ -153,7 +196,8 @@ export class SmartDaemonSystemdManager {
|
|
|
153
196
|
public async deleteService(serviceArg: SmartDaemonService) {
|
|
154
197
|
if (await this.checkElegibility()) {
|
|
155
198
|
const filePath = SmartDaemonSystemdManager.createFilePathFromServiceName(serviceArg.name);
|
|
156
|
-
|
|
199
|
+
|
|
200
|
+
await this.ensureRootStatus();
|
|
157
201
|
if (this.isRoot) {
|
|
158
202
|
await this.smartFs.file(filePath).delete();
|
|
159
203
|
} else {
|
|
@@ -6,15 +6,14 @@ import { constants as fsConstants } from 'node:fs';
|
|
|
6
6
|
import * as crypto from 'node:crypto';
|
|
7
7
|
import * as util from 'node:util';
|
|
8
8
|
import * as os from 'node:os';
|
|
9
|
+
import * as timers from 'node:timers/promises';
|
|
9
10
|
export type { Stats } from 'node:fs';
|
|
10
11
|
|
|
11
|
-
export { path, childProcess, fs, fsConstants, crypto, util, os };
|
|
12
|
+
export { path, childProcess, fs, fsConstants, crypto, util, os, timers };
|
|
12
13
|
|
|
13
14
|
// @pushrocks scope
|
|
14
15
|
import * as lik from '@push.rocks/lik';
|
|
15
16
|
import * as smartfs from '@push.rocks/smartfs';
|
|
16
|
-
import * as smartlog from '@push.rocks/smartlog';
|
|
17
|
-
import * as smartlogDestinationLocal from '@push.rocks/smartlog/destination-local';
|
|
18
17
|
import * as smartmarkdown from '@push.rocks/smartmarkdown/iso';
|
|
19
18
|
import * as smartpath from '@push.rocks/smartpath';
|
|
20
19
|
import * as smartshell from '@push.rocks/smartshell';
|
|
@@ -22,8 +21,6 @@ import * as smartshell from '@push.rocks/smartshell';
|
|
|
22
21
|
export {
|
|
23
22
|
lik,
|
|
24
23
|
smartfs,
|
|
25
|
-
smartlog,
|
|
26
|
-
smartlogDestinationLocal,
|
|
27
24
|
smartmarkdown,
|
|
28
25
|
smartpath,
|
|
29
26
|
smartshell,
|
|
@@ -19,6 +19,9 @@ export const outputAppendMajor = 240;
|
|
|
19
19
|
/** Internal: the systemd major whose `systemctl clean` first releases a file descriptor store (`--what=fdstore`). */
|
|
20
20
|
export const fileDescriptorStoreCleanMajor = 254;
|
|
21
21
|
|
|
22
|
+
/** Internal: the systemd major whose `systemctl kill` first takes `--kill-whom=` (formerly `--kill-who=`). */
|
|
23
|
+
export const killWhomMajor = 252;
|
|
24
|
+
|
|
22
25
|
/**
|
|
23
26
|
* Internal: the systemd major whose `systemd-run` first takes `--expand-environment` and reports a
|
|
24
27
|
* started scope as `runInTransientScope()` reads it.
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import * as plugins from './smartdaemon.plugins.js';
|
|
2
|
-
export const logger = new plugins.smartlog.Smartlog({
|
|
3
|
-
logContext: {
|
|
4
|
-
company: 'Some Company',
|
|
5
|
-
companyunit: 'Some CompanyUnit',
|
|
6
|
-
containerName: 'Some Containername',
|
|
7
|
-
environment: 'local',
|
|
8
|
-
runtime: 'node',
|
|
9
|
-
zone: 'gitzone',
|
|
10
|
-
},
|
|
11
|
-
minimumLogLevel: 'silly',
|
|
12
|
-
});
|
|
13
|
-
logger.addLogDestination(new plugins.smartlogDestinationLocal.DestinationLocal());
|
|
14
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRkYW1vbi5sb2dnaW5nLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvc21hcnRkYW1vbi5sb2dnaW5nLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sMEJBQTBCLENBQUM7QUFFcEQsTUFBTSxDQUFDLE1BQU0sTUFBTSxHQUFHLElBQUksT0FBTyxDQUFDLFFBQVEsQ0FBQyxRQUFRLENBQUM7SUFDbEQsVUFBVSxFQUFFO1FBQ1YsT0FBTyxFQUFFLGNBQWM7UUFDdkIsV0FBVyxFQUFFLGtCQUFrQjtRQUMvQixhQUFhLEVBQUUsb0JBQW9CO1FBQ25DLFdBQVcsRUFBRSxPQUFPO1FBQ3BCLE9BQU8sRUFBRSxNQUFNO1FBQ2YsSUFBSSxFQUFFLFNBQVM7S0FDaEI7SUFDRCxlQUFlLEVBQUUsT0FBTztDQUN6QixDQUFDLENBQUM7QUFFSCxNQUFNLENBQUMsaUJBQWlCLENBQUMsSUFBSSxPQUFPLENBQUMsd0JBQXdCLENBQUMsZ0JBQWdCLEVBQUUsQ0FBQyxDQUFDIn0=
|
package/ts/smartdamon.logging.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import * as plugins from './smartdaemon.plugins.js';
|
|
2
|
-
|
|
3
|
-
export const logger = new plugins.smartlog.Smartlog({
|
|
4
|
-
logContext: {
|
|
5
|
-
company: 'Some Company',
|
|
6
|
-
companyunit: 'Some CompanyUnit',
|
|
7
|
-
containerName: 'Some Containername',
|
|
8
|
-
environment: 'local',
|
|
9
|
-
runtime: 'node',
|
|
10
|
-
zone: 'gitzone',
|
|
11
|
-
},
|
|
12
|
-
minimumLogLevel: 'silly',
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
logger.addLogDestination(new plugins.smartlogDestinationLocal.DestinationLocal());
|