iobroker.bee 0.0.0-stage → 0.2.2
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/README.md +111 -2
- package/admin/bee.svg +1 -0
- package/admin/jsonConfig.json +321 -0
- package/build/lib/commands.js +96 -0
- package/build/lib/commands.js.map +1 -0
- package/build/lib/console.js +218 -0
- package/build/lib/console.js.map +1 -0
- package/build/lib/enrollment.js +37 -0
- package/build/lib/enrollment.js.map +1 -0
- package/build/lib/inventory.js +136 -0
- package/build/lib/inventory.js.map +1 -0
- package/build/lib/jobs.js +251 -0
- package/build/lib/jobs.js.map +1 -0
- package/build/lib/packages.js +139 -0
- package/build/lib/packages.js.map +1 -0
- package/build/lib/planner.js +128 -0
- package/build/lib/planner.js.map +1 -0
- package/build/lib/preflight.js +90 -0
- package/build/lib/preflight.js.map +1 -0
- package/build/lib/protocol.js +70 -0
- package/build/lib/protocol.js.map +1 -0
- package/build/lib/remote.js +211 -0
- package/build/lib/remote.js.map +1 -0
- package/build/lib/transport.js +158 -0
- package/build/lib/transport.js.map +1 -0
- package/build/lib/tunnel.js +293 -0
- package/build/lib/tunnel.js.map +1 -0
- package/build/lib/util.js +102 -0
- package/build/lib/util.js.map +1 -0
- package/build/main.js +835 -0
- package/build/main.js.map +1 -0
- package/build/runner.js +193 -0
- package/build/runner.js.map +1 -0
- package/io-package.json +434 -0
- package/package.json +54 -4
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.MAX_SHELLS = void 0;
|
|
7
|
+
exports.findScript = findScript;
|
|
8
|
+
exports.loginShell = loginShell;
|
|
9
|
+
exports.consoleUser = consoleUser;
|
|
10
|
+
exports.parentOf = parentOf;
|
|
11
|
+
exports.startShell = startShell;
|
|
12
|
+
const node_child_process_1 = require("node:child_process");
|
|
13
|
+
const node_fs_1 = require("node:fs");
|
|
14
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
15
|
+
const tunnel_1 = require("./tunnel");
|
|
16
|
+
/**
|
|
17
|
+
* Console of the remote access: a shell on this system for one terminal in the browser of the technician.
|
|
18
|
+
* Each stream of the tunnel is one shell. No native module: `script` of util-linux (on every Debian) gives the
|
|
19
|
+
* shell a pseudo terminal, and `stty` sets the size of its window. Only Linux, and only when the owner allows it.
|
|
20
|
+
*/
|
|
21
|
+
/** Shells per access session at the same time */
|
|
22
|
+
exports.MAX_SHELLS = 4;
|
|
23
|
+
/** `script` of util-linux, null if this system has none (then there is no console) */
|
|
24
|
+
function findScript() {
|
|
25
|
+
if (process.platform !== 'linux') {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
return ['/usr/bin/script', '/bin/script'].find(p => (0, node_fs_1.existsSync)(p)) ?? null;
|
|
29
|
+
}
|
|
30
|
+
function findStty() {
|
|
31
|
+
return ['/usr/bin/stty', '/bin/stty'].find(p => (0, node_fs_1.existsSync)(p)) ?? 'stty';
|
|
32
|
+
}
|
|
33
|
+
/** A shell to log in with; the one of a service user is often nologin, so not taken from /etc/passwd */
|
|
34
|
+
function loginShell(env = process.env) {
|
|
35
|
+
return ([env.SHELL, '/bin/bash', '/bin/sh'].find(s => !!s && !/(nologin|false)$/.test(s) && (0, node_fs_1.existsSync)(s)) ?? '/bin/sh');
|
|
36
|
+
}
|
|
37
|
+
/** The user the shell runs as: the user of ioBroker */
|
|
38
|
+
function consoleUser() {
|
|
39
|
+
try {
|
|
40
|
+
return node_os_1.default.userInfo().username;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return String(process.getuid?.() ?? '');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** Parent process id from /proc/<pid>/stat; the name in parentheses may contain spaces and parentheses */
|
|
47
|
+
function parentOf(stat) {
|
|
48
|
+
const rest = stat.slice(stat.lastIndexOf(')') + 2).split(' ');
|
|
49
|
+
return parseInt(rest[1], 10);
|
|
50
|
+
}
|
|
51
|
+
/** The child of `script` (the shell) and the terminal it reads from, e.g. /dev/pts/3 */
|
|
52
|
+
function findTerminal(scriptPid) {
|
|
53
|
+
for (const entry of (0, node_fs_1.readdirSync)('/proc')) {
|
|
54
|
+
if (!/^\d+$/.test(entry)) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
if (parentOf((0, node_fs_1.readFileSync)(`/proc/${entry}/stat`, 'utf8')) === scriptPid) {
|
|
59
|
+
const tty = (0, node_fs_1.readlinkSync)(`/proc/${entry}/fd/0`);
|
|
60
|
+
if (tty.startsWith('/dev/pts/')) {
|
|
61
|
+
return { pid: parseInt(entry, 10), tty };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
// the process ended meanwhile, or is not ours
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
function signalGroup(pid, signal) {
|
|
72
|
+
if (!pid) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
process.kill(-pid, signal);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// already gone
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Runs one shell for a stream of the tunnel until one of both ends.
|
|
84
|
+
*
|
|
85
|
+
* @param stream stream of the tunnel, carrying console messages (see tunnel.ts)
|
|
86
|
+
* @param opts working directory, logger and who opened it
|
|
87
|
+
*/
|
|
88
|
+
function startShell(stream, opts) {
|
|
89
|
+
const script = findScript();
|
|
90
|
+
if (!script) {
|
|
91
|
+
stream.end((0, tunnel_1.consoleMessage)(tunnel_1.CONSOLE.EXIT, Buffer.from('keine Konsole auf diesem System')));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const shell = loginShell();
|
|
95
|
+
const home = (() => {
|
|
96
|
+
try {
|
|
97
|
+
return node_os_1.default.userInfo().homedir;
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return opts.cwd;
|
|
101
|
+
}
|
|
102
|
+
})();
|
|
103
|
+
// a fresh login environment instead of the one of the adapter process
|
|
104
|
+
const env = {
|
|
105
|
+
HOME: home,
|
|
106
|
+
USER: consoleUser(),
|
|
107
|
+
LOGNAME: consoleUser(),
|
|
108
|
+
SHELL: shell,
|
|
109
|
+
PATH: process.env.PATH || '/usr/local/bin:/usr/bin:/bin',
|
|
110
|
+
LANG: process.env.LANG || 'C.UTF-8',
|
|
111
|
+
TERM: 'xterm-256color',
|
|
112
|
+
COLORTERM: 'truecolor',
|
|
113
|
+
};
|
|
114
|
+
// detached: own process group, so that the end reaches everything started in it
|
|
115
|
+
const child = (0, node_child_process_1.spawn)(script, ['-qfec', `exec ${shell} -l`, '/dev/null'], {
|
|
116
|
+
cwd: (0, node_fs_1.existsSync)(opts.cwd) ? opts.cwd : home,
|
|
117
|
+
env,
|
|
118
|
+
detached: true,
|
|
119
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
120
|
+
});
|
|
121
|
+
let terminal = null;
|
|
122
|
+
let size = null;
|
|
123
|
+
let ended = false;
|
|
124
|
+
let killTimer = null;
|
|
125
|
+
const applySize = () => {
|
|
126
|
+
if (terminal && size) {
|
|
127
|
+
// the kernel tells the shell (SIGWINCH)
|
|
128
|
+
(0, node_child_process_1.execFile)(findStty(), ['-F', terminal.tty, 'cols', String(size.cols), 'rows', String(size.rows)], err => {
|
|
129
|
+
if (err) {
|
|
130
|
+
opts.log.debug(`Console: stty: ${err.message}`);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
// the shell needs a moment until it runs on its terminal
|
|
136
|
+
let tries = 0;
|
|
137
|
+
const lookup = setInterval(() => {
|
|
138
|
+
if (ended || terminal || ++tries > 60 || !child.pid) {
|
|
139
|
+
clearInterval(lookup);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
terminal = findTerminal(child.pid);
|
|
143
|
+
if (terminal) {
|
|
144
|
+
clearInterval(lookup);
|
|
145
|
+
applySize();
|
|
146
|
+
}
|
|
147
|
+
}, 50);
|
|
148
|
+
const stop = () => {
|
|
149
|
+
// nobody waits for a drain any more: read the pipes to their end, or the end of the shell is never seen
|
|
150
|
+
child.stdout?.resume();
|
|
151
|
+
child.stderr?.resume();
|
|
152
|
+
if (killTimer || ended) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
// hang up: the shell and what runs in its foreground get SIGHUP, as when an SSH connection drops
|
|
156
|
+
signalGroup(terminal?.pid, 'SIGHUP');
|
|
157
|
+
signalGroup(child.pid, 'SIGHUP');
|
|
158
|
+
killTimer = setTimeout(() => {
|
|
159
|
+
signalGroup(terminal?.pid, 'SIGKILL');
|
|
160
|
+
signalGroup(child.pid, 'SIGKILL');
|
|
161
|
+
}, 3000);
|
|
162
|
+
};
|
|
163
|
+
const forward = (chunk) => {
|
|
164
|
+
if (stream.destroyed || !stream.writable) {
|
|
165
|
+
// the browser is gone, the shell is being hung up: drop what it still writes
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (!stream.write((0, tunnel_1.consoleMessage)(tunnel_1.CONSOLE.DATA, chunk))) {
|
|
169
|
+
// the browser is slow: hold the shell back
|
|
170
|
+
child.stdout?.pause();
|
|
171
|
+
child.stderr?.pause();
|
|
172
|
+
stream.once('drain', () => {
|
|
173
|
+
child.stdout?.resume();
|
|
174
|
+
child.stderr?.resume();
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
child.stdout?.on('data', forward);
|
|
179
|
+
child.stderr?.on('data', forward);
|
|
180
|
+
child.stdin?.on('error', () => { });
|
|
181
|
+
const reader = new tunnel_1.ConsoleReader((type, payload) => {
|
|
182
|
+
if (type === tunnel_1.CONSOLE.DATA) {
|
|
183
|
+
child.stdin?.write(payload);
|
|
184
|
+
}
|
|
185
|
+
else if (type === tunnel_1.CONSOLE.RESIZE && payload.length >= 4) {
|
|
186
|
+
const clamp = (n) => Math.max(1, Math.min(1000, n));
|
|
187
|
+
size = { cols: clamp(payload.readUInt16BE(0)), rows: clamp(payload.readUInt16BE(2)) };
|
|
188
|
+
applySize();
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
stream.on('data', (chunk) => {
|
|
192
|
+
if (!reader.push(chunk)) {
|
|
193
|
+
stream.destroy(new Error('invalid console message'));
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
stream.on('end', stop);
|
|
197
|
+
stream.on('close', stop);
|
|
198
|
+
stream.on('error', stop);
|
|
199
|
+
child.on('error', err => {
|
|
200
|
+
opts.log.warn(`Console: ${err.message}`);
|
|
201
|
+
stop();
|
|
202
|
+
});
|
|
203
|
+
child.on('close', (code, signal) => {
|
|
204
|
+
ended = true;
|
|
205
|
+
clearInterval(lookup);
|
|
206
|
+
if (killTimer) {
|
|
207
|
+
clearTimeout(killTimer);
|
|
208
|
+
}
|
|
209
|
+
const result = signal ? signal : String(code ?? '');
|
|
210
|
+
if (!stream.destroyed && stream.writable) {
|
|
211
|
+
stream.end((0, tunnel_1.consoleMessage)(tunnel_1.CONSOLE.EXIT, Buffer.from(result)));
|
|
212
|
+
}
|
|
213
|
+
opts.log.info(`Console of ${opts.by} ended (${result})`);
|
|
214
|
+
opts.onEnd?.(result);
|
|
215
|
+
});
|
|
216
|
+
opts.log.info(`Console: shell ${shell} as ${consoleUser()} started for ${opts.by}`);
|
|
217
|
+
}
|
|
218
|
+
//# sourceMappingURL=console.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"console.js","sourceRoot":"","sources":["../../src/lib/console.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.enroll = enroll;
|
|
4
|
+
async function enroll(url, req) {
|
|
5
|
+
let res;
|
|
6
|
+
try {
|
|
7
|
+
res = await fetch(`${url.replace(/\/+$/, '')}/api/enroll`, {
|
|
8
|
+
method: 'POST',
|
|
9
|
+
headers: { 'Content-Type': 'application/json' },
|
|
10
|
+
body: JSON.stringify(req),
|
|
11
|
+
signal: AbortSignal.timeout(30_000),
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
catch (e) {
|
|
15
|
+
return { kind: 'retry', reason: e.cause?.message || e.message };
|
|
16
|
+
}
|
|
17
|
+
let body = null;
|
|
18
|
+
try {
|
|
19
|
+
body = await res.json();
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// ignore, handled by status
|
|
23
|
+
}
|
|
24
|
+
if (res.ok) {
|
|
25
|
+
const creds = body;
|
|
26
|
+
if (!creds?.deviceId || !creds.cert || !creds.key || !creds.ca || !creds.mqttUrl) {
|
|
27
|
+
return { kind: 'retry', reason: 'Unvollständige Antwort des Servers' };
|
|
28
|
+
}
|
|
29
|
+
return { kind: 'ok', creds };
|
|
30
|
+
}
|
|
31
|
+
const reason = body?.reason || `HTTP ${res.status}`;
|
|
32
|
+
if (res.status === 403 || res.status === 400) {
|
|
33
|
+
return { kind: 'rejected', reason };
|
|
34
|
+
}
|
|
35
|
+
return { kind: 'retry', reason };
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=enrollment.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enrollment.js","sourceRoot":"","sources":["../../src/lib/enrollment.ts"],"names":[],"mappings":";;AAYA,wBA8BC;AA9BM,KAAK,UAAU,MAAM,CAAC,GAAW,EAAE,GAAkB;IACxD,IAAI,GAAa,CAAC;IAClB,IAAI,CAAC;QACD,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,aAAa,EAAE;YACvD,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC;YACzB,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC;SACtC,CAAC,CAAC;IACP,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACT,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,KAAK,EAAE,OAAO,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;IACpE,CAAC;IACD,IAAI,IAAI,GAAY,IAAI,CAAC;IACzB,IAAI,CAAC;QACD,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACL,4BAA4B;IAChC,CAAC;IACD,IAAI,GAAG,CAAC,EAAE,EAAE,CAAC;QACT,MAAM,KAAK,GAAG,IAAsB,CAAC;QACrC,IAAI,CAAC,KAAK,EAAE,QAAQ,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;YAC/E,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,oCAAoC,EAAE,CAAC;QAC3E,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IACjC,CAAC;IACD,MAAM,MAAM,GAAI,IAA2B,EAAE,MAAM,IAAI,QAAQ,GAAG,CAAC,MAAM,EAAE,CAAC;IAC5E,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;QAC3C,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC;IACxC,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;AACrC,CAAC"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.sdWrites = sdWrites;
|
|
7
|
+
exports.diskInfo = diskInfo;
|
|
8
|
+
exports.cpuTemperature = cpuTemperature;
|
|
9
|
+
exports.collectInventory = collectInventory;
|
|
10
|
+
exports.installedVersions = installedVersions;
|
|
11
|
+
const node_child_process_1 = require("node:child_process");
|
|
12
|
+
const promises_1 = require("node:fs/promises");
|
|
13
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
14
|
+
/**
|
|
15
|
+
* Collects the inventory of this ioBroker system. Versions and system data only;
|
|
16
|
+
* no measured values of the end customer (data minimization).
|
|
17
|
+
*/
|
|
18
|
+
async function readText(path) {
|
|
19
|
+
try {
|
|
20
|
+
return (await (0, promises_1.readFile)(path, 'utf8')).trim();
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
async function osName() {
|
|
27
|
+
const release = await readText('/etc/os-release');
|
|
28
|
+
const m = release && /^PRETTY_NAME="?([^"\n]+)"?/m.exec(release);
|
|
29
|
+
return m ? m[1] : `${node_os_1.default.type()} ${node_os_1.default.version?.() || node_os_1.default.release()}`;
|
|
30
|
+
}
|
|
31
|
+
let npmVersion = null;
|
|
32
|
+
async function npmVer() {
|
|
33
|
+
if (npmVersion === null) {
|
|
34
|
+
npmVersion = await new Promise(resolve => {
|
|
35
|
+
(0, node_child_process_1.execFile)(node_os_1.default.platform() === 'win32' ? 'npm.cmd' : 'npm', ['-v'], { timeout: 20_000, shell: node_os_1.default.platform() === 'win32' }, (err, stdout) => resolve(err ? '' : stdout.trim()));
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return npmVersion || '';
|
|
39
|
+
}
|
|
40
|
+
/** Sectors written to the SD card since boot (field 7 of /sys/block/mmcblk0/stat) */
|
|
41
|
+
async function sdWrites() {
|
|
42
|
+
const stat = await readText('/sys/block/mmcblk0/stat');
|
|
43
|
+
if (!stat) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
const fields = stat.split(/\s+/);
|
|
47
|
+
return fields.length > 6 ? parseInt(fields[6], 10) : null;
|
|
48
|
+
}
|
|
49
|
+
async function diskInfo(path) {
|
|
50
|
+
try {
|
|
51
|
+
const s = await (0, promises_1.statfs)(path);
|
|
52
|
+
return { total: s.blocks * s.bsize, free: s.bavail * s.bsize };
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async function cpuTemperature() {
|
|
59
|
+
const t = await readText('/sys/class/thermal/thermal_zone0/temp');
|
|
60
|
+
return t ? Math.round(parseInt(t, 10) / 100) / 10 : null;
|
|
61
|
+
}
|
|
62
|
+
/** backitup targets that store the backup outside the device */
|
|
63
|
+
const OFF_DEVICE_KEYS = [
|
|
64
|
+
'cifsEnabled',
|
|
65
|
+
'ftpEnabled',
|
|
66
|
+
'dropboxEnabled',
|
|
67
|
+
'googledriveEnabled',
|
|
68
|
+
'onedriveEnabled',
|
|
69
|
+
'webdavEnabled',
|
|
70
|
+
'sftpEnabled',
|
|
71
|
+
];
|
|
72
|
+
async function collectInventory(adapter, opts) {
|
|
73
|
+
const adapterRows = await adapter.getObjectViewAsync('system', 'adapter', {
|
|
74
|
+
startkey: 'system.adapter.',
|
|
75
|
+
endkey: 'system.adapter.香',
|
|
76
|
+
});
|
|
77
|
+
const adapters = {};
|
|
78
|
+
for (const row of adapterRows.rows) {
|
|
79
|
+
const common = row.value?.common;
|
|
80
|
+
if (common?.name && common.version) {
|
|
81
|
+
adapters[common.name] = {
|
|
82
|
+
version: common.version,
|
|
83
|
+
...(common.installedFrom ? { installedFrom: common.installedFrom } : {}),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const instanceRows = await adapter.getObjectViewAsync('system', 'instance', {
|
|
88
|
+
startkey: 'system.adapter.',
|
|
89
|
+
endkey: 'system.adapter.香',
|
|
90
|
+
});
|
|
91
|
+
const instances = {};
|
|
92
|
+
let offDevice = false;
|
|
93
|
+
for (const row of instanceRows.rows) {
|
|
94
|
+
const obj = row.value;
|
|
95
|
+
if (!obj || obj.common.host !== adapter.host) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const id = obj._id.replace(/^system\.adapter\./, '');
|
|
99
|
+
const alive = await adapter.getForeignStateAsync(`${obj._id}.alive`);
|
|
100
|
+
instances[id] = { enabled: !!obj.common.enabled, alive: !!alive?.val, mode: obj.common.mode };
|
|
101
|
+
if (obj.common.name === 'backitup' && obj.common.enabled) {
|
|
102
|
+
const native = obj.native;
|
|
103
|
+
offDevice ||= OFF_DEVICE_KEYS.some(k => native[k] === true);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const hostObj = await adapter.getForeignObjectAsync(`system.host.${adapter.host}`);
|
|
107
|
+
const disk = await diskInfo(opts.dataDir);
|
|
108
|
+
const model = (await readText('/proc/device-tree/model'))?.replace(/\0/g, '') || node_os_1.default.cpus()[0]?.model || '';
|
|
109
|
+
return {
|
|
110
|
+
agentVersion: opts.agentVersion,
|
|
111
|
+
uuid: '',
|
|
112
|
+
hostname: adapter.host || node_os_1.default.hostname(),
|
|
113
|
+
software: {
|
|
114
|
+
jsController: hostObj?.common?.installedVersion || '',
|
|
115
|
+
node: process.version,
|
|
116
|
+
npm: await npmVer(),
|
|
117
|
+
adapters,
|
|
118
|
+
instances,
|
|
119
|
+
},
|
|
120
|
+
system: {
|
|
121
|
+
platform: node_os_1.default.platform(),
|
|
122
|
+
arch: node_os_1.default.arch(),
|
|
123
|
+
os: await osName(),
|
|
124
|
+
kernel: node_os_1.default.release(),
|
|
125
|
+
model,
|
|
126
|
+
memTotal: node_os_1.default.totalmem(),
|
|
127
|
+
diskTotal: disk?.total ?? 0,
|
|
128
|
+
},
|
|
129
|
+
backup: { lastLocal: opts.lastBackup ?? null, offDevice },
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/** Installed adapter versions (name -> version) */
|
|
133
|
+
function installedVersions(inv) {
|
|
134
|
+
return Object.fromEntries(Object.entries(inv.software.adapters).map(([n, a]) => [n, a.version]));
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=inventory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inventory.js","sourceRoot":"","sources":["../../src/lib/inventory.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.JobManager = void 0;
|
|
4
|
+
const node_child_process_1 = require("node:child_process");
|
|
5
|
+
const node_fs_1 = require("node:fs");
|
|
6
|
+
const promises_1 = require("node:fs/promises");
|
|
7
|
+
const node_path_1 = require("node:path");
|
|
8
|
+
const planner_1 = require("./planner");
|
|
9
|
+
const util_1 = require("./util");
|
|
10
|
+
function pidAlive(pid) {
|
|
11
|
+
if (!pid) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
process.kill(pid, 0);
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
catch (e) {
|
|
19
|
+
return e.code === 'EPERM';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
class JobManager {
|
|
23
|
+
deps;
|
|
24
|
+
job = null;
|
|
25
|
+
jobFile;
|
|
26
|
+
runnerFile;
|
|
27
|
+
haltFile;
|
|
28
|
+
busy = false;
|
|
29
|
+
constructor(deps) {
|
|
30
|
+
this.deps = deps;
|
|
31
|
+
this.jobFile = (0, node_path_1.join)(deps.dataDir, 'job.json');
|
|
32
|
+
this.runnerFile = (0, node_path_1.join)(deps.dataDir, 'runner.json');
|
|
33
|
+
this.haltFile = (0, node_path_1.join)(deps.dataDir, 'halt');
|
|
34
|
+
}
|
|
35
|
+
async load() {
|
|
36
|
+
this.job = await (0, util_1.readJson)(this.jobFile);
|
|
37
|
+
if (this.job?.phase === 'finished') {
|
|
38
|
+
this.job = null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
isActive() {
|
|
42
|
+
return !!this.job && this.job.phase !== 'finished';
|
|
43
|
+
}
|
|
44
|
+
async save() {
|
|
45
|
+
if (this.job) {
|
|
46
|
+
await (0, util_1.writeJsonAtomic)(this.jobFile, this.job);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async setState(state, detail) {
|
|
50
|
+
if (!this.job) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const changed = this.job.state !== state || this.job.detail !== detail;
|
|
54
|
+
this.job.state = state;
|
|
55
|
+
this.job.detail = detail;
|
|
56
|
+
await this.save();
|
|
57
|
+
if (changed) {
|
|
58
|
+
await this.deps.report(this.job);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async setHalt(halt) {
|
|
62
|
+
if (halt) {
|
|
63
|
+
await (0, util_1.writeJsonAtomic)(this.haltFile, { ts: Date.now() });
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
await (0, promises_1.rm)(this.haltFile, { force: true });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
async spawnRunner(mode, steps, files) {
|
|
70
|
+
const file = {
|
|
71
|
+
mode,
|
|
72
|
+
steps,
|
|
73
|
+
...(files && Object.keys(files).length ? { files } : {}),
|
|
74
|
+
iobroker: this.deps.iobrokerJs,
|
|
75
|
+
cwd: this.deps.cwd,
|
|
76
|
+
haltFile: this.haltFile,
|
|
77
|
+
logFile: (0, node_path_1.join)(this.deps.dataDir, 'runner.log'),
|
|
78
|
+
phase: 'start',
|
|
79
|
+
};
|
|
80
|
+
await (0, util_1.writeJsonAtomic)(this.runnerFile, file);
|
|
81
|
+
const child = (0, node_child_process_1.spawn)(process.execPath, [(0, node_path_1.join)(__dirname, '..', 'runner.js'), this.runnerFile], {
|
|
82
|
+
detached: true,
|
|
83
|
+
stdio: 'ignore',
|
|
84
|
+
cwd: this.deps.cwd,
|
|
85
|
+
windowsHide: true,
|
|
86
|
+
});
|
|
87
|
+
child.unref();
|
|
88
|
+
this.deps.log.info(`Runner started (${mode}, ${steps.length} steps, pid ${child.pid})`);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Starts a job
|
|
92
|
+
*
|
|
93
|
+
* @param desired desired state
|
|
94
|
+
* @param steps plan
|
|
95
|
+
* @param withBackup backup first
|
|
96
|
+
* @param files local packages per adapter instead of npm (the rollback takes npm)
|
|
97
|
+
*/
|
|
98
|
+
async start(desired, steps, withBackup, files = {}) {
|
|
99
|
+
const profile = desired.profile;
|
|
100
|
+
const all = withBackup ? [{ action: 'backup', status: 'pending' }, ...steps] : steps;
|
|
101
|
+
this.job = {
|
|
102
|
+
key: `${profile.hash}:${desired.retry}`,
|
|
103
|
+
profileId: profile.id,
|
|
104
|
+
profileName: profile.name,
|
|
105
|
+
profileHash: profile.hash,
|
|
106
|
+
retry: desired.retry,
|
|
107
|
+
targets: Object.fromEntries(steps.map(s => [s.adapter, s.to ?? null])),
|
|
108
|
+
state: 'preflight',
|
|
109
|
+
detail: '',
|
|
110
|
+
steps: all,
|
|
111
|
+
phase: 'apply',
|
|
112
|
+
createdAt: Date.now(),
|
|
113
|
+
};
|
|
114
|
+
await this.save();
|
|
115
|
+
await this.spawnRunner('apply', all, files);
|
|
116
|
+
await this.setState(withBackup ? 'backup' : 'rollout', withBackup ? 'Sicherung wird erstellt' : 'Ausrollen');
|
|
117
|
+
}
|
|
118
|
+
/** Called periodically while a job is active */
|
|
119
|
+
async poll() {
|
|
120
|
+
if (!this.job || this.busy) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
this.busy = true;
|
|
124
|
+
try {
|
|
125
|
+
if (this.job.phase === 'apply' || this.job.phase === 'rollback') {
|
|
126
|
+
await this.pollRunner();
|
|
127
|
+
}
|
|
128
|
+
else if (this.job.phase === 'verify') {
|
|
129
|
+
await this.verify();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch (e) {
|
|
133
|
+
this.deps.log.error(`Job: ${e.stack || e.message}`);
|
|
134
|
+
}
|
|
135
|
+
finally {
|
|
136
|
+
this.busy = false;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
async pollRunner() {
|
|
140
|
+
const job = this.job;
|
|
141
|
+
const runner = await (0, util_1.readJson)(this.runnerFile);
|
|
142
|
+
if (!runner) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const finished = runner.phase === 'done' || runner.phase === 'failed' || runner.phase === 'halted';
|
|
146
|
+
if (!finished && runner.phase === 'running' && !pidAlive(runner.pid)) {
|
|
147
|
+
// runner died (power loss, reboot)
|
|
148
|
+
runner.phase = 'failed';
|
|
149
|
+
runner.error = 'Ausführung wurde unterbrochen (Neustart oder Stromausfall)';
|
|
150
|
+
}
|
|
151
|
+
else if (!finished) {
|
|
152
|
+
if (job.phase === 'apply') {
|
|
153
|
+
job.steps = runner.steps;
|
|
154
|
+
const current = runner.steps.find(s => s.status === 'running');
|
|
155
|
+
if (current?.action === 'backup') {
|
|
156
|
+
await this.setState('backup', 'Sicherung wird erstellt');
|
|
157
|
+
}
|
|
158
|
+
else if (current) {
|
|
159
|
+
await this.setState('rollout', `${current.action} ${current.adapter} ${current.to ?? ''}`.trim());
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (job.phase === 'apply') {
|
|
165
|
+
job.steps = runner.steps;
|
|
166
|
+
if (runner.phase === 'done') {
|
|
167
|
+
job.phase = 'verify';
|
|
168
|
+
job.verifyStartedAt = Date.now();
|
|
169
|
+
await this.setState('verify', 'Instanzen und Log werden geprüft');
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
await this.startRollback(runner.phase === 'halted' ? 'Not-Aus während des Rollouts' : runner.error || 'Fehler', runner.phase === 'halted');
|
|
173
|
+
}
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
// rollback finished
|
|
177
|
+
const ok = runner.phase === 'done';
|
|
178
|
+
const detail = `${job.failure}; ${ok ? 'vorheriger Stand wiederhergestellt' : `Rollback unvollständig (${runner.error}), Sicherung vorhanden`}`;
|
|
179
|
+
for (const s of runner.steps) {
|
|
180
|
+
const original = job.steps.find(o => o.adapter === s.adapter && o.status === 'ok');
|
|
181
|
+
if (original) {
|
|
182
|
+
original.status = s.status === 'ok' ? 'skipped' : 'error';
|
|
183
|
+
original.detail = s.status === 'ok' ? 'zurückgerollt' : `Rollback: ${s.detail || 'fehlgeschlagen'}`;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
await this.finish(job.halted && ok ? 'waiting' : 'blocked', detail);
|
|
187
|
+
}
|
|
188
|
+
async startRollback(reason, halted = false) {
|
|
189
|
+
const job = this.job;
|
|
190
|
+
job.failure = reason;
|
|
191
|
+
job.halted = halted;
|
|
192
|
+
const steps = (0, planner_1.rollbackPlan)(job.steps);
|
|
193
|
+
if (!steps.length) {
|
|
194
|
+
await this.finish(halted ? 'waiting' : 'blocked', `${reason}; keine Änderung erfolgt`);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
job.phase = 'rollback';
|
|
198
|
+
await this.save();
|
|
199
|
+
await this.spawnRunner('rollback', steps);
|
|
200
|
+
await this.setState('rollback', reason);
|
|
201
|
+
}
|
|
202
|
+
async verify() {
|
|
203
|
+
const job = this.job;
|
|
204
|
+
const elapsed = (Date.now() - (job.verifyStartedAt ?? Date.now())) / 1000;
|
|
205
|
+
const installed = await this.deps.installed();
|
|
206
|
+
for (const [name, version] of Object.entries(job.targets)) {
|
|
207
|
+
if ((installed[name] ?? null) !== version) {
|
|
208
|
+
await this.startRollback(`${name}: installiert ${installed[name] ?? 'nichts'}, erwartet ${version ?? 'entfernt'}`);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const changed = Object.entries(job.targets)
|
|
213
|
+
.filter(([, v]) => v)
|
|
214
|
+
.map(([n]) => n);
|
|
215
|
+
const errors = this.deps.errorsSince(job.verifyStartedAt, changed);
|
|
216
|
+
if (errors.length) {
|
|
217
|
+
await this.startRollback(`Fehler im Log von ${errors[0].from}: ${errors[0].message.slice(0, 200)}`);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const down = [];
|
|
221
|
+
for (const name of changed) {
|
|
222
|
+
for (const inst of await this.deps.instancesOf(name)) {
|
|
223
|
+
if (!inst.alive) {
|
|
224
|
+
down.push(inst.id);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if (down.length) {
|
|
229
|
+
if (elapsed > this.deps.startTimeoutSeconds) {
|
|
230
|
+
await this.startRollback(`Instanz ${down.join(', ')} startet nach dem Update nicht`);
|
|
231
|
+
}
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
if (elapsed >= this.deps.verifySeconds) {
|
|
235
|
+
const n = job.steps.filter(s => s.action !== 'backup').length;
|
|
236
|
+
await this.finish('done', `${n} Schritte erfolgreich, Instanzen laufen, keine neuen Fehler im Log`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
async finish(result, detail) {
|
|
240
|
+
const job = this.job;
|
|
241
|
+
job.phase = 'finished';
|
|
242
|
+
await this.setState(result === 'waiting' ? 'waiting' : result, detail);
|
|
243
|
+
await this.deps.onFinished(job, result);
|
|
244
|
+
this.job = null;
|
|
245
|
+
if ((0, node_fs_1.existsSync)(this.runnerFile)) {
|
|
246
|
+
await (0, promises_1.rm)(this.runnerFile, { force: true });
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
exports.JobManager = JobManager;
|
|
251
|
+
//# sourceMappingURL=jobs.js.map
|