dcp-worker 3.2.31 → 3.2.33
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/.eslintrc.js +117 -0
- package/.gitlab-ci.yml +3 -1
- package/bin/dcp-evaluator-manager +512 -0
- package/bin/dcp-worker +11 -0
- package/package.json +9 -2
- package/.eslintrc.json +0 -12
package/.eslintrc.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
'env': {
|
|
3
|
+
'browser': true,
|
|
4
|
+
'commonjs': true,
|
|
5
|
+
'es2021': true,
|
|
6
|
+
'node': true
|
|
7
|
+
},
|
|
8
|
+
'extends': 'eslint:recommended',
|
|
9
|
+
'parserOptions': {
|
|
10
|
+
'ecmaVersion': 13
|
|
11
|
+
},
|
|
12
|
+
globals: {
|
|
13
|
+
dcpConfig: true
|
|
14
|
+
},
|
|
15
|
+
'rules': {
|
|
16
|
+
'indent': [
|
|
17
|
+
'warn',
|
|
18
|
+
2,
|
|
19
|
+
{
|
|
20
|
+
'SwitchCase': 1,
|
|
21
|
+
'ignoredNodes': ['CallExpression', 'ForStatement'],
|
|
22
|
+
}
|
|
23
|
+
],
|
|
24
|
+
'linebreak-style': [
|
|
25
|
+
'error',
|
|
26
|
+
'unix'
|
|
27
|
+
],
|
|
28
|
+
'quotes': [
|
|
29
|
+
'warn',
|
|
30
|
+
'single'
|
|
31
|
+
],
|
|
32
|
+
'func-call-spacing': [
|
|
33
|
+
'off', 'never'
|
|
34
|
+
],
|
|
35
|
+
'no-prototype-builtins': 'off',
|
|
36
|
+
'quotes': ['warn', 'single', 'avoid-escape'],
|
|
37
|
+
'no-unused-vars': ['warn', { 'vars': 'all', 'args': 'none', 'ignoreRestSiblings': false }],
|
|
38
|
+
'no-empty': [ 'warn' ],
|
|
39
|
+
'no-trailing-spaces': [
|
|
40
|
+
'off', {
|
|
41
|
+
skipBlankLines: true,
|
|
42
|
+
ignoreComments: true
|
|
43
|
+
}
|
|
44
|
+
],
|
|
45
|
+
'no-multi-spaces': [
|
|
46
|
+
'off',
|
|
47
|
+
],
|
|
48
|
+
'prettier/prettier': [
|
|
49
|
+
'off',
|
|
50
|
+
],
|
|
51
|
+
'vars-on-top': [
|
|
52
|
+
'error',
|
|
53
|
+
],
|
|
54
|
+
'no-var': [
|
|
55
|
+
'off',
|
|
56
|
+
],
|
|
57
|
+
'spaced-comment': [
|
|
58
|
+
'warn',
|
|
59
|
+
],
|
|
60
|
+
'brace-style': [
|
|
61
|
+
/* 'warn', 'allman', { 'allowSingleLine': true } */
|
|
62
|
+
'off'
|
|
63
|
+
],
|
|
64
|
+
'no-eval': [
|
|
65
|
+
'error',
|
|
66
|
+
],
|
|
67
|
+
'object-curly-spacing': [
|
|
68
|
+
'warn',
|
|
69
|
+
'always'
|
|
70
|
+
],
|
|
71
|
+
'no-dupe-keys': [ 'warn' ],
|
|
72
|
+
'no-constant-condition': [ 'warn' ],
|
|
73
|
+
'no-extra-boolean-cast': [ 'warn' ],
|
|
74
|
+
'no-sparse-arrays': [ 'off' ],
|
|
75
|
+
'no-inner-declarations': [ 'off' ],
|
|
76
|
+
'no-loss-of-precision': [ 'warn' ],
|
|
77
|
+
'require-atomic-updates': [ 'warn' ], /* watch for false positives, remove if (m)any */
|
|
78
|
+
'eqeqeq': [ 'warn', 'always' ],
|
|
79
|
+
'no-dupe-keys': [ 'warn' ],
|
|
80
|
+
'no-dupe-class-members': [ 'warn' ],
|
|
81
|
+
'no-fallthrough': [ 'warn', { commentPattern: 'fall[ -]*through' }],
|
|
82
|
+
'no-invalid-this': [ 'error' ],
|
|
83
|
+
'no-return-assign': [ 'error' ],
|
|
84
|
+
'no-return-await': [ 'warn' ],
|
|
85
|
+
'no-unused-expressions': [ 'warn', { allowShortCircuit: true, allowTernary: true } ],
|
|
86
|
+
'prefer-promise-reject-errors': [ 'error' ],
|
|
87
|
+
'no-throw-literal': [ 'error' ],
|
|
88
|
+
'semi': [ 'off', { omitLastInOneLineBlock: true }], /* does not work right with exports.X = function allmanStyle */
|
|
89
|
+
'semi-style': [ 'warn', 'last' ],
|
|
90
|
+
'semi-spacing': [ 'error', {'before': false, 'after': true}],
|
|
91
|
+
'no-extra-semi': [ 'warn' ],
|
|
92
|
+
'no-tabs': [ 'error' ],
|
|
93
|
+
'symbol-description': [ 'error' ],
|
|
94
|
+
'operator-linebreak': [ 'warn', 'before' ],
|
|
95
|
+
'new-cap': [ 'warn' ],
|
|
96
|
+
'consistent-this': [ 'error', 'that' ],
|
|
97
|
+
'no-use-before-define': [ 'error', { functions: false, classes: false } ],
|
|
98
|
+
'no-shadow': [ 'error' ],
|
|
99
|
+
'no-label-var': [ 'error' ],
|
|
100
|
+
'radix': [ 'error' ],
|
|
101
|
+
'no-self-compare': [ 'error' ],
|
|
102
|
+
'require-await': [ 'error' ],
|
|
103
|
+
'require-yield': [ 'error' ],
|
|
104
|
+
'no-promise-executor-return': [ 'off' ],
|
|
105
|
+
'no-template-curly-in-string': [ 'warn' ],
|
|
106
|
+
'no-unmodified-loop-condition': [ 'warn' ],
|
|
107
|
+
'no-unused-private-class-members': [ 'warn' ],
|
|
108
|
+
'no-use-before-define': ['error', { functions: false, classes: true, variables: true }],
|
|
109
|
+
"no-implicit-coercion": [1, {
|
|
110
|
+
disallowTemplateShorthand: false,
|
|
111
|
+
boolean: true,
|
|
112
|
+
number: true,
|
|
113
|
+
string: true,
|
|
114
|
+
allow: ['!!'] /* really only want to allow if(x) and if(!x) but not if(!!x) */
|
|
115
|
+
}],
|
|
116
|
+
}
|
|
117
|
+
};
|
package/.gitlab-ci.yml
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
default:
|
|
6
6
|
# Use in-house GitLab runners.
|
|
7
7
|
tags:
|
|
8
|
-
- dcp
|
|
8
|
+
- dcp-core
|
|
9
9
|
- linux
|
|
10
10
|
|
|
11
11
|
stages:
|
|
@@ -14,6 +14,8 @@ stages:
|
|
|
14
14
|
|
|
15
15
|
tidelift:
|
|
16
16
|
stage: build
|
|
17
|
+
rules:
|
|
18
|
+
- when: never
|
|
17
19
|
variables:
|
|
18
20
|
# This should be kept in a GitLab Variable. Read more:
|
|
19
21
|
# https://docs.gitlab.com/ee/ci/variables/#create-a-custom-variable-in-the-ui
|
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
#! /usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @file dcp-evaluator-manager
|
|
4
|
+
* Daemon to monitor the state of the system and enable/disable
|
|
5
|
+
* the evaluator as appropriate.
|
|
6
|
+
*
|
|
7
|
+
* The actual mechanics of communicating with the evaluator is
|
|
8
|
+
* similar in principle to how inetd works, and the code is, in
|
|
9
|
+
* fact, based on the dcp inet-daemon.
|
|
10
|
+
*
|
|
11
|
+
* The methods the system can be monitored include:
|
|
12
|
+
* - screensaver enabled/disabled
|
|
13
|
+
*
|
|
14
|
+
* @author Wes Garland, wes@distributive.network
|
|
15
|
+
* @date June 2018, Sep 2022
|
|
16
|
+
*/
|
|
17
|
+
'use strict';
|
|
18
|
+
|
|
19
|
+
const os = require('os');
|
|
20
|
+
const fs = require('fs');
|
|
21
|
+
const path = require('path');
|
|
22
|
+
const net = require('net')
|
|
23
|
+
const dns = require('dns');
|
|
24
|
+
const debug = require('debug');
|
|
25
|
+
const getopt = require('posix-getopt');
|
|
26
|
+
|
|
27
|
+
var userActivity = { /* no props true => okay to launch evaluator */
|
|
28
|
+
screensaver: true,
|
|
29
|
+
session: true,
|
|
30
|
+
};
|
|
31
|
+
var children;
|
|
32
|
+
var seq = 0;
|
|
33
|
+
var ptsDir = '/dev/pts';
|
|
34
|
+
var lastConnectionMs = 0;
|
|
35
|
+
|
|
36
|
+
const defaultPrefix = '/usr';
|
|
37
|
+
const daemonConfig = {
|
|
38
|
+
net: new URL('tcpip://localhost:9000/'),
|
|
39
|
+
proc: require.resolve('./dcp-evaluator-start'),
|
|
40
|
+
argv: [ '-s', '--prefix', `${path.resolve(defaultPrefix)}/` ],
|
|
41
|
+
limits: {},
|
|
42
|
+
session: {
|
|
43
|
+
idlePoll: 3, /* s between checks*/
|
|
44
|
+
idleTimeout: 30, /* s to decide the session really IS idle */
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
function usage()
|
|
49
|
+
{
|
|
50
|
+
console.log(
|
|
51
|
+
`
|
|
52
|
+
DCP Evaluator Monitor - Copyright (c) 2022-2023 Distributive Corp.
|
|
53
|
+
Released under the terms of the MIT License.
|
|
54
|
+
|
|
55
|
+
Usage: ${process.argv[0]} [-h | --help] [--prefix=<dir>] [--pts=<dir>] [-a]
|
|
56
|
+
[--disable-monitor=<session|screensaver>] [-i <timeout>] [-p port]
|
|
57
|
+
[-l | --max-load=<number>] [-r | --rate=<number>] [-L]
|
|
58
|
+
[-- <options to dcp-evaluator-start, must be last option>]
|
|
59
|
+
Where:
|
|
60
|
+
- -i sets the idle timeout in seconds for tty sessions (${daemonConfig.session.idleTimeout}s)
|
|
61
|
+
- -p specifies the port to listen on for dcpsaw: worker connections (${daemonConfig.net.port})
|
|
62
|
+
- --prefix specifies the directory where the DCP evaluator was installed
|
|
63
|
+
- --pts specifies the location of the system's pseudo terminal (${ptsDir})
|
|
64
|
+
- --disable-monitor=session disables monitoring of ssh/telnet/etc sessions
|
|
65
|
+
- --disable-monitor=screensaver disables monitoring of the screensaver
|
|
66
|
+
- -a means always run the evaluator, even when the monitors say the machine is busy
|
|
67
|
+
- --max-load specifies the point at which machine load prevents connections
|
|
68
|
+
- --rate specifies the minimum number of seconds between connections
|
|
69
|
+
- -L only apply the limits when the monitors say the machine is busy
|
|
70
|
+
`);
|
|
71
|
+
|
|
72
|
+
process.exit(0);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Establish listening socket evaluator proxy
|
|
77
|
+
*/
|
|
78
|
+
async function listenForConnections(config)
|
|
79
|
+
{
|
|
80
|
+
var server = net.createServer((socket) => handleConnection(socket, config));
|
|
81
|
+
var hostaddr;
|
|
82
|
+
|
|
83
|
+
if (config.net.hostname === 'localhost')
|
|
84
|
+
hostaddr = '::';
|
|
85
|
+
else
|
|
86
|
+
hostaddr = await dns.promises.lookup(config.net.hostname, { family: 4 });
|
|
87
|
+
|
|
88
|
+
server.on('error', (error) => {
|
|
89
|
+
delete error.stack;
|
|
90
|
+
console.error(error);
|
|
91
|
+
process.exit(2);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
server.listen({ host: hostaddr, port: config.net.port }, function daemonReady() {
|
|
95
|
+
console.log('Listening for connections on', server._connectionKey);
|
|
96
|
+
console.log('Evaluator command is', config.proc, config.argv);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Check the state of the userActivity object to determine if there has been user activity
|
|
102
|
+
* on the system. If there has been, it returns a string of activity types. If there
|
|
103
|
+
* hasn't, it returns false and we are clear to evaluate.
|
|
104
|
+
*/
|
|
105
|
+
function checkUserActivity()
|
|
106
|
+
{
|
|
107
|
+
var active = [];
|
|
108
|
+
|
|
109
|
+
for (let prop in userActivity)
|
|
110
|
+
{
|
|
111
|
+
if (userActivity[prop])
|
|
112
|
+
active.push(prop);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (active.length)
|
|
116
|
+
debug('dcp-evaluator-manager')('System is not idle; activity detected via', active.join(', '));
|
|
117
|
+
else
|
|
118
|
+
debug('dcp-evaluator-manager')('System is idle; checked', Object.entries(userActivity).filter(a => a[1] !== null).map(a => a[0]).join(', '));
|
|
119
|
+
|
|
120
|
+
return active.length ? active.join() : false;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Handle an incoming connection. If the screensaver is not active, the conneciton
|
|
125
|
+
* is immediately rejected. Otherwise, we spin up an evaluator process and proxy its
|
|
126
|
+
* stdio to the socket.
|
|
127
|
+
*/
|
|
128
|
+
function handleConnection (socket, config)
|
|
129
|
+
{
|
|
130
|
+
var child;
|
|
131
|
+
var userActivityMemo = checkUserActivity();
|
|
132
|
+
|
|
133
|
+
function cleanup()
|
|
134
|
+
{
|
|
135
|
+
try
|
|
136
|
+
{
|
|
137
|
+
if (socket)
|
|
138
|
+
{
|
|
139
|
+
socket.end();
|
|
140
|
+
socket.destroy();
|
|
141
|
+
socket = null;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
finally
|
|
145
|
+
{
|
|
146
|
+
if (child)
|
|
147
|
+
{
|
|
148
|
+
child.kill();
|
|
149
|
+
if (children.includes(child))
|
|
150
|
+
children.delete(child);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (userActivityMemo && !daemonConfig.limits.onlyWhenBusy)
|
|
156
|
+
{
|
|
157
|
+
debug('dcp-evaluator-manager')(`New connection; closing due to activity (${userActivityMemo})`);
|
|
158
|
+
cleanup();
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (!daemonConfig.limits.onlyWhenBusy || (daemonConfig.limits.onlyWhenBusy && userActivityMemo))
|
|
163
|
+
{
|
|
164
|
+
if (daemonConfig.limits.maxLoad < os.loadavg()[0])
|
|
165
|
+
{
|
|
166
|
+
debug('dcp-evaluator-manager')(`New connection; closing due to system load (${os.loadavg().join(', ')})`);
|
|
167
|
+
cleanup();
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (daemonConfig.limits.rateMs > (Date.now() - lastConnectionMs))
|
|
172
|
+
{
|
|
173
|
+
debug('dcp-evaluator-manager')(`New connection; closing due to rate-limit ${daemonConfig.limits.rateMs} > ${Date.now() - lastConnectionMs}`);
|
|
174
|
+
cleanup();
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
debug('dcp-evaluator-manager')('New connection; spawning ', config.proc, config.argv);
|
|
180
|
+
lastConnectionMs = Date.now();
|
|
181
|
+
|
|
182
|
+
child = require('child_process').spawn(config.proc, config.argv);
|
|
183
|
+
child.stderr.setEncoding('ascii')
|
|
184
|
+
child.socket = socket;
|
|
185
|
+
children.add(child);
|
|
186
|
+
child.id = ++seq;
|
|
187
|
+
debug('dcp-evaluator-manager')(`Spawned worker process ${child.pid} for child ${child.id}`);
|
|
188
|
+
|
|
189
|
+
socket.on('end', function socketEnd() {
|
|
190
|
+
debug('dcp-evaluator-manager')('Socket end; killing child', child.id);
|
|
191
|
+
cleanup();
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
socket.on('error', function socketError(error) {
|
|
195
|
+
debug('dcp-evaluator-manager')(`Socket error ${error.code}; killing child`, child.id);
|
|
196
|
+
debug('dcp-evaluator-manager')(error);
|
|
197
|
+
cleanup();
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
child.on('error', function childError(error) {
|
|
201
|
+
console.error('Error from worker ' + child.id + ':', error);
|
|
202
|
+
cleanup();
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
child.on('exit', function(code) {
|
|
206
|
+
debug('dcp-evaluator-manager')(`child ${child.id} exited; closing socket`, code || '');
|
|
207
|
+
cleanup();
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
child.stdout.on('data', function (data) {
|
|
211
|
+
debug('dcp-evaluator-manager:network')('<', child.id, bufToDisplayStr(data), 93);
|
|
212
|
+
if (socket)
|
|
213
|
+
socket.write(data)
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
child.stderr.on('data', function (data) {
|
|
217
|
+
console.log('child ' + child.id + ' stderr: ', data);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
socket.on('data', function (data) {
|
|
221
|
+
debug('dcp-evaluator-manager:network')('>', child.id, bufToDisplayStr(data, 93));
|
|
222
|
+
|
|
223
|
+
try
|
|
224
|
+
{
|
|
225
|
+
child.stdin.write(data)
|
|
226
|
+
}
|
|
227
|
+
catch (error)
|
|
228
|
+
{
|
|
229
|
+
console.warn('could not write to child process (', child.pid, ', index', child.id, ') stdin')
|
|
230
|
+
throw error;
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Format detailed debug output of raw socket traffic
|
|
237
|
+
*/
|
|
238
|
+
function bufToDisplayStr (buf, limit)
|
|
239
|
+
{
|
|
240
|
+
const str = buf.toString('utf-8').replace(/\n/, '\u2424').toString('utf-8');
|
|
241
|
+
|
|
242
|
+
if (typeof limit === 'number' && str.length > limit)
|
|
243
|
+
return str.substr(0, limit) + '\u2026';
|
|
244
|
+
else
|
|
245
|
+
return str;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Kill all evaluators, as quickly as possible.
|
|
250
|
+
*/
|
|
251
|
+
function killChildren()
|
|
252
|
+
{
|
|
253
|
+
if (!children.length)
|
|
254
|
+
return;
|
|
255
|
+
|
|
256
|
+
debug('dcp-evaluator-manager')(`Terminating all (${children.length}) running evaluators..`);
|
|
257
|
+
for (let child of children)
|
|
258
|
+
{
|
|
259
|
+
debug('dcp-evaluator-manager')(`killing child ${child.pid}`);
|
|
260
|
+
try
|
|
261
|
+
{
|
|
262
|
+
child.kill('SIGKILL');
|
|
263
|
+
}
|
|
264
|
+
catch(error)
|
|
265
|
+
{
|
|
266
|
+
/* Don't throw if process death race */
|
|
267
|
+
if (error.code !== 'ESRCH')
|
|
268
|
+
throw error;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Monitor dbus messages for screensaver start / stop events.
|
|
275
|
+
*/
|
|
276
|
+
async function dbusScreenSaverMonitor()
|
|
277
|
+
{
|
|
278
|
+
const dbus = odRequire('dbus-next');
|
|
279
|
+
const bus = dbus.sessionBus();
|
|
280
|
+
const screensaverList = [
|
|
281
|
+
'org.gnome.ScreenSaver',
|
|
282
|
+
'org.cinnamon.ScreenSaver',
|
|
283
|
+
'org.kde.screensaver',
|
|
284
|
+
'org.freedesktop.ScreenSaver'
|
|
285
|
+
];
|
|
286
|
+
var iface;
|
|
287
|
+
|
|
288
|
+
for (let ss of screensaverList)
|
|
289
|
+
{
|
|
290
|
+
try
|
|
291
|
+
{
|
|
292
|
+
let obj = await bus.getProxyObject(ss, '/' + ss.replace(/\./g, '/'));
|
|
293
|
+
if (obj)
|
|
294
|
+
{
|
|
295
|
+
iface = obj.interfaces[ss];
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
catch (error)
|
|
300
|
+
{
|
|
301
|
+
debug('dcp-evaluator-manager:dbus')('Could not acquire screensaver', ss + '; trying next', error);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (!iface)
|
|
306
|
+
{
|
|
307
|
+
console.error('Could not open dbus session to any screensaver, tried', screensaverList);
|
|
308
|
+
process.exit(3);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const ssActive = await iface.GetActive();
|
|
312
|
+
debug('dcp-evaluator-manager')('Screen saver active:', ssActive);
|
|
313
|
+
userActivity.screensaver = ssActive ? false : 'active dbus screensaver (initial)';
|
|
314
|
+
|
|
315
|
+
/* screensaver not active => user activity */
|
|
316
|
+
iface.on('ActiveChanged', function screenSaverChangeHandler(active) {
|
|
317
|
+
userActivity.screensaver = active ? false : 'active dbus screensaver (change)';
|
|
318
|
+
debug('dcp-evaluator-manager')(`screen saver ${active ? 'started' : 'finished'} at`, new Date());
|
|
319
|
+
if (!daemonConfig.limits.onlyWhenBusy && checkUserActivity())
|
|
320
|
+
killChildren();
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
console.log('Monitoring dbus messages for', iface.$name);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/* Activate screensaver monitor. Promise resolves when the initial state has been figured out. */
|
|
327
|
+
function screensaverMonitor()
|
|
328
|
+
{
|
|
329
|
+
if (userActivity.screensaver === null) /* disabled */
|
|
330
|
+
return;
|
|
331
|
+
|
|
332
|
+
return dbusScreenSaverMonitor();
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Activate session monitor - mechanism is similar to last/fingerd, where we check the last
|
|
337
|
+
* update time on all ttys to see if there is a remote user using the system or not. We poll
|
|
338
|
+
* this every idleTimeout (15?) seconds, when the screen saver is active.
|
|
339
|
+
*/
|
|
340
|
+
function sessionMonitor()
|
|
341
|
+
{
|
|
342
|
+
const myTTY = fs.realpathSync('/dev/stdout');
|
|
343
|
+
|
|
344
|
+
if (userActivity.session === null) /* disabled */
|
|
345
|
+
return;
|
|
346
|
+
|
|
347
|
+
function checkSessions()
|
|
348
|
+
{
|
|
349
|
+
const recentMs = Date.now() - (daemonConfig.session.idleTimeout * 1000);
|
|
350
|
+
|
|
351
|
+
userActivity.session = false;
|
|
352
|
+
for (let dent of fs.readdirSync(ptsDir, { withFileTypes: true }))
|
|
353
|
+
{
|
|
354
|
+
if (!dent.isCharacterDevice())
|
|
355
|
+
continue;
|
|
356
|
+
|
|
357
|
+
const fullPath = path.join(ptsDir, dent.name);
|
|
358
|
+
debug('dcp-evaluator-manager:session')(`Checking TTY ${fullPath}`);
|
|
359
|
+
if (fullPath === myTTY)
|
|
360
|
+
continue;
|
|
361
|
+
|
|
362
|
+
const sb = fs.statSync(fullPath);
|
|
363
|
+
if (sb.atimeMs > recentMs)
|
|
364
|
+
{
|
|
365
|
+
/* Experiment suggests atime involves reads and mtime includes writes. Using mtime so that
|
|
366
|
+
* tailing logs has anticipated result.
|
|
367
|
+
*/
|
|
368
|
+
userActivity.session = fullPath;
|
|
369
|
+
debug('dcp-evaluator-manager:session')(`TTY ${fullPath} is active`);
|
|
370
|
+
break;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (!daemonConfig.limits.onlyWhenBusy && checkUserActivity())
|
|
375
|
+
killChildren();
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
setInterval(checkSessions, daemonConfig.session.idlePoll * 1000);
|
|
379
|
+
checkSessions();
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/* Require an optional dependency, print decent error message if not found, then exit. */
|
|
383
|
+
function odRequire(moduleId)
|
|
384
|
+
{
|
|
385
|
+
try
|
|
386
|
+
{
|
|
387
|
+
return require(moduleId);
|
|
388
|
+
}
|
|
389
|
+
catch(error)
|
|
390
|
+
{
|
|
391
|
+
if (error.code !== 'MODULE_NOT_FOUND')
|
|
392
|
+
throw error;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
console.error(`Screensaver operating mode requires optional dependency ${moduleId}.`);
|
|
396
|
+
console.error(`Please \`npm i ${moduleId}\` and try again.`);
|
|
397
|
+
process.exit(2);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/* Main program entry point */
|
|
401
|
+
async function main()
|
|
402
|
+
{
|
|
403
|
+
const parser = new getopt.BasicParser('h(help)P:(prefix)d:(disable-monitor)l:(max-load)r:(rate)Li:ap:', process.argv);
|
|
404
|
+
var option;
|
|
405
|
+
|
|
406
|
+
while ((option = parser.getopt()) !== undefined)
|
|
407
|
+
{
|
|
408
|
+
switch (option.option)
|
|
409
|
+
{
|
|
410
|
+
case 'h':
|
|
411
|
+
usage();
|
|
412
|
+
break;
|
|
413
|
+
|
|
414
|
+
default:
|
|
415
|
+
throw new Error('defined but unspecified option', option);
|
|
416
|
+
|
|
417
|
+
case '?':
|
|
418
|
+
process.exit(1);
|
|
419
|
+
|
|
420
|
+
case 'P':
|
|
421
|
+
{
|
|
422
|
+
const re = new RegExp(`^${defaultPrefix}/`);
|
|
423
|
+
for (let i=0; i < daemonConfig.argv.length; i++)
|
|
424
|
+
daemonConfig.argv[i] = daemonConfig.argv[i].replace(re, path.resolve(option.optarg + '/'));
|
|
425
|
+
break;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
case 'd':
|
|
429
|
+
{
|
|
430
|
+
let what = option.optarg;
|
|
431
|
+
|
|
432
|
+
if (what[0] === '=')
|
|
433
|
+
what = what.slice(1);
|
|
434
|
+
userActivity[what] = null; /* null => do not check for this */
|
|
435
|
+
debug('dcp-evaluator-manager')('!', what, 'monitor is disabled');
|
|
436
|
+
break;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
case 'i':
|
|
440
|
+
{
|
|
441
|
+
daemonConfig.session.idleTimeout = Number(option.optarg).toFixed(1);
|
|
442
|
+
break;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
case 'a': /* always on => disable everything */
|
|
446
|
+
{
|
|
447
|
+
for (let prop in userActivity)
|
|
448
|
+
{
|
|
449
|
+
if (userActivity.hasOwnProperty(prop))
|
|
450
|
+
userActivity[prop] = null;
|
|
451
|
+
}
|
|
452
|
+
break;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
case 'L':
|
|
456
|
+
{
|
|
457
|
+
daemonConfig.limits.onlyWhenBusy = true;
|
|
458
|
+
break;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
case 'p':
|
|
462
|
+
{
|
|
463
|
+
daemonConfig.net.port = Number(option.optarg);
|
|
464
|
+
break;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
case 'l':
|
|
468
|
+
{
|
|
469
|
+
daemonConfig.limits.maxLoad = Number(option.optarg);
|
|
470
|
+
break;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
case 'r':
|
|
474
|
+
{
|
|
475
|
+
daemonConfig.limits.rateMs = 1000 * Number(option.optarg);
|
|
476
|
+
break;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
daemonConfig.argv = daemonConfig.argv.concat(process.argv.slice(parser.optind())); /* All options after -- pass to dcp-evaluator-start */
|
|
482
|
+
|
|
483
|
+
process.on('uncaughtException', function (error) {
|
|
484
|
+
console.error('\n---', (new Date()).toLocaleString(), '-------------------------------------------------')
|
|
485
|
+
console.error('uncaught exception:', error.stack)
|
|
486
|
+
console.error('\n')
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
process.on('unhandledRejection', function (error) {
|
|
490
|
+
console.error('\n---', (new Date()).toLocaleString(), '-------------------------------------------------')
|
|
491
|
+
console.error('unhandled rejection:', error.stack)
|
|
492
|
+
console.error('\n')
|
|
493
|
+
})
|
|
494
|
+
|
|
495
|
+
children = new (require('dcp/utils').Inventory)();
|
|
496
|
+
|
|
497
|
+
await screensaverMonitor();
|
|
498
|
+
sessionMonitor();
|
|
499
|
+
|
|
500
|
+
listenForConnections(daemonConfig);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/* Initialize dcp-client to use only local resources before launching the main function */
|
|
504
|
+
require('dcp-client').init({
|
|
505
|
+
progName: 'dcp-worker',
|
|
506
|
+
parseArgv: false,
|
|
507
|
+
configName: process.env.DCP_CONFIG || '../etc/dcp-worker-config',
|
|
508
|
+
dcpConfig: {
|
|
509
|
+
scheduler: { configLocation: false },
|
|
510
|
+
bundle: { location: false },
|
|
511
|
+
}
|
|
512
|
+
}).then(main);
|
package/bin/dcp-worker
CHANGED
|
@@ -160,6 +160,11 @@ function parseCliArgs()
|
|
|
160
160
|
describe: 'If set, dump all sandbox and worker events',
|
|
161
161
|
},
|
|
162
162
|
|
|
163
|
+
showConfig: {
|
|
164
|
+
hide: false,
|
|
165
|
+
describe: 'Show merged configuration node (eg worker)',
|
|
166
|
+
},
|
|
167
|
+
|
|
163
168
|
logfile: {
|
|
164
169
|
describe: 'Path to log file',
|
|
165
170
|
type: 'string',
|
|
@@ -219,6 +224,12 @@ function parseCliArgs()
|
|
|
219
224
|
.wrap(process.stdout.columns || 80)
|
|
220
225
|
.argv;
|
|
221
226
|
|
|
227
|
+
if (cliArgs.showConfig)
|
|
228
|
+
{
|
|
229
|
+
console.log(eval('dcpConfig.' + cliArgs.showConfig));
|
|
230
|
+
process.exit();
|
|
231
|
+
}
|
|
232
|
+
|
|
222
233
|
if (cliArgs.dumpConfig)
|
|
223
234
|
{
|
|
224
235
|
console.log(JSON.stringify(require('dcp/dcp-config'), null, 2));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dcp-worker",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.33",
|
|
4
4
|
"description": "JavaScript portion of DCP Workers for Node.js",
|
|
5
5
|
"main": "bin/dcp-worker",
|
|
6
6
|
"keywords": [
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"license": "MIT",
|
|
18
18
|
"author": "Kings Distributed Systems",
|
|
19
19
|
"bin": {
|
|
20
|
+
"dcp-evaluator-manager": "bin/dcp-evaluator-manager",
|
|
20
21
|
"dcp-evaluator-start": "bin/dcp-evaluator-start",
|
|
21
22
|
"dcp-worker": "bin/dcp-worker"
|
|
22
23
|
},
|
|
@@ -37,11 +38,14 @@
|
|
|
37
38
|
"blessed": "^0.1.81",
|
|
38
39
|
"blessed-contrib": "^4.11.0",
|
|
39
40
|
"chalk": "^4.1.0",
|
|
40
|
-
"dcp-client": "4.3.
|
|
41
|
+
"dcp-client": "4.3.3",
|
|
42
|
+
"kvin": "^1.2.7",
|
|
43
|
+
"posix-getopt": "^1.2.1",
|
|
41
44
|
"semver": "^7.3.8",
|
|
42
45
|
"syslog-client": "1.1.1"
|
|
43
46
|
},
|
|
44
47
|
"optionalDependencies": {
|
|
48
|
+
"dbus-next": "^0.10.2",
|
|
45
49
|
"telnet-console": "^1.0.4"
|
|
46
50
|
},
|
|
47
51
|
"devDependencies": {
|
|
@@ -60,5 +64,8 @@
|
|
|
60
64
|
"engines": {
|
|
61
65
|
"node": ">=18",
|
|
62
66
|
"npm": ">=7"
|
|
67
|
+
},
|
|
68
|
+
"overrides": {
|
|
69
|
+
"@azure/msal-node": "2.2.0"
|
|
63
70
|
}
|
|
64
71
|
}
|