iobroker.admin 7.7.1 → 7.7.3

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.
Files changed (152) hide show
  1. package/README.md +44 -11
  2. package/admin/admin.png +0 -0
  3. package/admin/i18n/de.json +1 -1
  4. package/admin/i18n/es.json +1 -1
  5. package/admin/i18n/fr.json +1 -1
  6. package/admin/i18n/it.json +1 -1
  7. package/admin/i18n/nl.json +1 -1
  8. package/admin/i18n/pl.json +1 -1
  9. package/admin/i18n/pt.json +1 -1
  10. package/admin/i18n/ru.json +1 -1
  11. package/admin/i18n/uk.json +1 -1
  12. package/admin/i18n/zh-cn.json +1 -1
  13. package/admin/jsonConfig.json5 +1 -2
  14. package/adminWww/assets/{AdapterUpdateDialog-Bgswiqs9.js → AdapterUpdateDialog-BQUTMe7v.js} +1 -1
  15. package/adminWww/assets/Adapters-KuMqwfzU.js +9 -0
  16. package/adminWww/assets/Config-LH40Fzyp.js +1 -0
  17. package/adminWww/assets/CustomModal-UQ6ICKdg.js +1 -0
  18. package/adminWww/assets/CustomTab-CE2qvngn.js +1 -0
  19. package/adminWww/assets/DefaultPropsProvider-BxjOYH1h.js +30 -0
  20. package/adminWww/assets/EasyMode-XXQKZmWs.js +1 -0
  21. package/adminWww/assets/{Enums-8m252lzC.js → Enums-Ddmi-fYo.js} +3 -3
  22. package/adminWww/assets/{Fields-lI3YkIzQ.js → Fields-CQNdUYkd.js} +1 -1
  23. package/adminWww/assets/{Files-fQpSPYWI.js → Files-DgOiCRiN.js} +2 -2
  24. package/adminWww/assets/FilledInput-B9JSi0Ta.js +2 -0
  25. package/adminWww/assets/Hosts-BgDpLhdl.js +121 -0
  26. package/adminWww/assets/Instances-DikJwBl0.js +2 -0
  27. package/adminWww/assets/Intro-vxIHtYzr.js +2 -0
  28. package/adminWww/assets/Logs-DZUWTbJn.js +1 -0
  29. package/adminWww/assets/Objects-Dm1_4aGR.js +60 -0
  30. package/adminWww/assets/{State-nZ6sjU9_.js → State-CrcWiBdx.js} +1 -1
  31. package/adminWww/assets/TextField-j-uewRwI.js +138 -0
  32. package/adminWww/assets/ThemeProvider-DHntAcOp.js +29 -0
  33. package/adminWww/assets/Users-GcWjwa7A.js +1 -0
  34. package/adminWww/assets/ace-1DqSbvTN.js +985 -0
  35. package/adminWww/assets/bootstrap-B3ZI_-0g.js +2024 -0
  36. package/adminWww/assets/{createSvgIcon-DuKI6CvW.js → createSvgIcon-Ds12z7B3.js} +1 -1
  37. package/adminWww/assets/emotion-cache.browser.esm-BgJfNZJN.js +1 -0
  38. package/adminWww/assets/emotion-react.browser.esm-CBtpmfsG.js +8 -0
  39. package/adminWww/assets/emotion-serialize.esm-Q4o_CgeF.js +1 -0
  40. package/adminWww/assets/emotion-styled.browser.esm-BeD8nBzN.js +1 -0
  41. package/adminWww/assets/emotion-use-insertion-effect-with-fallbacks.browser.esm-BIy6Leuu.js +1 -0
  42. package/adminWww/assets/geosearch.module-B6h9BuIR.js +1 -0
  43. package/adminWww/assets/hostInit-fzxL8JAx.js +1 -1
  44. package/adminWww/assets/iconBase-BAOnvyiQ.js +1 -0
  45. package/adminWww/assets/{index-BP5kWU3L.js → index-BOnh7es7.js} +1 -1
  46. package/adminWww/assets/{index-BmWdDCX_.js → index-BSfwmoAE.js} +4 -4
  47. package/adminWww/assets/{index-gHyOgbGJ.js → index-BVgYaw7t.js} +1 -1
  48. package/adminWww/assets/{index-D7Rlw-5R.js → index-C9i3HTsG.js} +1 -1
  49. package/adminWww/assets/{index-DH1kmwK1.js → index-CELb-gCK.js} +2 -2
  50. package/adminWww/assets/{index-HOidq_rM.js → index-CU6eZCem.js} +1 -1
  51. package/adminWww/assets/{index-Bsv_DXxB.js → index-DZ0MncEz.js} +2 -2
  52. package/adminWww/assets/index-Dg-s3k0-.js +10 -0
  53. package/adminWww/assets/index-tS5iCAF7.js +55 -0
  54. package/adminWww/assets/iobroker_admin__loadShare___mf_0_emotion_mf_1_react__loadShare__-CXogaIXO.js +1 -0
  55. package/adminWww/assets/{iobroker_admin__loadShare__leaflet__loadShare__-B1OZ7tj-.js → iobroker_admin__loadShare__leaflet__loadShare__-DIpcqFbm.js} +1 -1
  56. package/adminWww/assets/iobroker_admin__loadShare__prop_mf_2_types__loadShare__-pDONiJUe.js +1 -0
  57. package/adminWww/assets/{iobroker_admin__loadShare__react__loadShare__-DtlEM_52.js → iobroker_admin__loadShare__react__loadShare__-CuzHmAOj.js} +1 -1
  58. package/adminWww/assets/iobroker_admin__mf_v__runtimeInit__mf_v__-CHE4rLsT.js +5 -0
  59. package/adminWww/assets/leaflet-src-BRBM-1HK.js +4 -0
  60. package/adminWww/assets/{sentry-CNy_SQq1.js → sentry-B6yHtsbd.js} +1 -1
  61. package/adminWww/assets/{zh-cn-DZTx1vAh.js → zh-cn-heTcsLnU.js} +15 -15
  62. package/adminWww/img/admin.png +0 -0
  63. package/adminWww/index.html +2 -2
  64. package/adminWww/mf-manifest.json +1 -1
  65. package/adminWww/remoteEntry.js +2 -2
  66. package/adminWww/static/js/worker-javascript.js +1 -1
  67. package/adminWww/static/js/worker-yaml.js +1 -1
  68. package/build/i18n/de.json +18 -0
  69. package/build/i18n/es.json +18 -0
  70. package/build/i18n/fr.json +18 -0
  71. package/build/i18n/it.json +18 -0
  72. package/build/i18n/nl.json +18 -0
  73. package/build/i18n/pl.json +18 -0
  74. package/build/i18n/pt.json +18 -0
  75. package/build/i18n/ru.json +18 -0
  76. package/build/i18n/uk.json +18 -0
  77. package/build/i18n/zh-cn.json +18 -0
  78. package/build/lib/DockerManager.js +1315 -0
  79. package/build/lib/DockerManager.js.map +1 -0
  80. package/build/lib/dockerManager.types.js +3 -0
  81. package/build/lib/dockerManager.types.js.map +1 -0
  82. package/{build-backend → build}/lib/testPassword.js +1 -1
  83. package/{build-backend → build}/lib/testPassword.js.map +1 -1
  84. package/{build-backend/src → build}/lib/translations.js +16 -1
  85. package/build/lib/translations.js.map +1 -0
  86. package/{build-backend → build}/main.js +58 -5
  87. package/build/main.js.map +1 -0
  88. package/io-package.json +27 -27
  89. package/package.json +13 -12
  90. package/adminWww/assets/Adapters-yQiEeoh8.js +0 -7
  91. package/adminWww/assets/Config-C1Gjt8DC.js +0 -1
  92. package/adminWww/assets/CustomModal-CgEtkfCG.js +0 -1
  93. package/adminWww/assets/CustomTab-CwioFUor.js +0 -1
  94. package/adminWww/assets/DefaultPropsProvider-DxsXuIyE.js +0 -30
  95. package/adminWww/assets/EasyMode-CN-reJVI.js +0 -1
  96. package/adminWww/assets/FilledInput-CKswrNd5.js +0 -2
  97. package/adminWww/assets/Hosts-BPdUlXV5.js +0 -121
  98. package/adminWww/assets/Instances-BStmfz4r.js +0 -2
  99. package/adminWww/assets/Intro-Dr1-aqO9.js +0 -2
  100. package/adminWww/assets/Logs-xCCrWV30.js +0 -1
  101. package/adminWww/assets/Objects-BF8brXaf.js +0 -60
  102. package/adminWww/assets/Tabs-BJ1rL35Z.js +0 -138
  103. package/adminWww/assets/Users-B0iasrSG.js +0 -1
  104. package/adminWww/assets/ace-CFIUQz8j.js +0 -970
  105. package/adminWww/assets/blueGrey-D-KQaGde.js +0 -29
  106. package/adminWww/assets/bootstrap-DDrI3lVl.js +0 -2036
  107. package/adminWww/assets/emotion-cache.browser.esm-B8BFze5o.js +0 -1
  108. package/adminWww/assets/emotion-react.browser.esm-DApPKwaj.js +0 -8
  109. package/adminWww/assets/emotion-serialize.esm-BUa21YfQ.js +0 -1
  110. package/adminWww/assets/emotion-styled.browser.esm-Dc05hmeu.js +0 -1
  111. package/adminWww/assets/emotion-utils.browser.esm-DOjH6Olm.js +0 -1
  112. package/adminWww/assets/geosearch.module-CWfRtxrN.js +0 -1
  113. package/adminWww/assets/iconBase-CgxZ5MMC.js +0 -1
  114. package/adminWww/assets/index-BjVqnwqt.js +0 -10
  115. package/adminWww/assets/index-RvHSqeMD.js +0 -55
  116. package/adminWww/assets/iobroker_admin__loadShare___mf_0_emotion_mf_1_react__loadShare__-gw2iqPBS.js +0 -1
  117. package/adminWww/assets/iobroker_admin__loadShare__prop_mf_2_types__loadShare__-BGyJCibC.js +0 -1
  118. package/adminWww/assets/iobroker_admin__mf_v__runtimeInit__mf_v__-VDoBF19b.js +0 -10
  119. package/adminWww/assets/leaflet-src-A2ZHl6nF.js +0 -4
  120. package/build-backend/i18n/de.json +0 -18
  121. package/build-backend/i18n/es.json +0 -18
  122. package/build-backend/i18n/fr.json +0 -18
  123. package/build-backend/i18n/it.json +0 -18
  124. package/build-backend/i18n/nl.json +0 -18
  125. package/build-backend/i18n/pl.json +0 -18
  126. package/build-backend/i18n/pt.json +0 -18
  127. package/build-backend/i18n/ru.json +0 -18
  128. package/build-backend/i18n/uk.json +0 -18
  129. package/build-backend/i18n/zh-cn.json +0 -18
  130. package/build-backend/lib/translations.js +0 -27
  131. package/build-backend/lib/translations.js.map +0 -1
  132. package/build-backend/main.js.map +0 -1
  133. package/build-backend/src/lib/checkLinuxPass.js +0 -280
  134. package/build-backend/src/lib/checkLinuxPass.js.map +0 -1
  135. package/build-backend/src/lib/testPassword.js +0 -58
  136. package/build-backend/src/lib/testPassword.js.map +0 -1
  137. package/build-backend/src/lib/translations.js.map +0 -1
  138. package/build-backend/src/lib/utils.js +0 -344
  139. package/build-backend/src/lib/utils.js.map +0 -1
  140. package/build-backend/src/lib/web.js +0 -1165
  141. package/build-backend/src/lib/web.js.map +0 -1
  142. package/build-backend/src/main.js +0 -1704
  143. package/build-backend/src/main.js.map +0 -1
  144. package/build-backend/src-admin/src/components/Adapters/Utils.js +0 -39
  145. package/build-backend/src-admin/src/components/Adapters/Utils.js.map +0 -1
  146. /package/{build-backend → build}/i18n/en.json +0 -0
  147. /package/{build-backend → build}/lib/checkLinuxPass.js +0 -0
  148. /package/{build-backend → build}/lib/checkLinuxPass.js.map +0 -0
  149. /package/{build-backend → build}/lib/utils.js +0 -0
  150. /package/{build-backend → build}/lib/utils.js.map +0 -0
  151. /package/{build-backend → build}/lib/web.js +0 -0
  152. /package/{build-backend → build}/lib/web.js.map +0 -0
@@ -0,0 +1,1315 @@
1
+ "use strict";
2
+ // This class implements docker commands using CLI and
3
+ // it monitors periodically the docker daemon status.
4
+ // It manages containers defined in adapter.config.containers and monitors other containers
5
+ var _a;
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const node_util_1 = require("node:util");
8
+ const node_child_process_1 = require("node:child_process");
9
+ const execPromise = (0, node_util_1.promisify)(node_child_process_1.exec);
10
+ const dockerDefaults = {
11
+ tty: false,
12
+ stdinOpen: false,
13
+ attachStdin: false,
14
+ attachStdout: false,
15
+ attachStderr: false,
16
+ openStdin: false,
17
+ publishAllPorts: false,
18
+ readOnly: false,
19
+ user: '',
20
+ workdir: '',
21
+ domainname: '',
22
+ macAddress: '',
23
+ networkMode: 'bridge',
24
+ };
25
+ function isDefault(value, def) {
26
+ return JSON.stringify(value) === JSON.stringify(def);
27
+ }
28
+ function deepCompare(object1, object2) {
29
+ if (typeof object1 === 'number') {
30
+ object1 = object1.toString();
31
+ }
32
+ if (typeof object2 === 'number') {
33
+ object2 = object2.toString();
34
+ }
35
+ if (typeof object1 !== typeof object2) {
36
+ return false;
37
+ }
38
+ if (typeof object1 !== 'object' || object1 === null || object2 === null) {
39
+ return object1 === object2;
40
+ }
41
+ if (Array.isArray(object1)) {
42
+ if (!Array.isArray(object2) || object1.length !== object2.length) {
43
+ return false;
44
+ }
45
+ for (let i = 0; i < object1.length; i++) {
46
+ if (!deepCompare(object1[i], object2[i])) {
47
+ return false;
48
+ }
49
+ }
50
+ return true;
51
+ }
52
+ const keys1 = Object.keys(object1);
53
+ const keys2 = Object.keys(object2);
54
+ if (keys1.length !== keys2.length) {
55
+ return false;
56
+ }
57
+ for (const key of keys1) {
58
+ if (!deepCompare(object1[key], object2[key])) {
59
+ return false;
60
+ }
61
+ }
62
+ return true;
63
+ }
64
+ function compareConfigs(desired, existing) {
65
+ const diffs = [];
66
+ const keys = Object.keys(desired);
67
+ // We only compare keys that are in the desired config
68
+ for (const key of keys) {
69
+ // ignore iob* properties as they belong to ioBroker configuration
70
+ if (key.startsWith('iob')) {
71
+ continue;
72
+ }
73
+ // ignore hostname
74
+ if (key === 'hostname') {
75
+ continue;
76
+ }
77
+ if (typeof desired[key] === 'object' && desired[key] !== null) {
78
+ if (Array.isArray(desired[key])) {
79
+ if (!Array.isArray(existing[key]) || desired[key].length !== existing[key].length) {
80
+ diffs.push(key);
81
+ }
82
+ else {
83
+ for (let i = 0; i < desired[key].length; i++) {
84
+ if (!deepCompare(desired[key][i], existing[key][i])) {
85
+ diffs.push(`${key}[${i}]`);
86
+ }
87
+ }
88
+ }
89
+ }
90
+ else {
91
+ Object.keys(desired[key]).forEach((subKey) => {
92
+ if (!deepCompare(desired[key][subKey], existing[key][subKey])) {
93
+ diffs.push(`${key}.${subKey}`);
94
+ }
95
+ });
96
+ }
97
+ }
98
+ else if (desired[key] !== existing[key]) {
99
+ diffs.push(key);
100
+ }
101
+ }
102
+ return diffs;
103
+ }
104
+ // remove undefined entries recursively
105
+ function removeUndefined(obj) {
106
+ if (Array.isArray(obj)) {
107
+ const arr = obj.map(v => (v && typeof v === 'object' ? removeUndefined(v) : v)).filter(v => v !== undefined);
108
+ if (!arr.length) {
109
+ return undefined;
110
+ }
111
+ return arr;
112
+ }
113
+ if (obj && typeof obj === 'object') {
114
+ const _obj = Object.fromEntries(Object.entries(obj)
115
+ .map(([k, v]) => [k, v && typeof v === 'object' ? removeUndefined(v) : v])
116
+ .filter(([_, v]) => v !== undefined &&
117
+ v !== null &&
118
+ v !== '' &&
119
+ !(Array.isArray(v) && v.length === 0) &&
120
+ !(typeof v === 'object' && Object.keys(v).length === 0)));
121
+ if (Object.keys(_obj).length === 0) {
122
+ return undefined;
123
+ }
124
+ return _obj;
125
+ }
126
+ if (obj === '') {
127
+ return undefined;
128
+ }
129
+ return obj;
130
+ }
131
+ function cleanContainerConfig(obj) {
132
+ obj = removeUndefined(obj);
133
+ Object.keys(obj).forEach(name => {
134
+ if (isDefault(obj[name], dockerDefaults[name])) {
135
+ delete obj[name];
136
+ }
137
+ if (name === 'mounts') {
138
+ if (!obj.mounts) {
139
+ delete obj.mounts;
140
+ return;
141
+ }
142
+ obj.mounts = obj.mounts.map((mount) => {
143
+ const m = { ...mount };
144
+ delete m.readOnly;
145
+ return m;
146
+ });
147
+ if (!obj.mounts.length) {
148
+ delete obj.mounts;
149
+ return;
150
+ }
151
+ obj.mounts?.sort((a, b) => a.target.localeCompare(b.target));
152
+ }
153
+ if (name === 'ports') {
154
+ if (!obj.ports) {
155
+ delete obj.ports;
156
+ return;
157
+ }
158
+ obj.ports = obj.ports.map((port) => {
159
+ const p = { ...port };
160
+ if (p.protocol === 'tcp') {
161
+ delete p.protocol;
162
+ }
163
+ return p;
164
+ });
165
+ if (!obj.ports.length) {
166
+ delete obj.ports;
167
+ return;
168
+ }
169
+ obj.ports?.sort((a, b) => {
170
+ if (a.hostPort !== b.hostPort) {
171
+ return parseInt(a.containerPort, 10) - parseInt(b.containerPort, 10);
172
+ }
173
+ if (a.hostIP !== b.hostIP && a.hostIP && b.hostIP) {
174
+ return a.hostIP?.localeCompare(b.hostIP);
175
+ }
176
+ return 0;
177
+ });
178
+ }
179
+ if (name === 'environment') {
180
+ if (!obj.environment) {
181
+ delete obj.environment;
182
+ return;
183
+ }
184
+ const env = obj.environment;
185
+ if (Object.keys(env).length) {
186
+ obj.environment = {};
187
+ Object.keys(env)
188
+ .sort()
189
+ .forEach(key => {
190
+ if (key && env[key]) {
191
+ obj.environment[key] = env[key];
192
+ }
193
+ });
194
+ }
195
+ else {
196
+ delete obj.environment;
197
+ }
198
+ }
199
+ });
200
+ obj.volumes?.sort();
201
+ return obj;
202
+ }
203
+ class DockerManager {
204
+ installed = false;
205
+ dockerVersion = '';
206
+ needSudo = false;
207
+ #waitReady;
208
+ adapter;
209
+ #ownContainers = [];
210
+ #monitoringInterval = null;
211
+ #ownContainersStats = {};
212
+ constructor(adapter, containers) {
213
+ this.adapter = adapter;
214
+ this.#ownContainers = containers || [];
215
+ this.#waitReady = new Promise(resolve => this.#init().then(() => resolve()));
216
+ }
217
+ /** Wait till the check if docker is installed and the daemon is running is ready */
218
+ isReady() {
219
+ return this.#waitReady;
220
+ }
221
+ /**
222
+ * Convert information from inspect to docker configuration to start it
223
+ *
224
+ * @param inspect Inspect information
225
+ */
226
+ static mapInspectToConfig(inspect) {
227
+ const obj = {
228
+ image: inspect.Config.Image,
229
+ name: inspect.Name.replace(/^\//, ''),
230
+ command: inspect.Config.Cmd ?? undefined,
231
+ entrypoint: inspect.Config.Entrypoint ?? undefined,
232
+ user: inspect.Config.User ?? undefined,
233
+ workdir: inspect.Config.WorkingDir ?? undefined,
234
+ hostname: inspect.Config.Hostname ?? undefined,
235
+ domainname: inspect.Config.Domainname ?? undefined,
236
+ macAddress: inspect.NetworkSettings.MacAddress ?? undefined,
237
+ environment: inspect.Config.Env
238
+ ? Object.fromEntries(inspect.Config.Env.map(e => {
239
+ const [key, ...rest] = e.split('=');
240
+ return [key, rest.join('=')];
241
+ }))
242
+ : undefined,
243
+ labels: inspect.Config.Labels ?? undefined,
244
+ tty: inspect.Config.Tty,
245
+ stdinOpen: inspect.Config.OpenStdin,
246
+ attachStdin: inspect.Config.AttachStdin,
247
+ attachStdout: inspect.Config.AttachStdout,
248
+ attachStderr: inspect.Config.AttachStderr,
249
+ openStdin: inspect.Config.OpenStdin,
250
+ publishAllPorts: inspect.HostConfig.PublishAllPorts,
251
+ ports: inspect.HostConfig.PortBindings
252
+ ? Object.entries(inspect.HostConfig.PortBindings).flatMap(([containerPort, bindings]) => bindings.map(binding => ({
253
+ containerPort: containerPort.split('/')[0],
254
+ protocol: containerPort.split('/')[1] || 'tcp',
255
+ hostPort: binding.HostPort,
256
+ hostIP: binding.HostIp,
257
+ })))
258
+ : undefined,
259
+ mounts: inspect.Mounts?.map(mount => ({
260
+ type: mount.Type,
261
+ source: mount.Source,
262
+ target: mount.Destination,
263
+ readOnly: mount.RW,
264
+ })),
265
+ volumes: inspect.Config.Volumes ? Object.keys(inspect.Config.Volumes) : undefined,
266
+ extraHosts: inspect.HostConfig.ExtraHosts ?? undefined,
267
+ dns: {
268
+ servers: inspect.HostConfig.Dns,
269
+ search: inspect.HostConfig.DnsSearch,
270
+ options: inspect.HostConfig.DnsOptions,
271
+ },
272
+ networkMode: inspect.HostConfig.NetworkMode,
273
+ networks: inspect.NetworkSettings.Networks
274
+ ? Object.entries(inspect.NetworkSettings.Networks).map(([name, net]) => ({
275
+ name,
276
+ aliases: net.Aliases ?? undefined,
277
+ ipv4Address: net.IPAddress,
278
+ ipv6Address: net.GlobalIPv6Address,
279
+ driverOpts: net.DriverOpts ?? undefined,
280
+ }))
281
+ : undefined,
282
+ restart: {
283
+ policy: inspect.HostConfig.RestartPolicy.Name,
284
+ maxRetries: inspect.HostConfig.RestartPolicy.MaximumRetryCount,
285
+ },
286
+ resources: {
287
+ cpuShares: inspect.HostConfig.CpuShares,
288
+ cpuQuota: inspect.HostConfig.CpuQuota,
289
+ cpuPeriod: inspect.HostConfig.CpuPeriod,
290
+ cpusetCpus: inspect.HostConfig.CpusetCpus,
291
+ memory: inspect.HostConfig.Memory,
292
+ memorySwap: inspect.HostConfig.MemorySwap,
293
+ memoryReservation: inspect.HostConfig.MemoryReservation,
294
+ pidsLimit: inspect.HostConfig.PidsLimit ?? undefined,
295
+ shmSize: inspect.HostConfig.ShmSize,
296
+ readOnlyRootFilesystem: inspect.HostConfig.ReadonlyRootfs,
297
+ },
298
+ logging: {
299
+ driver: inspect.HostConfig.LogConfig.Type,
300
+ options: inspect.HostConfig.LogConfig.Config,
301
+ },
302
+ security: {
303
+ privileged: inspect.HostConfig.Privileged,
304
+ capAdd: inspect.HostConfig.CapAdd ?? undefined,
305
+ capDrop: inspect.HostConfig.CapDrop ?? undefined,
306
+ usernsMode: inspect.HostConfig.UsernsMode ?? undefined,
307
+ ipc: inspect.HostConfig.IpcMode,
308
+ pid: inspect.HostConfig.PidMode,
309
+ seccomp: inspect.HostConfig.SecurityOpt?.find(opt => opt.startsWith('seccomp='))?.split('=')[1] ?? undefined,
310
+ apparmor: inspect.AppArmorProfile,
311
+ groupAdd: inspect.HostConfig.GroupAdd ?? undefined,
312
+ noNewPrivileges: undefined, // Nicht direkt verfügbar
313
+ },
314
+ sysctls: inspect.HostConfig.Sysctls ?? undefined,
315
+ init: inspect.HostConfig.Init ?? undefined,
316
+ stop: {
317
+ signal: inspect.Config.StopSignal ?? undefined,
318
+ gracePeriodSec: inspect.Config.StopTimeout ?? undefined,
319
+ },
320
+ readOnly: inspect.HostConfig.ReadonlyRootfs,
321
+ timezone: undefined, // Nicht direkt verfügbar
322
+ __meta: undefined, // Eigene Metadaten
323
+ };
324
+ return cleanContainerConfig(obj);
325
+ }
326
+ /**
327
+ * Get information about the Docker daemon: is it running and which version
328
+ *
329
+ * @returns Object with version and daemonRunning
330
+ */
331
+ async getDockerDaemonInfo() {
332
+ await this.isReady();
333
+ const daemonRunning = await this.#isDockerDaemonRunning();
334
+ return {
335
+ version: this.dockerVersion,
336
+ daemonRunning,
337
+ };
338
+ }
339
+ async #init() {
340
+ const version = await this.#isDockerInstalled();
341
+ this.installed = !!version;
342
+ if (version) {
343
+ this.dockerVersion = version;
344
+ }
345
+ else {
346
+ const daemonRunning = await this.#isDockerDaemonRunning();
347
+ if (daemonRunning) {
348
+ // Docker daemon is running, but docker command not found
349
+ this.adapter.log.warn('Docker daemon is running, but docker command not found. May be "iobroker" user has no access to Docker. Run "iob fix" command to fix it.');
350
+ }
351
+ else {
352
+ this.adapter.log.warn('Docker is not installed. Please install Docker.');
353
+ }
354
+ }
355
+ if (this.installed) {
356
+ this.needSudo = await this.#isNeedSudo();
357
+ await this.#checkOwnContainers();
358
+ }
359
+ }
360
+ async #isDockerDaemonRunning() {
361
+ try {
362
+ const { stdout, stderr } = await execPromise('systemctl status docker');
363
+ // ● docker.service - Docker Application Container Engine
364
+ // Loaded: loaded (/lib/systemd/system/docker.service; enabled; preset: enabled)
365
+ // Active: active (running) since Fri 2025-08-15 08:37:22 CEST; 3 weeks 2 days ago
366
+ // TriggeredBy: ● docker.socket
367
+ // Docs: https://docs.docker.com
368
+ // Main PID: 785 (dockerd)
369
+ // Tasks: 30
370
+ // CPU: 4min 17.003s
371
+ // CGroup: /system.slice/docker.service
372
+ // ├─ 785 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
373
+ // ├─97032 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 5000 -container-ip 172.17.0.2 -container-port 5000 -use-listen-fd
374
+ // └─97039 /usr/bin/docker-proxy -proto tcp -host-ip :: -host-port 5000 -container-ip 172.17.0.2 -container-port 5000 -use-listen-fd
375
+ if (stderr?.includes('could not be found') || stderr.includes('not-found')) {
376
+ this.adapter.log.error(`Docker is not installed: ${stderr}`);
377
+ return false;
378
+ }
379
+ return stdout.includes('(running)');
380
+ }
381
+ catch {
382
+ return false;
383
+ }
384
+ }
385
+ /**
386
+ * Ensure that the given container is running with the actual configuration
387
+ *
388
+ * @param container Container configuration
389
+ */
390
+ async #ensureActualConfiguration(container) {
391
+ // Check the configuration of the container
392
+ const inspect = await this.containerInspect(container.name);
393
+ if (inspect) {
394
+ const existingConfig = _a.mapInspectToConfig(inspect);
395
+ console.log('Compare existing config', existingConfig, ' and', container);
396
+ container = cleanContainerConfig(container);
397
+ const diffs = compareConfigs(container, existingConfig);
398
+ if (diffs.length) {
399
+ this.adapter.log.info(`Configuration of own container ${container.name} has changed: ${diffs.join(', ')}. Restarting container...`);
400
+ const result = await this.containerReCreate(container);
401
+ if (result.stderr) {
402
+ this.adapter.log.warn(`Cannot recreate own container ${container.name}: ${result.stderr}`);
403
+ }
404
+ }
405
+ // Check if container is running
406
+ this.adapter.log.debug(`Configuration of own container ${container.name} is up to date`);
407
+ const status = await this.containerList(true);
408
+ const containerInfo = status.find(it => it.names === container.name);
409
+ if (containerInfo) {
410
+ if (containerInfo.status !== 'running' && containerInfo.status !== 'restarting') {
411
+ // Start the container
412
+ this.adapter.log.info(`Starting own container ${container.name}`);
413
+ try {
414
+ const result = await this.containerStart(containerInfo.id);
415
+ if (result.stderr) {
416
+ this.adapter.log.warn(`Cannot start own container ${container.name}: ${result.stderr}`);
417
+ }
418
+ }
419
+ catch (e) {
420
+ this.adapter.log.warn(`Cannot start own container ${container.name}: ${e.message}`);
421
+ }
422
+ }
423
+ else {
424
+ this.adapter.log.debug(`Own container ${container.name} is already running`);
425
+ }
426
+ }
427
+ else {
428
+ this.adapter.log.warn(`Own container ${container.name} not found in container list after recreation`);
429
+ }
430
+ }
431
+ }
432
+ async #checkOwnContainers() {
433
+ if (!this.#ownContainers.length) {
434
+ return;
435
+ }
436
+ const status = await this.containerList(true);
437
+ let images = await this.imageList();
438
+ let anyStartedOrRunning = false;
439
+ const networkChecked = [];
440
+ for (let c = 0; c < this.#ownContainers.length; c++) {
441
+ const container = this.#ownContainers[c];
442
+ if (container.iobEnabled !== false) {
443
+ if (!container.image.includes(':')) {
444
+ container.image += ':latest';
445
+ }
446
+ try {
447
+ // create iobroker network if necessary
448
+ if (container.networkMode &&
449
+ container.networkMode !== 'container' &&
450
+ container.networkMode !== 'host' &&
451
+ container.networkMode !== 'bridge' &&
452
+ container.networkMode !== 'none') {
453
+ if (!networkChecked.includes(container.networkMode)) {
454
+ // check if the network exists
455
+ const networks = await this.networkList();
456
+ if (!networks.find(it => it.name === container.networkMode)) {
457
+ this.adapter.log.info(`Creating docker network ${container.networkMode}`);
458
+ await this.networkCreate(container.networkMode);
459
+ }
460
+ networkChecked.push(container.networkMode);
461
+ }
462
+ }
463
+ let containerInfo = status.find(it => it.names === container.name);
464
+ let image = images.find(it => `${it.repository}:${it.tag}` === container.image);
465
+ if (container.iobAutoImageUpdate) {
466
+ // ensure that the image is actual
467
+ const newImage = await this.imageUpdate(container.image, true);
468
+ if (newImage) {
469
+ this.adapter.log.info(`Image ${container.image} for own container ${container.name} was updated`);
470
+ if (containerInfo) {
471
+ // destroy current container
472
+ await this.containerRemove(containerInfo.id);
473
+ containerInfo = undefined;
474
+ }
475
+ image = newImage;
476
+ }
477
+ }
478
+ if (!image) {
479
+ this.adapter.log.info(`Pulling image ${container.image} for own container ${container.name}`);
480
+ try {
481
+ const result = await this.imagePull(container.image);
482
+ if (result.stderr) {
483
+ this.adapter.log.warn(`Cannot pull image ${container.image}: ${result.stderr}`);
484
+ continue;
485
+ }
486
+ }
487
+ catch (e) {
488
+ this.adapter.log.warn(`Cannot pull image ${container.image}: ${e.message}`);
489
+ continue;
490
+ }
491
+ // Check that image is available now
492
+ images = await this.imageList();
493
+ image = images.find(it => `${it.repository}:${it.tag}` === container.image);
494
+ if (!image) {
495
+ this.adapter.log.warn(`Image ${container.image} for own container ${container.name} not found after pull`);
496
+ continue;
497
+ }
498
+ }
499
+ if (containerInfo) {
500
+ await this.#ensureActualConfiguration(container);
501
+ anyStartedOrRunning ||= !!container.iobMonitoringEnabled;
502
+ }
503
+ else {
504
+ // Create and start the container, as the container was not found
505
+ this.adapter.log.info(`Creating and starting own container ${container.name}`);
506
+ try {
507
+ const result = await this.containerRun(container);
508
+ if (result.stderr) {
509
+ this.adapter.log.warn(`Cannot start own container ${container.name}: ${result.stderr}`);
510
+ }
511
+ else {
512
+ anyStartedOrRunning ||= !!container.iobMonitoringEnabled;
513
+ }
514
+ }
515
+ catch (e) {
516
+ this.adapter.log.warn(`Cannot start own container ${container.name}: ${e.message}`);
517
+ }
518
+ }
519
+ }
520
+ catch (e) {
521
+ this.adapter.log.warn(`Cannot check own container ${container.name}: ${e.message}`);
522
+ }
523
+ }
524
+ }
525
+ if (anyStartedOrRunning) {
526
+ this.#monitoringInterval ||= setInterval(() => this.#monitorOwnContainers(), 60000);
527
+ }
528
+ }
529
+ async #monitorOwnContainers() {
530
+ // get the status of containers
531
+ const containers = await this.containerList();
532
+ // Check the status of own containers
533
+ for (let c = 0; c < this.#ownContainers.length; c++) {
534
+ const container = this.#ownContainers[c];
535
+ if (container.iobEnabled !== false && container.iobMonitoringEnabled) {
536
+ // Check if container is running
537
+ const running = containers.find(it => it.names === container.name);
538
+ if (!running || (running.status !== 'running' && running.status !== 'restarting')) {
539
+ this.adapter.log.warn(`Own container ${container.name} is not running. Restarting...`);
540
+ try {
541
+ const result = await this.containerStart(container.name);
542
+ if (result.stderr) {
543
+ this.adapter.log.warn(`Cannot start own container ${container.name}: ${result.stderr}`);
544
+ this.#ownContainersStats[container.name] = {
545
+ ...this.#ownContainersStats[container.name],
546
+ status: running?.status || 'unknown',
547
+ statusTs: Date.now(),
548
+ };
549
+ continue;
550
+ }
551
+ }
552
+ catch (e) {
553
+ this.adapter.log.warn(`Cannot start own container ${container.name}: ${e.message}`);
554
+ this.#ownContainersStats[container.name] = {
555
+ ...this.#ownContainersStats[container.name],
556
+ status: running?.status || 'unknown',
557
+ statusTs: Date.now(),
558
+ };
559
+ continue;
560
+ }
561
+ }
562
+ // check the stats
563
+ this.#ownContainersStats[container.name] = {
564
+ ...((await this.containerGetRamAndCpuUsage(container.name)) || {}),
565
+ status: running?.status || 'unknown',
566
+ statusTs: Date.now(),
567
+ };
568
+ }
569
+ }
570
+ }
571
+ /** Read own container stats */
572
+ getOwnContainerStats() {
573
+ return this.#ownContainersStats;
574
+ }
575
+ async containerGetRamAndCpuUsage(containerNameOrId) {
576
+ try {
577
+ const { stdout } = await this.#exec(`stats ${containerNameOrId} --no-stream --format "{{.CPUPerc}};{{.MemUsage}};{{.NetIO}};{{.BlockIO}};{{.PIDs}}"`);
578
+ // Example: "0.15%;12.34MiB / 512MiB;1.2kB / 2.3kB;0B / 0B;5"
579
+ const [cpuStr, memStr, netStr, blockIoStr, pid] = stdout.trim().split(';');
580
+ const [memUsed, memMax] = memStr.split('/').map(it => it.trim());
581
+ const [netRead, netWrite] = netStr.split('/').map(it => it.trim());
582
+ const [blockIoRead, blockIoWrite] = blockIoStr.split('/').map(it => it.trim());
583
+ return {
584
+ ts: Date.now(),
585
+ cpu: parseFloat(cpuStr.replace('%', '').replace(',', '.')),
586
+ memUsed: this.#parseSize(memUsed.replace('iB', 'B')),
587
+ memMax: this.#parseSize(memMax.replace('iB', 'B')),
588
+ netRead: this.#parseSize(netRead.replace('iB', 'B')),
589
+ netWrite: this.#parseSize(netWrite.replace('iB', 'B')),
590
+ processes: parseInt(pid, 10),
591
+ blockIoRead: this.#parseSize(blockIoRead.replace('iB', 'B')),
592
+ blockIoWrite: this.#parseSize(blockIoWrite.replace('iB', 'B')),
593
+ };
594
+ }
595
+ catch (e) {
596
+ this.adapter.log.debug(`Cannot get stats: ${e.message}`);
597
+ return null;
598
+ }
599
+ }
600
+ /**
601
+ * Update the image if a newer version is available
602
+ *
603
+ * @param image Image name with tag
604
+ * @param ignoreIfNotExist If true, do not throw error if image does not exist
605
+ * @returns New image info if image was updated, null if no update was necessary
606
+ */
607
+ async imageUpdate(image, ignoreIfNotExist) {
608
+ const list = await this.imageList();
609
+ if (!image.includes(':')) {
610
+ image += ':latest';
611
+ }
612
+ const existingImage = list.find(it => `${it.repository}:${it.tag}` === image);
613
+ if (!existingImage && !ignoreIfNotExist) {
614
+ throw new Error(`Image ${image} not found`);
615
+ }
616
+ // Pull the image
617
+ const result = await this.imagePull(image);
618
+ if (result.stderr) {
619
+ throw new Error(`Cannot pull image ${image}: ${result.stderr}`);
620
+ }
621
+ const newList = await this.imageList();
622
+ const newImage = newList.find(it => `${it.repository}:${it.tag}` === image);
623
+ if (!newImage) {
624
+ throw new Error(`Image ${image} not found after pull`);
625
+ }
626
+ // If image ID has changed, image was updated
627
+ return !existingImage || existingImage.id !== newImage.id ? newImage : null;
628
+ }
629
+ #exec(command) {
630
+ if (!this.installed) {
631
+ return Promise.reject(new Error('Docker is not installed'));
632
+ }
633
+ const finalCommand = this.needSudo ? `sudo docker ${command}` : `docker ${command}`;
634
+ return execPromise(finalCommand);
635
+ }
636
+ async #isDockerInstalled() {
637
+ try {
638
+ const result = await execPromise('docker --version');
639
+ if (!result.stderr && result.stdout) {
640
+ // "Docker version 28.3.2, build 578ccf6\n"
641
+ return result.stdout.split('\n')[0].trim();
642
+ }
643
+ this.adapter.log.debug(`Docker not installed: ${result.stderr}`);
644
+ }
645
+ catch (e) {
646
+ this.adapter.log.debug(`Docker not installed: ${e.message}`);
647
+ }
648
+ return false;
649
+ }
650
+ async #isNeedSudo() {
651
+ try {
652
+ await execPromise('docker ps');
653
+ return false;
654
+ }
655
+ catch {
656
+ return true;
657
+ }
658
+ }
659
+ /** Get disk usage information */
660
+ async discUsage() {
661
+ const { stdout } = await this.#exec(`system df`);
662
+ const result = { total: { size: 0, reclaimable: 0 } };
663
+ // parse the output
664
+ // TYPE TOTAL ACTIVE SIZE RECLAIMABLE
665
+ // Images 2 1 2.715GB 2.715GB (99%)
666
+ // Containers 1 1 26.22MB 0B (0%)
667
+ // Local Volumes 0 0 0B 0B
668
+ // Build Cache 0 0 0B 0B
669
+ const lines = stdout.split('\n');
670
+ for (const line of lines) {
671
+ const parts = line.trim().split(/\s+/);
672
+ if (parts.length >= 5 && parts[0] !== 'TYPE') {
673
+ let size;
674
+ let reclaimable;
675
+ if (parts[0] === 'Images') {
676
+ const sizeStr = parts[3];
677
+ const reclaimableStr = parts[4].split(' ')[0];
678
+ size = this.#parseSize(sizeStr);
679
+ reclaimable = this.#parseSize(reclaimableStr);
680
+ result.images = {
681
+ total: parseInt(parts[1], 10),
682
+ active: parseInt(parts[2], 10),
683
+ size,
684
+ reclaimable: reclaimable,
685
+ };
686
+ }
687
+ else if (parts[0] === 'Containers') {
688
+ const sizeStr = parts[3];
689
+ const reclaimableStr = parts[4].split(' ')[0];
690
+ size = this.#parseSize(sizeStr);
691
+ reclaimable = this.#parseSize(reclaimableStr);
692
+ result.containers = {
693
+ total: parseInt(parts[1], 10),
694
+ active: parseInt(parts[2], 10),
695
+ size,
696
+ reclaimable: reclaimable,
697
+ };
698
+ }
699
+ else if (parts[0] === 'Local' && parts[1] === 'Volumes') {
700
+ const sizeStr = parts[4];
701
+ const reclaimableStr = parts[5].split(' ')[0];
702
+ size = this.#parseSize(sizeStr);
703
+ reclaimable = this.#parseSize(reclaimableStr);
704
+ result.volumes = {
705
+ total: parseInt(parts[2], 10),
706
+ active: parseInt(parts[3], 10),
707
+ size,
708
+ reclaimable: reclaimable,
709
+ };
710
+ }
711
+ else if (parts[0] === 'Build' && parts[1] === 'Cache') {
712
+ const sizeStr = parts[4];
713
+ const reclaimableStr = parts[5].split(' ')[0];
714
+ size = this.#parseSize(sizeStr);
715
+ reclaimable = this.#parseSize(reclaimableStr);
716
+ result.buildCache = {
717
+ total: parseInt(parts[2], 10),
718
+ active: parseInt(parts[3], 10),
719
+ size,
720
+ reclaimable: reclaimable,
721
+ };
722
+ }
723
+ result.total.size += size || 0;
724
+ result.total.reclaimable += reclaimable || 0;
725
+ }
726
+ }
727
+ return result;
728
+ }
729
+ /** Pull an image from the registry */
730
+ async imagePull(image) {
731
+ try {
732
+ if (!image.includes(':')) {
733
+ image += ':latest';
734
+ }
735
+ const result = await this.#exec(`pull ${image}`);
736
+ const images = await this.imageList();
737
+ if (!images.find(it => `${it.repository}:${it.tag}` === image)) {
738
+ throw new Error(`Image ${image} not found after pull`);
739
+ }
740
+ return { ...result, images };
741
+ }
742
+ catch (e) {
743
+ return { stdout: '', stderr: e.message.toString() };
744
+ }
745
+ }
746
+ /** Autocomplete image names from Docker Hub */
747
+ async imageNameAutocomplete(partialName) {
748
+ try {
749
+ // Read stars and descriptions
750
+ const { stdout } = await this.#exec(`search ${partialName} --format "{{.Name}};{{.Description}};{{.IsOfficial}};{{.StarCount}}" --limit 50`);
751
+ return stdout
752
+ .split('\n')
753
+ .filter(line => line.trim() !== '')
754
+ .map(line => {
755
+ const [name, description, isOfficial, starCount] = line.split(';');
756
+ return {
757
+ name,
758
+ description,
759
+ isOfficial: isOfficial === 'true',
760
+ starCount: parseInt(starCount, 10) || 0,
761
+ };
762
+ });
763
+ }
764
+ catch (e) {
765
+ this.adapter.log.debug(`Cannot search images: ${e.message}`);
766
+ return [];
767
+ }
768
+ }
769
+ /**
770
+ * Create and start a container with the given configuration. No checks are done.
771
+ */
772
+ async containerRun(config) {
773
+ try {
774
+ return await this.#exec(`run ${this.#toDockerRun(config)}`);
775
+ }
776
+ catch (e) {
777
+ return { stdout: '', stderr: e.message.toString() };
778
+ }
779
+ }
780
+ /**
781
+ * Create a container with the given configuration without starting it. No checks are done.
782
+ */
783
+ async containerCreate(config) {
784
+ try {
785
+ return await this.#exec(`create ${this.#toDockerRun(config, true)}`);
786
+ }
787
+ catch (e) {
788
+ return { stdout: '', stderr: e.message.toString() };
789
+ }
790
+ }
791
+ /**
792
+ * Recreate a container
793
+ *
794
+ * This function checks if a container is running, stops it if necessary,
795
+ * removes it and creates a new one with the given configuration.
796
+ *
797
+ * @param config new configuration
798
+ * @returns stdout and stderr of the create command
799
+ */
800
+ async containerReCreate(config) {
801
+ try {
802
+ // Get if the container is running
803
+ let containers = await this.containerList();
804
+ // find ID of container
805
+ const containerInfo = containers.find(it => it.names === config.name);
806
+ if (containerInfo) {
807
+ if (containerInfo.status === 'running' || containerInfo.status === 'restarting') {
808
+ const stopResult = await this.#exec(`stop ${containerInfo.id}`);
809
+ containers = await this.containerList();
810
+ if (containers.find(it => it.id === containerInfo.id && it.status === 'running')) {
811
+ this.adapter.log.warn(`Cannot remove container: ${stopResult.stderr || stopResult.stdout}`);
812
+ throw new Error(`Container ${containerInfo.id} still running after stop`);
813
+ }
814
+ }
815
+ // Remove container
816
+ const rmResult = await this.#exec(`rm ${containerInfo.id}`);
817
+ containers = await this.containerList();
818
+ if (containers.find(it => it.id === containerInfo.id)) {
819
+ this.adapter.log.warn(`Cannot remove container: ${rmResult.stderr || rmResult.stdout}`);
820
+ throw new Error(`Container ${containerInfo.id} still found after stop`);
821
+ }
822
+ }
823
+ return await this.#exec(`create ${this.#toDockerRun(config, true)}`);
824
+ }
825
+ catch (e) {
826
+ return { stdout: '', stderr: e.message.toString() };
827
+ }
828
+ }
829
+ /** List all images */
830
+ async imageList() {
831
+ try {
832
+ const { stdout } = await this.#exec('images --format "{{.Repository}}:{{.Tag}};{{.ID}};{{.CreatedAt}};{{.Size}}"');
833
+ return stdout
834
+ .split('\n')
835
+ .filter(line => line.trim() !== '')
836
+ .map(line => {
837
+ const [repositoryTag, id, createdSince, size] = line.split(';');
838
+ const [repository, tag] = repositoryTag.split(':');
839
+ return {
840
+ repository,
841
+ tag,
842
+ id,
843
+ createdSince,
844
+ size: this.#parseSize(size),
845
+ };
846
+ });
847
+ }
848
+ catch (e) {
849
+ this.adapter.log.debug(`Cannot list images: ${e.message}`);
850
+ return [];
851
+ }
852
+ }
853
+ /** Build an image from a Dockerfile */
854
+ async imageBuild(dockerfilePath, tag) {
855
+ try {
856
+ return await this.#exec(`build -t ${tag} -f ${dockerfilePath} .`);
857
+ }
858
+ catch (e) {
859
+ return { stdout: '', stderr: e.message.toString() };
860
+ }
861
+ }
862
+ /** Tag an image with a new tag */
863
+ async imageTag(imageId, newTag) {
864
+ try {
865
+ return await this.#exec(`tag ${imageId} ${newTag}`);
866
+ }
867
+ catch (e) {
868
+ return { stdout: '', stderr: e.message.toString() };
869
+ }
870
+ }
871
+ /** Remove an image */
872
+ async imageRemove(imageId) {
873
+ try {
874
+ const result = await this.#exec(`rmi ${imageId}`);
875
+ const images = await this.imageList();
876
+ if (images.find(it => `${it.repository}:${it.tag}` === imageId)) {
877
+ return { stdout: '', stderr: `Image ${imageId} still found after deletion`, images };
878
+ }
879
+ return { ...result, images };
880
+ }
881
+ catch (e) {
882
+ return { stdout: '', stderr: e.message.toString() };
883
+ }
884
+ }
885
+ /** Inspect an image */
886
+ async imageInspect(imageId) {
887
+ try {
888
+ const { stdout } = await this.#exec(`inspect ${imageId}`);
889
+ return JSON.parse(stdout)[0];
890
+ }
891
+ catch (e) {
892
+ this.adapter.log.debug(`Cannot inspect image: ${e.message.toString()}`);
893
+ return null;
894
+ }
895
+ }
896
+ #parseSize(sizeStr) {
897
+ const units = {
898
+ B: 1,
899
+ KB: 1024,
900
+ MB: 1024 * 1024,
901
+ GB: 1024 * 1024 * 1024,
902
+ TB: 1024 * 1024 * 1024 * 1024,
903
+ };
904
+ const match = sizeStr.match(/^([\d.]+)([KMGTP]?B)$/);
905
+ if (match) {
906
+ const value = parseFloat(match[1]);
907
+ const unit = match[2];
908
+ return value * (units[unit] || 1);
909
+ }
910
+ return 0;
911
+ }
912
+ /**
913
+ * Stop a container
914
+ *
915
+ * @param container Container name or ID
916
+ */
917
+ async containerStop(container) {
918
+ try {
919
+ let containers = await this.containerList();
920
+ // find ID of container
921
+ const containerInfo = containers.find(it => it.names === container || it.id === container);
922
+ if (!containerInfo) {
923
+ throw new Error(`Container ${container} not found`);
924
+ }
925
+ const result = await this.#exec(`stop ${containerInfo.id}`);
926
+ containers = await this.containerList();
927
+ if (containers.find(it => it.id === containerInfo.id && it.status === 'running')) {
928
+ throw new Error(`Container ${container} still running after stop`);
929
+ }
930
+ return { ...result, containers };
931
+ }
932
+ catch (e) {
933
+ return { stdout: '', stderr: e.message.toString() };
934
+ }
935
+ }
936
+ /**
937
+ * Start a container
938
+ *
939
+ * @param container Container name or ID
940
+ */
941
+ async containerStart(container) {
942
+ try {
943
+ let containers = await this.containerList();
944
+ // find ID of container
945
+ const containerInfo = containers.find(it => it.names === container || it.id === container);
946
+ if (!containerInfo) {
947
+ throw new Error(`Container ${container} not found`);
948
+ }
949
+ const result = await this.#exec(`start ${containerInfo.id}`);
950
+ containers = await this.containerList();
951
+ if (containers.find(it => it.id === containerInfo.id && it.status !== 'running' && it.status !== 'restarting')) {
952
+ throw new Error(`Container ${container} still running after stop`);
953
+ }
954
+ return { ...result, containers };
955
+ }
956
+ catch (e) {
957
+ return { stdout: '', stderr: e.message.toString() };
958
+ }
959
+ }
960
+ /**
961
+ * Restart a container
962
+ *
963
+ * This function restarts a container by its name or ID.
964
+ * It accepts an optional timeout in seconds to wait before killing the container (default is 5 seconds).
965
+ *
966
+ * @param container Container name or ID
967
+ * @param timeoutSeconds Timeout in seconds to wait before killing the container (default: 5)
968
+ */
969
+ async containerRestart(container, timeoutSeconds) {
970
+ try {
971
+ const containers = await this.containerList();
972
+ // find ID of container
973
+ const containerInfo = containers.find(it => it.names === container || it.id === container);
974
+ if (!containerInfo) {
975
+ throw new Error(`Container ${container} not found`);
976
+ }
977
+ return await this.#exec(`restart -t ${timeoutSeconds || 5} ${containerInfo.id}`);
978
+ }
979
+ catch (e) {
980
+ return { stdout: '', stderr: e.message.toString() };
981
+ }
982
+ }
983
+ /**
984
+ * Remove the container and if necessary, stop it first
985
+ *
986
+ * @param container Container name or ID
987
+ */
988
+ async containerRemove(container) {
989
+ try {
990
+ let containers = await this.containerList();
991
+ // find ID of container
992
+ const containerInfo = containers.find(it => it.names === container || it.id === container);
993
+ if (!containerInfo) {
994
+ throw new Error(`Container ${container} not found`);
995
+ }
996
+ // ensure that container is stopped
997
+ if (containerInfo.status === 'running' || containerInfo.status === 'restarting') {
998
+ // stop container
999
+ const result = await this.#exec(`stop ${containerInfo.id}`);
1000
+ if (result.stderr) {
1001
+ throw new Error(`Cannot stop container ${container}: ${result.stderr}`);
1002
+ }
1003
+ }
1004
+ const result = await this.#exec(`rm ${container}`);
1005
+ containers = await this.containerList();
1006
+ if (containers.find(it => it.id === containerInfo.id)) {
1007
+ throw new Error(`Container ${container} still found after stop`);
1008
+ }
1009
+ return { ...result, containers };
1010
+ }
1011
+ catch (e) {
1012
+ return { stdout: '', stderr: e.message.toString() };
1013
+ }
1014
+ }
1015
+ /**
1016
+ * List all containers
1017
+ *
1018
+ * @param all If true, list all containers. If false, list only running containers. Default is true.
1019
+ */
1020
+ async containerList(all = true) {
1021
+ try {
1022
+ const { stdout } = await this.#exec(`ps ${all ? '-a' : ''} --format "{{.Names}};{{.Status}};{{.ID}};{{.Image}};{{.Command}};{{.CreatedAt}};{{.Ports}}"`);
1023
+ return stdout
1024
+ .split('\n')
1025
+ .filter(line => line.trim() !== '')
1026
+ .map(line => {
1027
+ const [names, statusInfo, id, image, command, createdAt, ports] = line.split(';');
1028
+ const [status, ...uptime] = statusInfo.split(' ');
1029
+ let statusKey = status.toLowerCase();
1030
+ if (statusKey === 'up') {
1031
+ statusKey = 'running';
1032
+ }
1033
+ return { id, image, command, createdAt, status: statusKey, uptime: uptime.join(' '), ports, names };
1034
+ });
1035
+ }
1036
+ catch (e) {
1037
+ this.adapter.log.debug(`Cannot list containers: ${e.message}`);
1038
+ return [];
1039
+ }
1040
+ }
1041
+ /**
1042
+ * Get the logs of a container
1043
+ *
1044
+ * @param containerNameOrId Container name or ID
1045
+ * @param options Options for logs
1046
+ * @param options.tail Number of lines to show from the end of the logs
1047
+ * @param options.follow If true, follow the logs (not implemented yet)
1048
+ */
1049
+ async containerLogs(containerNameOrId, options = {}) {
1050
+ try {
1051
+ const args = [];
1052
+ if (options.tail !== undefined) {
1053
+ args.push(`--tail ${options.tail}`);
1054
+ }
1055
+ if (options.follow) {
1056
+ args.push(`--follow`);
1057
+ throw new Error('Follow option is not implemented yet');
1058
+ }
1059
+ const result = await this.#exec(`logs${args.length ? ` ${args.join(' ')}` : ''} ${containerNameOrId}`);
1060
+ return (result.stdout || result.stderr).split('\n').filter(line => line.trim() !== '');
1061
+ }
1062
+ catch (e) {
1063
+ return e
1064
+ .toString()
1065
+ .split('\n')
1066
+ .map((line) => line.trim());
1067
+ }
1068
+ }
1069
+ /** Inspect a container */
1070
+ async containerInspect(containerNameOrId) {
1071
+ try {
1072
+ const { stdout } = await this.#exec(`inspect ${containerNameOrId}`);
1073
+ const result = JSON.parse(stdout)[0];
1074
+ if (result.State.Running) {
1075
+ result.Stats = (await this.containerGetRamAndCpuUsage(containerNameOrId)) || undefined;
1076
+ }
1077
+ return result;
1078
+ }
1079
+ catch (e) {
1080
+ this.adapter.log.debug(`Cannot inspect container: ${e.message.toString()}`);
1081
+ return null;
1082
+ }
1083
+ }
1084
+ /**
1085
+ * Build a docker run command string from ContainerConfig
1086
+ */
1087
+ #toDockerRun(config, create) {
1088
+ const args = [];
1089
+ // detach / interactive
1090
+ if (config.detach !== false && !create) {
1091
+ // default is true
1092
+ args.push('-d');
1093
+ }
1094
+ if (config.tty) {
1095
+ args.push('-t');
1096
+ }
1097
+ if (config.stdinOpen) {
1098
+ args.push('-i');
1099
+ }
1100
+ if (config.removeOnExit) {
1101
+ args.push('--rm');
1102
+ }
1103
+ // name
1104
+ if (config.name) {
1105
+ args.push('--name', config.name);
1106
+ }
1107
+ // hostname / domain
1108
+ if (config.hostname) {
1109
+ args.push('--hostname', config.hostname);
1110
+ }
1111
+ if (config.domainname) {
1112
+ args.push('--domainname', config.domainname);
1113
+ }
1114
+ // environment
1115
+ if (config.environment) {
1116
+ for (const [key, value] of Object.entries(config.environment)) {
1117
+ if (key && value) {
1118
+ args.push('-e', `${key}=${value}`);
1119
+ }
1120
+ }
1121
+ }
1122
+ if (config.envFile) {
1123
+ for (const file of config.envFile) {
1124
+ args.push('--env-file', file);
1125
+ }
1126
+ }
1127
+ // labels
1128
+ if (config.labels) {
1129
+ for (const [key, value] of Object.entries(config.labels)) {
1130
+ args.push('--label', `${key}=${value}`);
1131
+ }
1132
+ }
1133
+ // ports
1134
+ if (config.publishAllPorts) {
1135
+ args.push('-P');
1136
+ }
1137
+ if (config.ports) {
1138
+ for (const p of config.ports) {
1139
+ if (!p.containerPort) {
1140
+ continue;
1141
+ }
1142
+ const mapping = (p.hostIP ? `${p.hostIP}:` : '') +
1143
+ (p.hostPort ? `${p.hostPort}:` : '') +
1144
+ p.containerPort +
1145
+ (p.protocol ? `/${p.protocol}` : '');
1146
+ args.push('-p', mapping);
1147
+ }
1148
+ }
1149
+ // volumes / mounts
1150
+ if (config.volumes) {
1151
+ for (const v of config.volumes) {
1152
+ args.push('-v', v);
1153
+ }
1154
+ }
1155
+ if (config.mounts) {
1156
+ for (const m of config.mounts) {
1157
+ let mount = `type=${m.type},target=${m.target}`;
1158
+ if (m.source) {
1159
+ mount += `,source=${m.source}`;
1160
+ }
1161
+ if (m.readOnly) {
1162
+ mount += `,readonly`;
1163
+ }
1164
+ args.push('--mount', mount);
1165
+ }
1166
+ }
1167
+ // restart policy
1168
+ if (config.restart?.policy) {
1169
+ const val = config.restart.policy === 'on-failure' && config.restart.maxRetries
1170
+ ? `on-failure:${config.restart.maxRetries}`
1171
+ : config.restart.policy;
1172
+ args.push('--restart', val);
1173
+ }
1174
+ // user & workdir
1175
+ if (config.user) {
1176
+ args.push('--user', String(config.user));
1177
+ }
1178
+ if (config.workdir) {
1179
+ args.push('--workdir', config.workdir);
1180
+ }
1181
+ // logging
1182
+ if (config.logging?.driver) {
1183
+ args.push('--log-driver', config.logging.driver);
1184
+ if (config.logging.options) {
1185
+ for (const [k, v] of Object.entries(config.logging.options)) {
1186
+ args.push('--log-opt', `${k}=${v}`);
1187
+ }
1188
+ }
1189
+ }
1190
+ // security
1191
+ if (config.security?.privileged) {
1192
+ args.push('--privileged');
1193
+ }
1194
+ if (config.security?.capAdd) {
1195
+ for (const cap of config.security.capAdd) {
1196
+ args.push('--cap-add', cap);
1197
+ }
1198
+ }
1199
+ if (config.security?.capDrop) {
1200
+ for (const cap of config.security.capDrop) {
1201
+ args.push('--cap-drop', cap);
1202
+ }
1203
+ }
1204
+ if (config.security?.noNewPrivileges) {
1205
+ args.push('--security-opt', 'no-new-privileges');
1206
+ }
1207
+ if (config.security?.apparmor) {
1208
+ args.push('--security-opt', `apparmor=${config.security.apparmor}`);
1209
+ }
1210
+ // network
1211
+ if (config.networkMode) {
1212
+ args.push('--network', config.networkMode);
1213
+ }
1214
+ // extra hosts
1215
+ if (config.extraHosts) {
1216
+ for (const host of config.extraHosts) {
1217
+ if (typeof host === 'string') {
1218
+ args.push('--add-host', host);
1219
+ }
1220
+ else {
1221
+ args.push('--add-host', `${host.host}:${host.ip}`);
1222
+ }
1223
+ }
1224
+ }
1225
+ // sysctls
1226
+ if (config.sysctls) {
1227
+ for (const [k, v] of Object.entries(config.sysctls)) {
1228
+ args.push('--sysctl', `${k}=${v}`);
1229
+ }
1230
+ }
1231
+ // stop signal / timeout
1232
+ if (config.stop?.signal) {
1233
+ args.push('--stop-signal', config.stop.signal);
1234
+ }
1235
+ if (config.stop?.gracePeriodSec !== undefined) {
1236
+ args.push('--stop-timeout', String(config.stop.gracePeriodSec));
1237
+ }
1238
+ // resources
1239
+ if (config.resources?.cpus) {
1240
+ args.push('--cpus', String(config.resources.cpus));
1241
+ }
1242
+ if (config.resources?.memory) {
1243
+ args.push('--memory', String(config.resources.memory));
1244
+ }
1245
+ // image
1246
+ if (!config.image) {
1247
+ throw new Error('ContainerConfig.image is required for docker run');
1248
+ }
1249
+ args.push(config.image);
1250
+ // command override
1251
+ if (config.command) {
1252
+ if (Array.isArray(config.command)) {
1253
+ args.push(...config.command);
1254
+ }
1255
+ else {
1256
+ args.push(config.command);
1257
+ }
1258
+ }
1259
+ return args.join(' ');
1260
+ }
1261
+ async networkList() {
1262
+ // docker network ls
1263
+ try {
1264
+ const { stdout } = await this.#exec(`network ls --format "{{.Name}};{{.ID}};{{.Driver}};{{.Scope}}"`);
1265
+ return stdout
1266
+ .split('\n')
1267
+ .filter(line => line.trim() !== '')
1268
+ .map(line => {
1269
+ const [name, id, driver, scope] = line.split(';');
1270
+ return { name, id, driver: driver, scope };
1271
+ });
1272
+ }
1273
+ catch (e) {
1274
+ this.adapter.log.debug(`Cannot list networks: ${e.message.toString()}`);
1275
+ return [];
1276
+ }
1277
+ }
1278
+ async networkCreate(name, driver) {
1279
+ const result = await this.#exec(`network create ${driver ? `--driver ${driver}` : ''} ${name}`);
1280
+ const networks = await this.networkList();
1281
+ if (!networks.find(it => it.name === name)) {
1282
+ throw new Error(`Network ${name} not found after creation`);
1283
+ }
1284
+ return { ...result, networks };
1285
+ }
1286
+ async networkRemove(networkId) {
1287
+ const result = await this.#exec(`network remove ${networkId}`);
1288
+ const networks = await this.networkList();
1289
+ if (networks.find(it => it.id === networkId)) {
1290
+ throw new Error(`Network ${networkId} still found after deletion`);
1291
+ }
1292
+ return { ...result, networks };
1293
+ }
1294
+ /** Stop own containers if necessary */
1295
+ async destroy() {
1296
+ if (this.#monitoringInterval) {
1297
+ clearInterval(this.#monitoringInterval);
1298
+ this.#monitoringInterval = null;
1299
+ }
1300
+ for (const container of this.#ownContainers) {
1301
+ if (container.iobEnabled !== false && container.iobStopOnUnload) {
1302
+ this.adapter.log.info(`Stopping own container ${container.name} on destroy`);
1303
+ try {
1304
+ await this.containerStop(container.name);
1305
+ }
1306
+ catch (e) {
1307
+ this.adapter.log.warn(`Cannot stop own container ${container.name} on destroy: ${e.message}`);
1308
+ }
1309
+ }
1310
+ }
1311
+ }
1312
+ }
1313
+ _a = DockerManager;
1314
+ exports.default = DockerManager;
1315
+ //# sourceMappingURL=DockerManager.js.map