iobroker.bee 0.0.0-stage → 0.2.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/README.md CHANGED
@@ -1,3 +1,94 @@
1
- # Temporary Holding Version
1
+ ![Logo](admin/bee.svg)
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ # ioBroker.bee
4
+
5
+ Fleet agent for ioBroker. It connects a delivered ioBroker system (in a house, a radio station in the mountains, a vehicle, a machine in the field, …) to a **beekeeper** fleet server: it reports the inventory, receives the desired version profile and converges the system to it, locally and at the right time.
6
+
7
+ > Prototype. The server (beekeeper) is a separate, closed product. This adapter only reports, reads, executes and verifies; it holds no secrets besides its own device certificate.
8
+
9
+ ## How it works
10
+
11
+ 1. **Enrollment.** The factory image contains the enrollment URL and a one-time token bound to the asset (the managed installation). On first start the agent sends the token and the system UUID and receives its own device certificate. The token is used up.
12
+ 2. **Control channel.** MQTT over TLS with the device certificate, to the platform's own broker. The broker certificate may come from the platform CA (handed out at enrollment) or from a public CA such as Let's Encrypt; for the latter the agent uses the public root certificates shipped with Node.js. Heartbeat and Last Will report online status, the inventory is sent completely on connect and as a diff afterwards, all payloads gzip-compressed. Keepalive and heartbeat are configurable to keep mobile data low.
13
+ 3. **Desired state instead of commands.** The server publishes the desired profile as a retained message. A system that was offline for months reads the current profile, not a backlog of orders.
14
+ 4. **Reconciliation.** The agent compares the profile with the installed versions and plans install, upgrade, downgrade (and removal only when the server asks for it on return to managed mode). It never touches adapters that are not in the profile.
15
+ 5. **Preflight.** Emergency stop, dependencies of the target versions (`io-package.json` of the target version, `engines.node`), free space, maintenance window and configurable conditions on ioBroker states (installation not in use, battery charged, no roaming, vehicle standing, …). Conditions that change by themselves mean *waiting*, everything that needs a human means *blocked*.
16
+ 6. **Execution.** A detached runner process performs backup (`iobroker backup`) and the steps (`iobroker add/upgrade/del`) one by one. It survives restarts of the adapter, including the update of the agent itself, which always comes last.
17
+ 7. **Verification.** Versions must match, all enabled daemon instances of the changed adapters must be alive, and no new errors may appear in their log during the observation time. Otherwise the agent rolls back the changed adapters to their previous versions and reports *blocked*. A blocked job is not repeated until the fleet manager releases a new attempt.
18
+
19
+ ### Behaviour without server
20
+
21
+ - **Unknown means self-stop.** If the server explicitly rejects the device (unknown token, revoked certificate), the agent writes the reason to `bee.0.info.status`, raises a notification and terminates. It does not keep knocking.
22
+ - **Network errors are no reason to stop.** Without connection the distance between attempts grows up to a daily rhythm. Jobs that were started continue and report later.
23
+
24
+ ### Managed and self-managed
25
+
26
+ If the owner installs adapters that are not part of the profile, the agent reports them, stops automatic updates and informs the owner via an ioBroker notification. The fleet manager sees the installation in the deviation list and can bring it back to managed mode (removing the own adapters or tolerating them as exceptions).
27
+
28
+ ### Remote access to admin and vis-2
29
+
30
+ A technician of the fleet manager can open admin or vis-2 of the system from the fleet management, without ioBroker.pro and without open ports on the device:
31
+
32
+ 1. The server sends the job `tunnel` over MQTT with a one-time ticket, the kind (admin or vis-2), the technician, the purpose and the end of the session (at most 4 hours).
33
+ 2. The agent checks that the tunnel URL belongs to its own server (same host as enrollment or broker, `wss://` only) and opens an outgoing WebSocket to it.
34
+ 3. The server carries the connections of the technician's browser through this WebSocket. The agent connects them to an enabled admin instance (or web instance for vis-2, only with vis-2 installed) on this host and nowhere else. It reports these instances with port, HTTPS and login mode in the inventory; with several, the technician chooses one, and the agent accepts only one from its own list.
35
+
36
+ The owner can switch off each kind in the tab *Remote access*; the agent then refuses the session and the fleet management shows why. Running sessions are visible in `info.remoteAccess`. After a connection loss the agent reconnects until the session expires, also across restarts of the adapter. The server ends the session early when the technician ends it or the device is revoked.
37
+
38
+ ## Configuration
39
+
40
+ | Tab | Setting | Meaning |
41
+ | --- | --- | --- |
42
+ | Enrollment | Enrollment URL, token | from the factory image or the fleet manager; the token is stored encrypted |
43
+ | | comment | free text shown in the fleet management next to the installation, e.g. `Womo Mercedes KA-1525`; sent on enrollment and with the inventory. It helps to recognize the installation but proves nothing: the identity is the device certificate |
44
+ | Maintenance window | start, end | daily window for updates, may span midnight (e.g. 22:00–05:00) |
45
+ | | conditions | table of `state operator value`, e.g. `victron.0.soc >= 50`, `modem.0.roaming == false`, `presence.0.home == false` |
46
+ | | minimum free space | for download, unpacking and backup |
47
+ | Advanced | heartbeat, check interval, MQTT keepalive | trade-off between reaction time and mobile data |
48
+ | | observation after update, start timeout | verification of a rollout |
49
+ | | backup before every update | only disable for tests |
50
+ | Remote access | allow admin, allow vis-2 | whether the fleet manager may open the interface remotely (both allowed by default) |
51
+
52
+ ## States
53
+
54
+ | State | Meaning |
55
+ | --- | --- |
56
+ | `info.connection` | connected to the beekeeper broker |
57
+ | `info.status` | readable status, including the reason of a rejection |
58
+ | `info.deviceId`, `info.assetId` | identity assigned by the server |
59
+ | `info.mode`, `info.profile`, `info.onTarget` | managed/self, desired profile, system matches it |
60
+ | `job.state`, `job.detail`, `job.steps` | current or last job |
61
+ | `control.halted` | emergency stop of the fleet is active |
62
+ | `control.refresh`, `control.check` | buttons: send inventory, check desired state now |
63
+ | `info.remoteAccess`, `info.remoteAccessBy` | remote access running now, and by whom with which purpose |
64
+
65
+ Files in `iobroker-data/bee.0/`: `device.json` (certificate and key, mode 0600), `desired.json`, `job.json`, `runner.json`, `runner.log` (complete CLI output of all jobs), `tunnels.json` (running remote access sessions, resumed after a restart).
66
+
67
+ ## Not in this version
68
+
69
+ Platform updates of js-controller and Node.js (with a systemd watchdog outside ioBroker), vis-2 project distribution, remote console (PTY), signed artifacts. See the concept of the beekeeper project.
70
+
71
+ ## Development
72
+
73
+ ```bash
74
+ npm install
75
+ npm run build
76
+ npm test # unit tests of planner, preflight and diff
77
+ ```
78
+
79
+ `src/lib/protocol.ts` and `src/lib/tunnel.ts` are shared with the server and must stay identical to `beekeeper-server/src/protocol.ts` and `beekeeper-server/src/tunnel.ts`.
80
+
81
+ ## Changelog
82
+
83
+ ### 0.2.0 (2026-11-10)
84
+
85
+ - Remote access to admin and vis-2 through a tunnel to the fleet server, switchable per kind by the owner
86
+ - Versions set per asset by the fleet server, also adapter packages (tgz) loaded from the own server, checked by SHA-256 and installed with `iobroker url`
87
+
88
+ ### 0.1.0 (2026-09-18)
89
+
90
+ - Initial prototype: enrollment, inventory, desired state via MQTT, adapter rollout with preflight, backup, verification and rollback
91
+
92
+ ## License
93
+
94
+ Proprietary (UNLICENSED). Copyright (c) 2026 Denis Haev. All rights reserved.
package/admin/bee.svg ADDED
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><path d="M32 4 56 18v28L32 60 8 46V18z" fill="#e0a100"/><path d="M20 26h24M19 33h26M22 40h20" stroke="#1a1400" stroke-width="4" stroke-linecap="round"/><path d="M26 18c-3-6-9-7-12-4M38 18c3-6 9-7 12-4" stroke="#1a1400" stroke-width="2.5" fill="none" stroke-linecap="round"/></svg>
@@ -0,0 +1,307 @@
1
+ {
2
+ "i18n": false,
3
+ "type": "tabs",
4
+ "items": {
5
+ "connection": {
6
+ "type": "panel",
7
+ "label": { "en": "Enrollment", "de": "Anmeldung" },
8
+ "items": {
9
+ "hdr1": {
10
+ "type": "staticText",
11
+ "text": {
12
+ "en": "Enrollment URL and one-time token come with the factory image or from the fleet manager. After the first successful contact the device holds its own certificate; the token is used up.",
13
+ "de": "Enrollment-URL und Einmal-Token kommen mit dem Werksimage oder vom Flottenverwalter. Nach dem ersten erfolgreichen Kontakt hat das Gerät ein eigenes Zertifikat; das Token ist verbraucht."
14
+ },
15
+ "xs": 12,
16
+ "sm": 12,
17
+ "md": 12,
18
+ "lg": 12,
19
+ "xl": 12
20
+ },
21
+ "enrollUrl": {
22
+ "type": "text",
23
+ "label": { "en": "Enrollment URL", "de": "Enrollment-URL" },
24
+ "placeholder": "https://beekeeper.example.com",
25
+ "xs": 12,
26
+ "sm": 12,
27
+ "md": 6,
28
+ "lg": 6,
29
+ "xl": 6,
30
+ "newLine": true
31
+ },
32
+ "enrollToken": {
33
+ "type": "password",
34
+ "label": { "en": "Enrollment token", "de": "Enrollment-Token" },
35
+ "xs": 12,
36
+ "sm": 12,
37
+ "md": 6,
38
+ "lg": 6,
39
+ "xl": 6
40
+ },
41
+ "comment": {
42
+ "type": "text",
43
+ "label": { "en": "Comment", "de": "Kommentar" },
44
+ "help": {
45
+ "en": "Shown in the fleet management next to this installation, e.g. \"Womo Mercedes KA-1525\" (max. 200 characters)",
46
+ "de": "Erscheint in der Flottenverwaltung bei dieser Anlage, z. B. \"Womo Mercedes KA-1525\" (höchstens 200 Zeichen)"
47
+ },
48
+ "maxLength": 200,
49
+ "newLine": true,
50
+ "xs": 12,
51
+ "sm": 12,
52
+ "md": 12,
53
+ "lg": 12,
54
+ "xl": 12
55
+ },
56
+ "_status": {
57
+ "type": "state",
58
+ "oid": "info.status",
59
+ "label": { "en": "Status", "de": "Status" },
60
+ "newLine": true,
61
+ "xs": 12,
62
+ "sm": 12,
63
+ "md": 12,
64
+ "lg": 12,
65
+ "xl": 12
66
+ }
67
+ }
68
+ },
69
+ "remote": {
70
+ "type": "panel",
71
+ "label": { "en": "Remote access", "de": "Fernzugriff" },
72
+ "items": {
73
+ "hdr3": {
74
+ "type": "staticText",
75
+ "text": {
76
+ "en": "Service staff of the fleet management can open admin or vis-2 of this system from afar, without ioBroker.pro and without open ports: on request, this adapter connects to the fleet server for the duration of the access. Every access is logged with person and purpose, and is shown below while it lasts. Changing these settings ends running accesses.",
77
+ "de": "Servicepersonal der Flottenverwaltung kann admin oder vis-2 dieses Systems aus der Ferne öffnen, ohne ioBroker.pro und ohne offene Ports: auf Anforderung verbindet sich dieser Adapter für die Dauer des Zugriffs mit dem Flottenserver. Jeder Zugriff wird mit Person und Zweck protokolliert und unten angezeigt, solange er läuft. Eine Änderung dieser Einstellungen beendet laufende Zugriffe."
78
+ },
79
+ "xs": 12,
80
+ "sm": 12,
81
+ "md": 12,
82
+ "lg": 12,
83
+ "xl": 12
84
+ },
85
+ "allowAdmin": {
86
+ "type": "checkbox",
87
+ "label": { "en": "Allow remote access to admin", "de": "Fernzugriff auf admin erlauben" },
88
+ "help": {
89
+ "en": "admin means full access to the system",
90
+ "de": "admin bedeutet vollen Zugriff auf das System"
91
+ },
92
+ "newLine": true,
93
+ "xs": 12,
94
+ "sm": 12,
95
+ "md": 6,
96
+ "lg": 6,
97
+ "xl": 6
98
+ },
99
+ "allowVis": {
100
+ "type": "checkbox",
101
+ "label": { "en": "Allow remote access to vis-2 (web)", "de": "Fernzugriff auf vis-2 (web) erlauben" },
102
+ "help": {
103
+ "en": "vis-2 shows the values of this system, e.g. consumption",
104
+ "de": "vis-2 zeigt die Werte dieses Systems, z. B. Verbräuche"
105
+ },
106
+ "xs": 12,
107
+ "sm": 12,
108
+ "md": 6,
109
+ "lg": 6,
110
+ "xl": 6
111
+ },
112
+ "_remoteAccessBy": {
113
+ "type": "state",
114
+ "oid": "info.remoteAccessBy",
115
+ "label": { "en": "Access right now", "de": "Zugriff gerade" },
116
+ "newLine": true,
117
+ "xs": 12,
118
+ "sm": 12,
119
+ "md": 12,
120
+ "lg": 12,
121
+ "xl": 12
122
+ }
123
+ }
124
+ },
125
+ "maintenance": {
126
+ "type": "panel",
127
+ "label": { "en": "Maintenance window", "de": "Wartungsfenster" },
128
+ "items": {
129
+ "hdr2": {
130
+ "type": "staticText",
131
+ "text": {
132
+ "en": "Updates only run inside the maintenance window and when all conditions are met (e.g. installation not in use, battery charged, no roaming, vehicle standing).",
133
+ "de": "Updates laufen nur im Wartungsfenster und wenn alle Bedingungen erfüllt sind (z. B. Anlage nicht in Benutzung, Batterie geladen, kein Roaming, Fahrzeug steht)."
134
+ },
135
+ "xs": 12,
136
+ "sm": 12,
137
+ "md": 12,
138
+ "lg": 12,
139
+ "xl": 12
140
+ },
141
+ "windowStart": {
142
+ "type": "text",
143
+ "label": { "en": "Window start (HH:MM)", "de": "Fenster Beginn (HH:MM)" },
144
+ "xs": 6,
145
+ "sm": 6,
146
+ "md": 3,
147
+ "lg": 3,
148
+ "xl": 3,
149
+ "newLine": true
150
+ },
151
+ "windowEnd": {
152
+ "type": "text",
153
+ "label": { "en": "Window end (HH:MM)", "de": "Fenster Ende (HH:MM)" },
154
+ "xs": 6,
155
+ "sm": 6,
156
+ "md": 3,
157
+ "lg": 3,
158
+ "xl": 3
159
+ },
160
+ "ignoreWindow": {
161
+ "type": "checkbox",
162
+ "label": { "en": "Ignore window (testing only)", "de": "Fenster ignorieren (nur für Tests)" },
163
+ "xs": 12,
164
+ "sm": 12,
165
+ "md": 6,
166
+ "lg": 6,
167
+ "xl": 6
168
+ },
169
+ "minFreeMb": {
170
+ "type": "number",
171
+ "min": 100,
172
+ "label": { "en": "Minimum free space (MB)", "de": "Mindestens freier Speicher (MB)" },
173
+ "xs": 12,
174
+ "sm": 6,
175
+ "md": 3,
176
+ "lg": 3,
177
+ "xl": 3,
178
+ "newLine": true
179
+ },
180
+ "conditions": {
181
+ "type": "table",
182
+ "label": { "en": "Conditions for updates", "de": "Bedingungen für Updates" },
183
+ "newLine": true,
184
+ "xs": 12,
185
+ "sm": 12,
186
+ "md": 12,
187
+ "lg": 12,
188
+ "xl": 12,
189
+ "items": [
190
+ {
191
+ "type": "text",
192
+ "attr": "label",
193
+ "title": { "en": "Label", "de": "Bezeichnung" },
194
+ "width": "25%"
195
+ },
196
+ {
197
+ "type": "objectId",
198
+ "attr": "stateId",
199
+ "title": { "en": "State", "de": "Datenpunkt" },
200
+ "width": "40%"
201
+ },
202
+ {
203
+ "type": "select",
204
+ "attr": "op",
205
+ "title": { "en": "Operator", "de": "Vergleich" },
206
+ "options": [
207
+ { "label": "==", "value": "==" },
208
+ { "label": "!=", "value": "!=" },
209
+ { "label": ">=", "value": ">=" },
210
+ { "label": ">", "value": ">" },
211
+ { "label": "<=", "value": "<=" },
212
+ { "label": "<", "value": "<" }
213
+ ],
214
+ "default": "==",
215
+ "width": "15%"
216
+ },
217
+ {
218
+ "type": "text",
219
+ "attr": "value",
220
+ "title": { "en": "Value", "de": "Wert" },
221
+ "width": "20%"
222
+ }
223
+ ]
224
+ }
225
+ }
226
+ },
227
+ "advanced": {
228
+ "type": "panel",
229
+ "label": { "en": "Advanced", "de": "Erweitert" },
230
+ "items": {
231
+ "heartbeatMinutes": {
232
+ "type": "number",
233
+ "min": 5,
234
+ "label": { "en": "Heartbeat interval (min)", "de": "Heartbeat-Intervall (min)" },
235
+ "xs": 12,
236
+ "sm": 6,
237
+ "md": 3,
238
+ "lg": 3,
239
+ "xl": 3
240
+ },
241
+ "checkMinutes": {
242
+ "type": "number",
243
+ "min": 1,
244
+ "label": { "en": "Check interval (min)", "de": "Prüfintervall (min)" },
245
+ "xs": 12,
246
+ "sm": 6,
247
+ "md": 3,
248
+ "lg": 3,
249
+ "xl": 3
250
+ },
251
+ "keepaliveSeconds": {
252
+ "type": "number",
253
+ "min": 30,
254
+ "label": { "en": "MQTT keepalive (s)", "de": "MQTT-Keepalive (s)" },
255
+ "help": { "en": "Longer saves mobile data", "de": "Länger spart Mobilfunkvolumen" },
256
+ "xs": 12,
257
+ "sm": 6,
258
+ "md": 3,
259
+ "lg": 3,
260
+ "xl": 3
261
+ },
262
+ "signalStateId": {
263
+ "type": "objectId",
264
+ "label": { "en": "State with signal quality (optional)", "de": "Datenpunkt mit Signalqualität (optional)" },
265
+ "xs": 12,
266
+ "sm": 12,
267
+ "md": 6,
268
+ "lg": 6,
269
+ "xl": 6,
270
+ "newLine": true
271
+ },
272
+ "verifySeconds": {
273
+ "type": "number",
274
+ "min": 10,
275
+ "label": { "en": "Observation after update (s)", "de": "Beobachtung nach Update (s)" },
276
+ "help": { "en": "No new errors in the log during this time", "de": "In dieser Zeit keine neuen Fehler im Log" },
277
+ "xs": 12,
278
+ "sm": 6,
279
+ "md": 3,
280
+ "lg": 3,
281
+ "xl": 3,
282
+ "newLine": true
283
+ },
284
+ "startTimeoutSeconds": {
285
+ "type": "number",
286
+ "min": 30,
287
+ "label": { "en": "Start timeout of instances (s)", "de": "Startfrist der Instanzen (s)" },
288
+ "xs": 12,
289
+ "sm": 6,
290
+ "md": 3,
291
+ "lg": 3,
292
+ "xl": 3
293
+ },
294
+ "backupBeforeUpdate": {
295
+ "type": "checkbox",
296
+ "label": { "en": "Backup before every update", "de": "Sicherung vor jedem Update" },
297
+ "help": { "en": "Disable for tests only", "de": "Nur für Tests abschalten" },
298
+ "xs": 12,
299
+ "sm": 12,
300
+ "md": 6,
301
+ "lg": 6,
302
+ "xl": 6
303
+ }
304
+ }
305
+ }
306
+ }
307
+ }
@@ -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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"}