dcp-worker 3.2.32 → 3.2.34
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 +31 -0
- package/.gitlab-ci.yml +88 -5
- package/.trunk/trunk.yaml +29 -0
- package/bin/dcp-evaluator-manager +512 -0
- package/bin/dcp-worker +10 -8
- package/bin/publish-docs.sh +2 -2
- package/package.json +15 -4
- package/.eslintrc.json +0 -12
package/.eslintrc.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file .eslintrc.js - ESLint configuration file, following the Core Team's JS Style Guide.
|
|
3
|
+
*
|
|
4
|
+
* @author Wes Garland <wes@distributive.network>
|
|
5
|
+
* @author Bryan Hoang <bryan@distributive.network>
|
|
6
|
+
* @date Mar. 2022, Sep. 2023
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @see {@link https://eslint.org/docs/latest/use/configure/}
|
|
13
|
+
* @type {import('eslint').Linter.Config}
|
|
14
|
+
*/
|
|
15
|
+
module.exports = {
|
|
16
|
+
root: true,
|
|
17
|
+
reportUnusedDisableDirectives: true,
|
|
18
|
+
extends: ['eslint:recommended', '@distributive'],
|
|
19
|
+
env: {
|
|
20
|
+
node: true,
|
|
21
|
+
es2022: true,
|
|
22
|
+
},
|
|
23
|
+
globals: {
|
|
24
|
+
dcpConfig: true,
|
|
25
|
+
},
|
|
26
|
+
parserOptions: {
|
|
27
|
+
ecmaVersion: 'latest',
|
|
28
|
+
sourceType: 'script',
|
|
29
|
+
},
|
|
30
|
+
rules: {},
|
|
31
|
+
};
|
package/.gitlab-ci.yml
CHANGED
|
@@ -1,23 +1,70 @@
|
|
|
1
1
|
# @file .gitlab-ci.yml - GitLab CI configuration file.
|
|
2
2
|
# @author Eddie Roosenmallen <eddie@distributive.network>
|
|
3
|
+
# @author Bryan Hoang <bryan@distributive.network>
|
|
3
4
|
# @date September 2022
|
|
4
5
|
|
|
6
|
+
variables:
|
|
7
|
+
# Can't cache `~/.npm/`.
|
|
8
|
+
npm_config_cache: '.cache/npm/'
|
|
9
|
+
npm_config_prefer_offline: 'true'
|
|
10
|
+
npm_config_fund: 'false'
|
|
11
|
+
|
|
5
12
|
default:
|
|
6
13
|
# Use in-house GitLab runners.
|
|
7
14
|
tags:
|
|
8
|
-
- dcp
|
|
15
|
+
- dcp-core
|
|
9
16
|
- linux
|
|
17
|
+
image: node:lts
|
|
18
|
+
before_script:
|
|
19
|
+
- npm add --global npm@latest
|
|
20
|
+
# Access private deps. e.g., `dcp-client` from GitLab.
|
|
21
|
+
- git config --global
|
|
22
|
+
url."${CI_SERVER_PROTOCOL}://gitlab-ci-token:${CI_JOB_TOKEN}@${CI_SERVER_HOST}:${CI_SERVER_PORT}".insteadOf
|
|
23
|
+
"${CI_SERVER_URL}"
|
|
24
|
+
|
|
25
|
+
workflow:
|
|
26
|
+
rules:
|
|
27
|
+
# Only run branch pipelines for `develop`, or merge request pipelines.
|
|
28
|
+
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
|
29
|
+
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
10
30
|
|
|
11
31
|
stages:
|
|
12
32
|
- build
|
|
33
|
+
- test
|
|
13
34
|
- deploy
|
|
14
35
|
|
|
36
|
+
# Installs npm dependencies to be cached for subsequent jobs. Also checks that
|
|
37
|
+
# the lock file is synced with the manifest file.
|
|
38
|
+
build:
|
|
39
|
+
stage: build
|
|
40
|
+
script:
|
|
41
|
+
- npm clean-install
|
|
42
|
+
cache:
|
|
43
|
+
- key:
|
|
44
|
+
prefix: $CI_JOB_NAME_SLUG
|
|
45
|
+
files:
|
|
46
|
+
- package-lock.json
|
|
47
|
+
paths:
|
|
48
|
+
- $npm_config_cache
|
|
49
|
+
- key:
|
|
50
|
+
prefix: node-modules
|
|
51
|
+
files:
|
|
52
|
+
- package-lock.json
|
|
53
|
+
paths:
|
|
54
|
+
- node_modules/
|
|
55
|
+
policy: push
|
|
56
|
+
|
|
15
57
|
tidelift:
|
|
16
58
|
stage: build
|
|
59
|
+
rules:
|
|
60
|
+
- when: never
|
|
17
61
|
variables:
|
|
18
62
|
# This should be kept in a GitLab Variable. Read more:
|
|
19
63
|
# https://docs.gitlab.com/ee/ci/variables/#create-a-custom-variable-in-the-ui
|
|
20
64
|
TIDELIFT_API_KEY: $TIDELIFT_API_KEY
|
|
65
|
+
inherit:
|
|
66
|
+
default:
|
|
67
|
+
- tags
|
|
21
68
|
# The CLI only requires `glibc`.
|
|
22
69
|
image: frolvlad/alpine-glibc
|
|
23
70
|
before_script:
|
|
@@ -30,9 +77,39 @@ tidelift:
|
|
|
30
77
|
- echo "Running alignment and saving to Tidelift"
|
|
31
78
|
- ./tidelift alignment save --wait
|
|
32
79
|
allow_failure: true
|
|
33
|
-
|
|
80
|
+
|
|
81
|
+
# Run `trunk check` on the project to hightlight newly introduced lint issues
|
|
82
|
+
# when compared to the target branch.
|
|
83
|
+
check:
|
|
84
|
+
variables:
|
|
85
|
+
# Can't cache `~/.cache/trunk/`.
|
|
86
|
+
TRUNK_CACHE: ${CI_PROJECT_DIR}/.cache/trunk/
|
|
87
|
+
stage: test
|
|
88
|
+
script:
|
|
89
|
+
- UPSTREAM_BRANCH=${CI_MERGE_REQUEST_TARGET_BRANCH_NAME:-$CI_DEFAULT_BRANCH}
|
|
90
|
+
# Fetch the branch so that `trunk` has the proper information needed to
|
|
91
|
+
# determine the upstream changes to compare lint results against.
|
|
92
|
+
- git fetch origin $UPSTREAM_BRANCH
|
|
93
|
+
- npm run check -- --ci --upstream origin/$UPSTREAM_BRANCH
|
|
94
|
+
cache:
|
|
95
|
+
- key:
|
|
96
|
+
prefix: $CI_JOB_NAME_SLUG
|
|
97
|
+
files:
|
|
98
|
+
- .trunk/trunk.yaml
|
|
99
|
+
paths:
|
|
100
|
+
- $TRUNK_CACHE
|
|
101
|
+
- key:
|
|
102
|
+
prefix: node-modules
|
|
103
|
+
files:
|
|
104
|
+
- package-lock.json
|
|
105
|
+
paths:
|
|
106
|
+
- node_modules/
|
|
107
|
+
policy: pull
|
|
34
108
|
|
|
35
109
|
publish_docs:
|
|
110
|
+
stage: deploy
|
|
111
|
+
needs:
|
|
112
|
+
- build
|
|
36
113
|
rules:
|
|
37
114
|
- if: $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH
|
|
38
115
|
changes:
|
|
@@ -51,8 +128,14 @@ publish_docs:
|
|
|
51
128
|
ENTITY_NAMESPACE: 'default'
|
|
52
129
|
stage: deploy
|
|
53
130
|
needs: []
|
|
54
|
-
image: registry.gitlab.com/distributed-compute-protocol/
|
|
131
|
+
image: registry.gitlab.com/distributed-compute-protocol/backstage-ci-image:latest
|
|
55
132
|
script:
|
|
56
|
-
- npm clean-install
|
|
57
|
-
- python3 -m pip install mkdocs mkdocs-techdocs-core==1.* mkdocs-same-dir
|
|
58
133
|
- bin/publish-docs.sh
|
|
134
|
+
cache:
|
|
135
|
+
- key:
|
|
136
|
+
prefix: node-modules
|
|
137
|
+
files:
|
|
138
|
+
- package-lock.json
|
|
139
|
+
paths:
|
|
140
|
+
- node_modules/
|
|
141
|
+
policy: pull
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# @file trunk.yaml - This file controls the behavior of Trunk:
|
|
2
|
+
# https://docs.trunk.io/cli
|
|
3
|
+
#
|
|
4
|
+
# To learn more about the format of this file, see
|
|
5
|
+
# https://docs.trunk.io/reference/trunk-yaml
|
|
6
|
+
#
|
|
7
|
+
# Initially autogenerated by `npm exec -- trunk init --lock`
|
|
8
|
+
#
|
|
9
|
+
# @author Bryan Hoang <bryan@distributive.network>
|
|
10
|
+
# @date Oct. 2023
|
|
11
|
+
|
|
12
|
+
version: 0.1
|
|
13
|
+
cli:
|
|
14
|
+
version: 1.17.1
|
|
15
|
+
sha256:
|
|
16
|
+
darwin_arm64: 707359316ea1f972cbad8a77cb076449155c19d42933cecfe5b70cfefc38d085
|
|
17
|
+
darwin_x86_64: d5e5928a24a4eb9931eaed1d5899be12ab2556f17ce5cd89ef73fa42b2293c88
|
|
18
|
+
linux_x86_64: f2bbf8de19e7961390633fe23599b8890f617a6e8b2563ac6741a0514e7d16e6
|
|
19
|
+
plugins:
|
|
20
|
+
sources:
|
|
21
|
+
- id: trunk
|
|
22
|
+
ref: v1.2.6
|
|
23
|
+
uri: https://github.com/trunk-io/plugins
|
|
24
|
+
runtimes:
|
|
25
|
+
enabled:
|
|
26
|
+
- node@18.12.1
|
|
27
|
+
lint:
|
|
28
|
+
enabled:
|
|
29
|
+
- eslint@8.52.0
|
|
@@ -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
|
@@ -155,9 +155,10 @@ function parseCliArgs()
|
|
|
155
155
|
type: 'number',
|
|
156
156
|
group: 'Output options',
|
|
157
157
|
},
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
158
|
+
|
|
159
|
+
showConfig: {
|
|
160
|
+
hide: false,
|
|
161
|
+
describe: 'Show merged configuration node (eg worker)',
|
|
161
162
|
},
|
|
162
163
|
|
|
163
164
|
logfile: {
|
|
@@ -219,6 +220,12 @@ function parseCliArgs()
|
|
|
219
220
|
.wrap(process.stdout.columns || 80)
|
|
220
221
|
.argv;
|
|
221
222
|
|
|
223
|
+
if (cliArgs.showConfig)
|
|
224
|
+
{
|
|
225
|
+
console.log(eval('dcpConfig.' + cliArgs.showConfig));
|
|
226
|
+
process.exit();
|
|
227
|
+
}
|
|
228
|
+
|
|
222
229
|
if (cliArgs.dumpConfig)
|
|
223
230
|
{
|
|
224
231
|
console.log(JSON.stringify(require('dcp/dcp-config'), null, 2));
|
|
@@ -391,9 +398,6 @@ async function main()
|
|
|
391
398
|
else
|
|
392
399
|
worker.on('end', () => setTimeout(processExit, getCleanupTimeoutMs()).unref());
|
|
393
400
|
|
|
394
|
-
if (cliArgs.eventDebug)
|
|
395
|
-
worker.enableDebugEvents = true;
|
|
396
|
-
|
|
397
401
|
if (dcpWorkerOptions.publicGroupFallback)
|
|
398
402
|
{
|
|
399
403
|
if (dcpWorkerOptions.leavePublicGroup)
|
|
@@ -449,8 +453,6 @@ async function main()
|
|
|
449
453
|
introBanner += ` . Target core density: ${JSON.stringify(dcpWorkerOptions.defaultCoreDensity)}\n`;
|
|
450
454
|
if (cliArgs.verbose)
|
|
451
455
|
introBanner += ` + Verbosity level: ${cliArgs.verbose}` + '\n';
|
|
452
|
-
if (cliArgs.eventDebug)
|
|
453
|
-
introBanner += ' + Event debug on' + '\n';
|
|
454
456
|
if (telnetd.hasOwnProperty('port'))
|
|
455
457
|
introBanner += ` ! telnetd listening on port ${telnetd.port}\n`;
|
|
456
458
|
|
package/bin/publish-docs.sh
CHANGED
|
@@ -67,8 +67,8 @@ npx techdocs-cli publish \
|
|
|
67
67
|
--publisher-type awsS3 \
|
|
68
68
|
--storage-name "$TECHDOCS_S3_BUCKET_NAME" \
|
|
69
69
|
--entity "$ENTITY_NAMESPACE"/"$ENTITY_KIND"/"$ENTITY_NAME" \
|
|
70
|
-
--directory "$
|
|
70
|
+
--directory "$DOCS_DIR"/site
|
|
71
71
|
|
|
72
|
-
rm -r "$
|
|
72
|
+
rm -r "$DOCS_DIR"/site
|
|
73
73
|
|
|
74
74
|
echo "View generated component: https://backstage.overwatch.distributive.network/docs/default/component/$ENTITY_NAME"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dcp-worker",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.34",
|
|
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
|
},
|
|
@@ -25,9 +26,11 @@
|
|
|
25
26
|
"etc": "etc"
|
|
26
27
|
},
|
|
27
28
|
"scripts": {
|
|
29
|
+
"check": "trunk check",
|
|
28
30
|
"start": "node bin/dcp-worker start",
|
|
29
31
|
"start-evaluator": "node bin/dcp-evaluator-start",
|
|
30
32
|
"hook": "PATH=npm-hooks:$PATH &&",
|
|
33
|
+
"lint": "eslint --cache --cache-strategy=content --cache-location=.cache/eslint/ --ignore-path=.gitignore --ext=js .",
|
|
31
34
|
"test": "peter tests",
|
|
32
35
|
"prepack": "node npm-hooks/prepack",
|
|
33
36
|
"postpublish": "npm-hooks/postpublish",
|
|
@@ -37,17 +40,22 @@
|
|
|
37
40
|
"blessed": "^0.1.81",
|
|
38
41
|
"blessed-contrib": "^4.11.0",
|
|
39
42
|
"chalk": "^4.1.0",
|
|
40
|
-
"dcp-client": "4.3.
|
|
43
|
+
"dcp-client": "4.3.4",
|
|
44
|
+
"kvin": "^1.2.7",
|
|
45
|
+
"posix-getopt": "^1.2.1",
|
|
41
46
|
"semver": "^7.3.8",
|
|
42
47
|
"syslog-client": "1.1.1"
|
|
43
48
|
},
|
|
44
49
|
"optionalDependencies": {
|
|
50
|
+
"dbus-next": "^0.10.2",
|
|
45
51
|
"telnet-console": "^1.0.4"
|
|
46
52
|
},
|
|
47
53
|
"devDependencies": {
|
|
54
|
+
"@distributive/eslint-plugin": "1.0.2",
|
|
48
55
|
"@kingsds/eslint-config": "^1.0.1",
|
|
49
|
-
"@
|
|
50
|
-
"eslint": ">=8"
|
|
56
|
+
"@trunkio/launcher": "1.2.7",
|
|
57
|
+
"eslint": ">=8",
|
|
58
|
+
"eslint-plugin-jsdoc": "46.8.2"
|
|
51
59
|
},
|
|
52
60
|
"peerDependencies": {
|
|
53
61
|
"node-eventlog": "https://gitpkg.now.sh/Distributive-Network/node-eventlog/package?dcp/0.0.1"
|
|
@@ -60,5 +68,8 @@
|
|
|
60
68
|
"engines": {
|
|
61
69
|
"node": ">=18",
|
|
62
70
|
"npm": ">=7"
|
|
71
|
+
},
|
|
72
|
+
"overrides": {
|
|
73
|
+
"@azure/msal-node": "2.2.0"
|
|
63
74
|
}
|
|
64
75
|
}
|