@iobroker/js-controller-cli 7.2.3 → 8.0.0-alpha.1-20260921-e5941ca8a

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 (225) hide show
  1. package/build/cjs/index.js +3 -3
  2. package/build/cjs/lib/_Types.d.ts +8 -1
  3. package/build/cjs/lib/cli/cliCert.d.ts +7 -4
  4. package/build/cjs/lib/cli/cliCert.js +10 -6
  5. package/build/cjs/lib/cli/cliCert.js.map +2 -2
  6. package/build/cjs/lib/cli/cliCommand.d.ts +63 -15
  7. package/build/cjs/lib/cli/cliCommand.js +8 -1
  8. package/build/cjs/lib/cli/cliCommand.js.map +2 -2
  9. package/build/cjs/lib/cli/cliCompact.d.ts +5 -2
  10. package/build/cjs/lib/cli/cliCompact.js +9 -2
  11. package/build/cjs/lib/cli/cliCompact.js.map +2 -2
  12. package/build/cjs/lib/cli/cliDebug.d.ts +5 -2
  13. package/build/cjs/lib/cli/cliDebug.js +8 -1
  14. package/build/cjs/lib/cli/cliDebug.js.map +2 -2
  15. package/build/cjs/lib/cli/cliHost.d.ts +13 -10
  16. package/build/cjs/lib/cli/cliHost.js +13 -5
  17. package/build/cjs/lib/cli/cliHost.js.map +2 -2
  18. package/build/cjs/lib/cli/cliLogs.d.ts +9 -3
  19. package/build/cjs/lib/cli/cliLogs.js +12 -1
  20. package/build/cjs/lib/cli/cliLogs.js.map +2 -2
  21. package/build/cjs/lib/cli/cliMessage.d.ts +2 -1
  22. package/build/cjs/lib/cli/cliMessage.js +5 -0
  23. package/build/cjs/lib/cli/cliMessage.js.map +2 -2
  24. package/build/cjs/lib/cli/cliObjects.d.ts +21 -18
  25. package/build/cjs/lib/cli/cliObjects.js +51 -33
  26. package/build/cjs/lib/cli/cliObjects.js.map +2 -2
  27. package/build/cjs/lib/cli/cliPlugin.d.ts +3 -3
  28. package/build/cjs/lib/cli/cliPlugin.js +6 -0
  29. package/build/cjs/lib/cli/cliPlugin.js.map +2 -2
  30. package/build/cjs/lib/cli/cliProcess.d.ts +17 -7
  31. package/build/cjs/lib/cli/cliProcess.js +77 -24
  32. package/build/cjs/lib/cli/cliProcess.js.map +3 -3
  33. package/build/cjs/lib/cli/cliStates.d.ts +7 -4
  34. package/build/cjs/lib/cli/cliStates.js +14 -6
  35. package/build/cjs/lib/cli/cliStates.js.map +2 -2
  36. package/build/cjs/lib/cli/cliTools.js +11 -0
  37. package/build/cjs/lib/cli/cliTools.js.map +1 -1
  38. package/build/cjs/lib/cli/messages.js +47 -46
  39. package/build/cjs/lib/cli/messages.js.map +1 -1
  40. package/build/cjs/lib/progressBar.d.ts +59 -0
  41. package/build/cjs/lib/progressBar.js +115 -0
  42. package/build/cjs/lib/progressBar.js.map +7 -0
  43. package/build/cjs/lib/setup/customError.d.ts +4 -0
  44. package/build/cjs/lib/setup/customError.js +7 -0
  45. package/build/cjs/lib/setup/customError.js.map +2 -2
  46. package/build/cjs/lib/setup/dbConnection.js +69 -64
  47. package/build/cjs/lib/setup/dbConnection.js.map +3 -3
  48. package/build/cjs/lib/setup/formatters.d.ts +4 -4
  49. package/build/cjs/lib/setup/formatters.js +5 -0
  50. package/build/cjs/lib/setup/formatters.js.map +2 -2
  51. package/build/cjs/lib/setup/multihostClient.js +6 -2
  52. package/build/cjs/lib/setup/multihostClient.js.map +1 -1
  53. package/build/cjs/lib/setup/pluginInfos.d.ts +5 -0
  54. package/build/cjs/lib/setup/pluginInfos.js +2 -0
  55. package/build/cjs/lib/setup/pluginInfos.js.map +2 -2
  56. package/build/cjs/lib/setup/setupBackup.d.ts +53 -0
  57. package/build/cjs/lib/setup/setupBackup.js +125 -14
  58. package/build/cjs/lib/setup/setupBackup.js.map +3 -3
  59. package/build/cjs/lib/setup/setupInstall.d.ts +51 -15
  60. package/build/cjs/lib/setup/setupInstall.js +212 -28
  61. package/build/cjs/lib/setup/setupInstall.js.map +2 -2
  62. package/build/cjs/lib/setup/setupLicense.d.ts +13 -0
  63. package/build/cjs/lib/setup/setupLicense.js +33 -2
  64. package/build/cjs/lib/setup/setupLicense.js.map +2 -2
  65. package/build/cjs/lib/setup/setupList.d.ts +55 -1
  66. package/build/cjs/lib/setup/setupList.js +78 -24
  67. package/build/cjs/lib/setup/setupList.js.map +3 -3
  68. package/build/cjs/lib/setup/setupMultihost.d.ts +7 -0
  69. package/build/cjs/lib/setup/setupMultihost.js +78 -11
  70. package/build/cjs/lib/setup/setupMultihost.js.map +3 -3
  71. package/build/cjs/lib/setup/setupPacketManager.d.ts +10 -0
  72. package/build/cjs/lib/setup/setupPacketManager.js +16 -6
  73. package/build/cjs/lib/setup/setupPacketManager.js.map +2 -2
  74. package/build/cjs/lib/setup/setupRepo.d.ts +19 -0
  75. package/build/cjs/lib/setup/setupRepo.js +141 -13
  76. package/build/cjs/lib/setup/setupRepo.js.map +2 -2
  77. package/build/cjs/lib/setup/setupSetup.d.ts +34 -0
  78. package/build/cjs/lib/setup/setupSetup.js +209 -35
  79. package/build/cjs/lib/setup/setupSetup.js.map +3 -3
  80. package/build/cjs/lib/setup/setupUpgrade.d.ts +13 -2
  81. package/build/cjs/lib/setup/setupUpgrade.js +72 -12
  82. package/build/cjs/lib/setup/setupUpgrade.js.map +2 -2
  83. package/build/cjs/lib/setup/setupUpload.d.ts +77 -1
  84. package/build/cjs/lib/setup/setupUpload.js +152 -9
  85. package/build/cjs/lib/setup/setupUpload.js.map +2 -2
  86. package/build/cjs/lib/setup/setupUsers.d.ts +44 -14
  87. package/build/cjs/lib/setup/setupUsers.js +330 -17
  88. package/build/cjs/lib/setup/setupUsers.js.map +2 -2
  89. package/build/cjs/lib/setup/setupVendor.d.ts +14 -1
  90. package/build/cjs/lib/setup/setupVendor.js +40 -8
  91. package/build/cjs/lib/setup/setupVendor.js.map +2 -2
  92. package/build/cjs/lib/setup/setupVisDebug.d.ts +16 -2
  93. package/build/cjs/lib/setup/setupVisDebug.js +41 -17
  94. package/build/cjs/lib/setup/setupVisDebug.js.map +3 -3
  95. package/build/cjs/lib/setup/utils.d.ts +1 -1
  96. package/build/cjs/lib/setup/utils.js +13 -9
  97. package/build/cjs/lib/setup/utils.js.map +3 -3
  98. package/build/cjs/lib/setup.d.ts +1 -1
  99. package/build/cjs/lib/setup.js +858 -1758
  100. package/build/cjs/lib/setup.js.map +3 -3
  101. package/build/esm/lib/_Types.d.ts +8 -1
  102. package/build/esm/lib/_Types.d.ts.map +1 -1
  103. package/build/esm/lib/cli/cliCert.d.ts +7 -4
  104. package/build/esm/lib/cli/cliCert.d.ts.map +1 -1
  105. package/build/esm/lib/cli/cliCert.js +6 -6
  106. package/build/esm/lib/cli/cliCert.js.map +1 -1
  107. package/build/esm/lib/cli/cliCommand.d.ts +63 -15
  108. package/build/esm/lib/cli/cliCommand.d.ts.map +1 -1
  109. package/build/esm/lib/cli/cliCommand.js +4 -1
  110. package/build/esm/lib/cli/cliCommand.js.map +1 -1
  111. package/build/esm/lib/cli/cliCompact.d.ts +5 -2
  112. package/build/esm/lib/cli/cliCompact.d.ts.map +1 -1
  113. package/build/esm/lib/cli/cliCompact.js +5 -2
  114. package/build/esm/lib/cli/cliCompact.js.map +1 -1
  115. package/build/esm/lib/cli/cliDebug.d.ts +5 -2
  116. package/build/esm/lib/cli/cliDebug.d.ts.map +1 -1
  117. package/build/esm/lib/cli/cliDebug.js +4 -1
  118. package/build/esm/lib/cli/cliDebug.js.map +1 -1
  119. package/build/esm/lib/cli/cliHost.d.ts +13 -10
  120. package/build/esm/lib/cli/cliHost.d.ts.map +1 -1
  121. package/build/esm/lib/cli/cliHost.js +8 -5
  122. package/build/esm/lib/cli/cliHost.js.map +1 -1
  123. package/build/esm/lib/cli/cliLogs.d.ts +9 -3
  124. package/build/esm/lib/cli/cliLogs.d.ts.map +1 -1
  125. package/build/esm/lib/cli/cliLogs.js +8 -1
  126. package/build/esm/lib/cli/cliLogs.js.map +1 -1
  127. package/build/esm/lib/cli/cliMessage.d.ts +2 -1
  128. package/build/esm/lib/cli/cliMessage.d.ts.map +1 -1
  129. package/build/esm/lib/cli/cliMessage.js +1 -0
  130. package/build/esm/lib/cli/cliMessage.js.map +1 -1
  131. package/build/esm/lib/cli/cliObjects.d.ts +21 -18
  132. package/build/esm/lib/cli/cliObjects.d.ts.map +1 -1
  133. package/build/esm/lib/cli/cliObjects.js +44 -38
  134. package/build/esm/lib/cli/cliObjects.js.map +1 -1
  135. package/build/esm/lib/cli/cliPlugin.d.ts +3 -3
  136. package/build/esm/lib/cli/cliPlugin.d.ts.map +1 -1
  137. package/build/esm/lib/cli/cliPlugin.js +1 -1
  138. package/build/esm/lib/cli/cliPlugin.js.map +1 -1
  139. package/build/esm/lib/cli/cliProcess.d.ts +17 -7
  140. package/build/esm/lib/cli/cliProcess.d.ts.map +1 -1
  141. package/build/esm/lib/cli/cliProcess.js +73 -12
  142. package/build/esm/lib/cli/cliProcess.js.map +1 -1
  143. package/build/esm/lib/cli/cliStates.d.ts +7 -4
  144. package/build/esm/lib/cli/cliStates.d.ts.map +1 -1
  145. package/build/esm/lib/cli/cliStates.js +6 -2
  146. package/build/esm/lib/cli/cliStates.js.map +1 -1
  147. package/build/esm/lib/progressBar.d.ts +59 -0
  148. package/build/esm/lib/progressBar.d.ts.map +1 -0
  149. package/build/esm/lib/progressBar.js +104 -0
  150. package/build/esm/lib/progressBar.js.map +1 -0
  151. package/build/esm/lib/setup/customError.d.ts +4 -0
  152. package/build/esm/lib/setup/customError.d.ts.map +1 -1
  153. package/build/esm/lib/setup/customError.js +3 -0
  154. package/build/esm/lib/setup/customError.js.map +1 -1
  155. package/build/esm/lib/setup/dbConnection.d.ts.map +1 -1
  156. package/build/esm/lib/setup/dbConnection.js +10 -13
  157. package/build/esm/lib/setup/dbConnection.js.map +1 -1
  158. package/build/esm/lib/setup/formatters.d.ts +4 -4
  159. package/build/esm/lib/setup/formatters.js +4 -4
  160. package/build/esm/lib/setup/pluginInfos.d.ts +5 -0
  161. package/build/esm/lib/setup/pluginInfos.d.ts.map +1 -1
  162. package/build/esm/lib/setup/pluginInfos.js +5 -0
  163. package/build/esm/lib/setup/pluginInfos.js.map +1 -1
  164. package/build/esm/lib/setup/setupBackup.d.ts +53 -0
  165. package/build/esm/lib/setup/setupBackup.d.ts.map +1 -1
  166. package/build/esm/lib/setup/setupBackup.js +132 -13
  167. package/build/esm/lib/setup/setupBackup.js.map +1 -1
  168. package/build/esm/lib/setup/setupInstall.d.ts +51 -15
  169. package/build/esm/lib/setup/setupInstall.d.ts.map +1 -1
  170. package/build/esm/lib/setup/setupInstall.js +228 -23
  171. package/build/esm/lib/setup/setupInstall.js.map +1 -1
  172. package/build/esm/lib/setup/setupLicense.d.ts +13 -0
  173. package/build/esm/lib/setup/setupLicense.d.ts.map +1 -1
  174. package/build/esm/lib/setup/setupLicense.js +31 -1
  175. package/build/esm/lib/setup/setupLicense.js.map +1 -1
  176. package/build/esm/lib/setup/setupList.d.ts +55 -1
  177. package/build/esm/lib/setup/setupList.d.ts.map +1 -1
  178. package/build/esm/lib/setup/setupList.js +61 -8
  179. package/build/esm/lib/setup/setupList.js.map +1 -1
  180. package/build/esm/lib/setup/setupMultihost.d.ts +7 -0
  181. package/build/esm/lib/setup/setupMultihost.d.ts.map +1 -1
  182. package/build/esm/lib/setup/setupMultihost.js +85 -6
  183. package/build/esm/lib/setup/setupMultihost.js.map +1 -1
  184. package/build/esm/lib/setup/setupPacketManager.d.ts +10 -0
  185. package/build/esm/lib/setup/setupPacketManager.d.ts.map +1 -1
  186. package/build/esm/lib/setup/setupPacketManager.js +9 -0
  187. package/build/esm/lib/setup/setupPacketManager.js.map +1 -1
  188. package/build/esm/lib/setup/setupRepo.d.ts +19 -0
  189. package/build/esm/lib/setup/setupRepo.d.ts.map +1 -1
  190. package/build/esm/lib/setup/setupRepo.js +164 -14
  191. package/build/esm/lib/setup/setupRepo.js.map +1 -1
  192. package/build/esm/lib/setup/setupSetup.d.ts +34 -0
  193. package/build/esm/lib/setup/setupSetup.d.ts.map +1 -1
  194. package/build/esm/lib/setup/setupSetup.js +218 -21
  195. package/build/esm/lib/setup/setupSetup.js.map +1 -1
  196. package/build/esm/lib/setup/setupUpgrade.d.ts +13 -2
  197. package/build/esm/lib/setup/setupUpgrade.d.ts.map +1 -1
  198. package/build/esm/lib/setup/setupUpgrade.js +70 -6
  199. package/build/esm/lib/setup/setupUpgrade.js.map +1 -1
  200. package/build/esm/lib/setup/setupUpload.d.ts +77 -1
  201. package/build/esm/lib/setup/setupUpload.d.ts.map +1 -1
  202. package/build/esm/lib/setup/setupUpload.js +164 -7
  203. package/build/esm/lib/setup/setupUpload.js.map +1 -1
  204. package/build/esm/lib/setup/setupUsers.d.ts +44 -14
  205. package/build/esm/lib/setup/setupUsers.d.ts.map +1 -1
  206. package/build/esm/lib/setup/setupUsers.js +346 -14
  207. package/build/esm/lib/setup/setupUsers.js.map +1 -1
  208. package/build/esm/lib/setup/setupVendor.d.ts +14 -1
  209. package/build/esm/lib/setup/setupVendor.d.ts.map +1 -1
  210. package/build/esm/lib/setup/setupVendor.js +33 -2
  211. package/build/esm/lib/setup/setupVendor.js.map +1 -1
  212. package/build/esm/lib/setup/setupVisDebug.d.ts +16 -2
  213. package/build/esm/lib/setup/setupVisDebug.d.ts.map +1 -1
  214. package/build/esm/lib/setup/setupVisDebug.js +39 -14
  215. package/build/esm/lib/setup/setupVisDebug.js.map +1 -1
  216. package/build/esm/lib/setup/utils.d.ts +1 -1
  217. package/build/esm/lib/setup/utils.d.ts.map +1 -1
  218. package/build/esm/lib/setup/utils.js +2 -4
  219. package/build/esm/lib/setup/utils.js.map +1 -1
  220. package/build/esm/lib/setup.d.ts +1 -1
  221. package/build/esm/lib/setup.d.ts.map +1 -1
  222. package/build/esm/lib/setup.js +959 -1910
  223. package/build/esm/lib/setup.js.map +1 -1
  224. package/build/tsconfig.build.tsbuildinfo +1 -1
  225. package/package.json +14 -14
@@ -1,13 +1,12 @@
1
1
  import fs from 'fs-extra';
2
- import { tools } from '@iobroker/js-controller-common';
3
- import { EXIT_CODES } from '@iobroker/js-controller-common';
4
- import deepClone from 'deep-clone';
5
- import { isDeepStrictEqual } from 'node:util';
6
2
  import Debug from 'debug';
7
- import { objectsDbHasServer, isLocalObjectsDbServer, isLocalStatesDbServer } from '@iobroker/js-controller-common';
8
3
  import path from 'node:path';
9
4
  import yargs from 'yargs/yargs';
10
- import * as CLITools from '../lib/cli/cliTools.js';
5
+ import * as url from 'node:url';
6
+ import * as events from 'node:events';
7
+ import { createRequire } from 'node:module';
8
+ import { tools, EXIT_CODES, objectsDbHasServer } from '@iobroker/js-controller-common';
9
+ import { SYSTEM_CONFIG_ID, SYSTEM_REPOSITORIES_ID } from '@iobroker/js-controller-common-db/constants';
11
10
  import { CLIHost } from '../lib/cli/cliHost.js';
12
11
  import { CLIStates } from '../lib/cli/cliStates.js';
13
12
  import { CLIDebug } from '../lib/cli/cliDebug.js';
@@ -19,15 +18,13 @@ import { CLIProcess } from '../lib/cli/cliProcess.js';
19
18
  import { CLIMessage } from '../lib/cli/cliMessage.js';
20
19
  import { CLIPlugin } from '../lib/cli/cliPlugin.js';
21
20
  import { error as CLIError } from '../lib/cli/messages.js';
22
- import { BETA_REPO_URL, getRepository, ignoreVersion, isIntegerLikeInput, recognizeVersion, STABLE_REPO_URL, } from '../lib/setup/utils.js';
21
+ import { ignoreVersion, recognizeVersion } from '../lib/setup/utils.js';
23
22
  import { dbConnect, dbConnectAsync, exitApplicationSave } from '../lib/setup/dbConnection.js';
24
23
  import { IoBrokerError } from '../lib/setup/customError.js';
25
- import * as url from 'node:url';
26
- import * as events from 'node:events';
24
+ import { List } from '../lib/setup/setupList.js';
25
+ import * as formatters from '../lib/setup/formatters.js';
27
26
  // eslint-disable-next-line unicorn/prefer-module
28
27
  const thisDir = url.fileURLToPath(new URL('.', import.meta.url || `file://${__filename}`));
29
- import { createRequire } from 'node:module';
30
- import { SYSTEM_CONFIG_ID, SYSTEM_REPOSITORIES_ID } from '@iobroker/js-controller-common-db/constants';
31
28
  // eslint-disable-next-line unicorn/prefer-module
32
29
  const require = createRequire(import.meta.url || `file://${__filename}`);
33
30
  tools.ensureDNSOrder();
@@ -435,18 +432,27 @@ function showHelp() {
435
432
  async function processCommand(command, args, params, callback) {
436
433
  const commandContext = { dbConnect, callback, showHelp };
437
434
  const commandOptions = { ...params, ...commandContext };
435
+ const options = {
436
+ command,
437
+ args,
438
+ params,
439
+ callback,
440
+ commandOptions,
441
+ cleanDatabase,
442
+ restartController,
443
+ };
438
444
  debug(`commandOptions: ${JSON.stringify(commandOptions)}`);
439
445
  debug(`args: ${JSON.stringify(args)}`);
440
446
  switch (command) {
441
447
  case 'start':
442
448
  case 'stop': {
443
449
  const procCommand = new CLIProcess(commandOptions);
444
- procCommand[command](args);
450
+ procCommand[command](args).catch(e => console.error(`Cannot ${command}: ${e.message}`));
445
451
  break;
446
452
  }
447
453
  case 'debug': {
448
454
  const debugCommand = new CLIDebug(commandOptions);
449
- debugCommand.execute(args);
455
+ debugCommand.execute(args).catch(e => console.error(`Cannot debug: ${e.message}`));
450
456
  break;
451
457
  }
452
458
  case 'status':
@@ -458,537 +464,48 @@ async function processCommand(command, args, params, callback) {
458
464
  case 'r':
459
465
  case 'restart': {
460
466
  const procCommand = new CLIProcess(commandOptions);
461
- procCommand.restart(args);
467
+ procCommand.restart(args).catch(e => console.error(`Cannot restart: ${e.message}`));
462
468
  break;
463
469
  }
464
470
  case '_restart':
465
- restartController();
471
+ restartController().catch(e => console.error(`Cannot restart controller: ${e.message}`));
466
472
  callback();
467
473
  break;
468
474
  case 'update': {
469
- const repoUrl = args[0]; // Repo url or name
470
- dbConnect(params, async ({ objects, states }) => {
471
- const { Repo } = await import('./setup/setupRepo.js');
472
- const repo = new Repo({
473
- objects,
474
- states,
475
- });
476
- await repo.showRepo(repoUrl, params);
477
- setTimeout(callback, 1_000);
478
- });
479
- break;
475
+ const { processCommandUpdate } = await import('../lib/setup/setupRepo.js');
476
+ return processCommandUpdate(options);
480
477
  }
481
478
  case 'setup': {
482
- const { Setup } = await import('./setup/setupSetup.js');
483
- const setup = new Setup({
484
- processExit: callback,
485
- cleanDatabase,
486
- restartController,
487
- params,
488
- });
489
- if (args[0] === 'custom' || params.custom) {
490
- const exitCode = await setup.setupCustom();
491
- callback(exitCode);
492
- return;
493
- }
494
- let isFirst = false;
495
- let isRedis = false;
496
- // we support "first" and "redis" without "--" flag
497
- for (const arg of args) {
498
- if (arg === 'first') {
499
- isFirst = true;
500
- }
501
- else if (arg === 'redis') {
502
- isRedis = true;
503
- }
504
- }
505
- // and as --flag
506
- isRedis = params.redis || isRedis;
507
- isFirst = params.first || isFirst;
508
- setup.setup({
509
- callback: async () => {
510
- const { states, objects } = await dbConnectAsync(false, params);
511
- if (isFirst) {
512
- // Creates all instances that are needed on a fresh installation
513
- const { Install } = await import('./setup/setupInstall.js');
514
- const install = new Install({
515
- objects,
516
- states,
517
- processExit: callback,
518
- params,
519
- });
520
- // Define the necessary instances
521
- const initialInstances = ['admin', 'discovery', 'backitup'];
522
- // And try to install each of them
523
- for (const instance of initialInstances) {
524
- try {
525
- const adapterInstalled = !!require.resolve(`${tools.appName.toLowerCase()}.${instance}`);
526
- if (adapterInstalled) {
527
- let otherInstanceExists = false;
528
- try {
529
- // check if another instance exists
530
- const res = await objects.getObjectViewAsync('system', 'instance', {
531
- startkey: `system.adapter.${instance}`,
532
- endkey: `system.adapter.${instance}\u9999`,
533
- });
534
- otherInstanceExists = !!res.rows.length;
535
- }
536
- catch {
537
- // ignore - on install we have no object views
538
- }
539
- if (!otherInstanceExists) {
540
- await install.createInstance(instance, {
541
- enabled: true,
542
- ignoreIfExists: true,
543
- });
544
- }
545
- }
546
- }
547
- catch {
548
- // not found, just continue
549
- }
550
- }
551
- await new Promise(resolve => {
552
- // Creates a fresh certificate
553
- // Create a new instance of the cert command,
554
- // but use the resolve method as a callback
555
- const cert = new CLICert({ ...commandOptions, callback: resolve });
556
- cert.create();
557
- });
558
- }
559
- // we update existing things, in first as well as normal setup
560
- // Rename repositories
561
- const { Repo } = await import('./setup/setupRepo.js');
562
- const repo = new Repo({ objects, states });
563
- try {
564
- await repo.rename('default', 'stable', STABLE_REPO_URL);
565
- await repo.rename('latest', 'beta', BETA_REPO_URL);
566
- }
567
- catch (err) {
568
- console.warn(`Cannot rename: ${err.message}`);
569
- }
570
- // there has been a bug that user can upload js-controller
571
- try {
572
- await objects.delObjectAsync('system.adapter.js-controller');
573
- }
574
- catch {
575
- // ignore
576
- }
577
- try {
578
- const configFile = tools.getConfigFileName();
579
- const configOrig = fs.readJSONSync(configFile);
580
- const config = deepClone(configOrig);
581
- config.objects.options = config.objects.options || {
582
- auth_pass: null,
583
- retry_max_delay: 5_000,
584
- };
585
- if (config.objects.options.retry_max_delay === 15_000 ||
586
- !config.objects.options.retry_max_delay) {
587
- config.objects.options.retry_max_delay = 5_000;
588
- }
589
- config.states.options = config.states.options || {
590
- auth_pass: null,
591
- retry_max_delay: 5_000,
592
- };
593
- if (config.states.options.retry_max_delay === 15_000 ||
594
- !config.states.options.retry_max_delay) {
595
- config.states.options.retry_max_delay = 5_000;
596
- }
597
- let migrated = '';
598
- // We migrate file to jsonl
599
- if (config.states.type === 'file') {
600
- config.states.type = 'jsonl';
601
- const hasLocalStatesServer = await isLocalStatesDbServer('file', config.states.host);
602
- if (hasLocalStatesServer) {
603
- // silent config change on secondaries
604
- console.log('States DB type migrated from "file" to "jsonl"');
605
- migrated += 'States';
606
- }
607
- }
608
- if (config.objects.type === 'file') {
609
- config.objects.type = 'jsonl';
610
- const hasLocalObjectsServer = await isLocalObjectsDbServer('file', config.objects.host);
611
- if (hasLocalObjectsServer) {
612
- // silent config change on secondaries
613
- console.log('Objects DB type migrated from "file" to "jsonl"');
614
- migrated += migrated ? ' and Objects' : 'Objects';
615
- }
616
- }
617
- if (migrated) {
618
- const { NotificationHandler } = await import('@iobroker/js-controller-common');
619
- const hostname = tools.getHostName();
620
- const notificationSettings = {
621
- states,
622
- objects,
623
- log: console,
624
- logPrefix: '',
625
- host: hostname,
626
- };
627
- const notificationHandler = new NotificationHandler(notificationSettings);
628
- try {
629
- const ioPackage = fs.readJsonSync(path.join(tools.getControllerDir(), 'io-package.json'));
630
- await notificationHandler.addConfig(ioPackage.notifications);
631
- await notificationHandler.addMessage({
632
- scope: 'system',
633
- category: 'fileToJsonl',
634
- message: `Migrated: ${migrated}`,
635
- instance: `system.host.${hostname}`,
636
- });
637
- notificationHandler.storeNotifications();
638
- }
639
- catch (e) {
640
- console.warn(`Could not add File-to-JSONL notification: ${e.message}`);
641
- }
642
- }
643
- if (!isDeepStrictEqual(config, configOrig)) {
644
- fs.writeFileSync(configFile, JSON.stringify(config, null, 2));
645
- console.log('ioBroker configuration updated');
646
- }
647
- }
648
- catch (err) {
649
- console.log(`Could not update ioBroker configuration: ${err.message}`);
650
- }
651
- return void callback();
652
- },
653
- ignoreIfExist: isFirst,
654
- useRedis: isRedis,
655
- });
656
- break;
479
+ const { processCommandSetup } = await import('../lib/setup/setupSetup.js');
480
+ return processCommandSetup(options);
657
481
  }
658
482
  case 'url': {
659
- let url = args[0];
660
- const name = args[1];
661
- if (!url) {
662
- console.log('Please provide a URL to install from and optionally a name of the adapter to install');
663
- callback(EXIT_CODES.INVALID_ARGUMENTS);
664
- }
665
- if (url[0] === '"' && url[url.length - 1] === '"') {
666
- url = url.substring(1, url.length - 1);
667
- }
668
- url = url.trim();
669
- dbConnect(params, async ({ objects, states }) => {
670
- const { Install } = await import('./setup/setupInstall.js');
671
- const install = new Install({
672
- objects,
673
- states,
674
- processExit: callback,
675
- params,
676
- });
677
- try {
678
- await install.installAdapterFromUrl(url, name);
679
- return void callback(EXIT_CODES.NO_ERROR);
680
- }
681
- catch (e) {
682
- console.error(`Could not install adapter from url: ${e.message}`);
683
- return void callback(EXIT_CODES.CANNOT_INSTALL_NPM_PACKET);
684
- }
685
- });
686
- break;
687
- }
688
- case 'info': {
689
- dbConnect(params, async ({ objects }) => {
690
- try {
691
- const data = await tools.getHostInfo(objects);
692
- const formatters = await import('./setup/formatters.js');
693
- const formatInfo = {
694
- Uptime: formatters.formatSeconds,
695
- 'System uptime': formatters.formatSeconds,
696
- RAM: formatters.formatRam,
697
- Speed: formatters.formatSpeed,
698
- 'Disk size': formatters.formatBytes,
699
- 'Disk free': formatters.formatBytes,
700
- };
701
- for (const attr of Object.keys(data)) {
702
- console.log(`${attr}${attr.length < 16 ? new Array(16 - attr.length).join(' ') : ''}: ${
703
- // @ts-expect-error todo would need checks
704
- formatInfo[attr] ? formatInfo[attr](data[attr]) : data[attr] || ''}`);
705
- }
706
- }
707
- catch (err) {
708
- console.error(`Cannot read host info: ${typeof err === 'object' ? JSON.stringify(err) : err}`);
709
- return callback(EXIT_CODES.CANNOT_GET_HOST_INFO);
710
- }
711
- return void callback();
712
- });
713
- break;
483
+ const { processCommandUrl } = await import('../lib/setup/setupInstall.js');
484
+ return processCommandUrl(options);
714
485
  }
486
+ case 'info':
487
+ return processCommandInfo(options);
715
488
  case 'a':
716
489
  case 'add':
717
490
  case 'install':
718
491
  case 'i': {
719
- let name = args[0];
720
- let instance = args[1];
721
- let repoUrl = args[2];
722
- if (parseInt(instance, 10).toString() !== (instance || '').toString()) {
723
- repoUrl = instance;
724
- instance = undefined;
725
- }
726
- if (parseInt(repoUrl, 10).toString() === (repoUrl || '').toString()) {
727
- const temp = instance;
728
- instance = repoUrl;
729
- repoUrl = temp;
730
- }
731
- if (instance && parseInt(instance, 10).toString() === (instance || '').toString()) {
732
- params.instance = parseInt(instance, 10);
733
- }
734
- // If user accidentally wrote tools.appName.adapter => remove adapter
735
- name = CLITools.normalizeAdapterName(name);
736
- const parsedName = CLITools.splitAdapterOrInstanceIdentifierWithVersion(name);
737
- if (!parsedName) {
738
- console.log('Invalid adapter name for install');
739
- showHelp();
740
- return void callback(EXIT_CODES.INVALID_ADAPTER_ID);
741
- }
742
- // split the adapter into its parts if necessary
743
- if (parsedName.instance !== null) {
744
- params.instance = parsedName.instance;
745
- }
746
- name = parsedName.name;
747
- const installName = parsedName.nameWithVersion;
748
- const adapterDir = tools.getAdapterDir(name);
749
- dbConnect(params, async ({ objects, states }) => {
750
- const { Install } = await import('./setup/setupInstall.js');
751
- const install = new Install({
752
- objects,
753
- states,
754
- processExit: callback,
755
- params,
756
- });
757
- if (params.host && params.host !== tools.getHostName()) {
758
- // if host argument provided we should check, that host actually exists in mh environment
759
- let obj;
760
- try {
761
- obj = await objects.getObjectAsync(`system.host.${params.host}`);
762
- }
763
- catch (err) {
764
- console.warn(`Could not check existence of host "${params.host}": ${err.message}`);
765
- }
766
- if (!obj) {
767
- console.error(`Cannot add instance to non-existing host "${params.host}"`);
768
- return void callback(EXIT_CODES.NON_EXISTING_HOST);
769
- }
770
- }
771
- if (!adapterDir || !fs.existsSync(adapterDir)) {
772
- try {
773
- const { stoppedList } = await install.downloadPacket(repoUrl, installName);
774
- await install.installAdapter(installName, repoUrl);
775
- await install.enableInstances(stoppedList, true); // even if unlikely make sure to re-enable disabled instances
776
- if (command !== 'install' && command !== 'i') {
777
- await install.createInstance(name, params);
778
- }
779
- return void callback();
780
- }
781
- catch (e) {
782
- console.error(`adapter "${name}" cannot be installed: ${e.message}`);
783
- return void callback(e instanceof IoBrokerError ? e.code : EXIT_CODES.UNKNOWN_ERROR);
784
- }
785
- }
786
- else if (command !== 'install' && command !== 'i') {
787
- try {
788
- await install.createInstance(name, params);
789
- return void callback();
790
- }
791
- catch (err) {
792
- console.error(`adapter "${name}" cannot be installed: ${err.message}`);
793
- return void callback(EXIT_CODES.UNKNOWN_ERROR);
794
- }
795
- }
796
- else {
797
- console.log(`adapter "${name}" already installed. Use "upgrade" to upgrade to a newer version.`);
798
- return void callback(EXIT_CODES.ADAPTER_ALREADY_INSTALLED);
799
- }
800
- });
801
- break;
802
- }
803
- case 'rebuild': {
804
- const options = { debug: process.argv.includes('--debug') };
805
- if (commandOptions.path) {
806
- if (path.isAbsolute(commandOptions.path)) {
807
- options.cwd = commandOptions.path;
808
- }
809
- else {
810
- console.log('Path argument needs to be an absolute path!');
811
- return void exitApplicationSave(EXIT_CODES.INVALID_ARGUMENTS);
812
- }
813
- }
814
- if (commandOptions.module) {
815
- options.module = commandOptions.module;
816
- console.log(`Rebuilding native module "${commandOptions.module}"${options.cwd ? ` in ${options.cwd}` : ''} ...`);
817
- }
818
- else {
819
- console.log(`Rebuilding native modules${options.cwd ? ` in ${options.cwd}` : ''} ...`);
820
- }
821
- const result = await tools.rebuildNodeModules(options);
822
- if (result.success) {
823
- console.log();
824
- console.log(`Rebuilding native modules done`);
825
- return void callback();
826
- }
827
- console.error('Rebuilding native modules failed');
828
- return void exitApplicationSave(result.exitCode);
492
+ const { processCommandInstall } = await import('../lib/setup/setupInstall.js');
493
+ return processCommandInstall(options);
829
494
  }
495
+ case 'rebuild':
496
+ return processCommandRebuild(options);
830
497
  case 'upload':
831
498
  case 'u': {
832
- const name = args[0];
833
- const subTree = args[1];
834
- if (name) {
835
- dbConnect(params, async ({ objects, states }) => {
836
- const { Upload } = await import('./setup/setupUpload.js');
837
- const upload = new Upload({ states, objects });
838
- if (name === 'all') {
839
- try {
840
- const objs = await objects.getObjectListAsync({
841
- startkey: 'system.adapter.',
842
- endkey: 'system.adapter.\u9999',
843
- });
844
- if (objs) {
845
- const adapters = [];
846
- for (const row of objs.rows) {
847
- if (row.value.type !== 'adapter') {
848
- continue;
849
- }
850
- adapters.push(tools.isObject(row.value.common.name)
851
- ? row.value.common.name.en
852
- : row.value.common.name);
853
- }
854
- await upload.uploadAdapterFullAsync(adapters);
855
- }
856
- callback();
857
- }
858
- catch (err) {
859
- console.error(`Cannot upload all adapters: ${err.message}`);
860
- return void callback(EXIT_CODES.CANNOT_UPLOAD_DATA);
861
- }
862
- }
863
- else {
864
- // if upload of file
865
- if (name.includes('.')) {
866
- if (!subTree) {
867
- console.log(`Please specify target name, like:\n${tools.appName} upload /file/picture.png /vis-2.0/main/img/picture.png`);
868
- return void callback(EXIT_CODES.INVALID_ARGUMENTS);
869
- }
870
- try {
871
- const newName = await upload.uploadFile(name, subTree);
872
- console.log(`File "${name}" is successfully saved under ${newName}`);
873
- return void callback();
874
- }
875
- catch (err) {
876
- console.error(`Cannot upload file "${name}": ${err.message}`);
877
- return void callback(EXIT_CODES.CANNOT_UPLOAD_DATA);
878
- }
879
- }
880
- else {
881
- try {
882
- if (subTree) {
883
- await upload.uploadAdapter(name, false, true, subTree);
884
- }
885
- else {
886
- await upload.uploadAdapterFullAsync([name]);
887
- }
888
- return void callback();
889
- }
890
- catch (err) {
891
- console.error(`Cannot upload files "${name}": ${err.message}`);
892
- return void callback(EXIT_CODES.CANNOT_UPLOAD_DATA);
893
- }
894
- }
895
- }
896
- });
897
- }
898
- else {
899
- console.log('No adapter name found!');
900
- showHelp();
901
- return void callback(EXIT_CODES.INVALID_ADAPTER_ID);
902
- }
903
- break;
499
+ const { processCommandUpload } = await import('../lib/setup/setupUpload.js');
500
+ return processCommandUpload(options);
904
501
  }
905
502
  case 'delete':
906
503
  case 'del': {
907
- let adapter = args[0];
908
- let instance = args[1];
909
- // The adapter argument is required
910
- if (!adapter) {
911
- showHelp();
912
- return void callback(EXIT_CODES.INVALID_ADAPTER_ID);
913
- }
914
- // If the user accidentally wrote <tools.appName>.adapter,
915
- // remove <tools.appName> from the adapter name
916
- adapter = CLITools.normalizeAdapterName(adapter);
917
- // Avoid deleting stuff we don't want to delete
918
- // e.g. `system.adapter.*`
919
- if (!instance) {
920
- // Ensure that adapter contains a valid adapter (without instance nr)
921
- // or instance (with instance nr) identifier
922
- if (!CLITools.validateAdapterOrInstanceIdentifier(adapter)) {
923
- showHelp();
924
- return void callback(EXIT_CODES.INVALID_ADAPTER_ID);
925
- }
926
- // split the adapter into adapter + instance if necessary
927
- if (adapter.indexOf('.') > -1) {
928
- [adapter, instance] = adapter.split('.', 2);
929
- }
930
- }
931
- else {
932
- // ensure that adapter contains a valid adapter identifier
933
- // and the instance is a number
934
- if (!CLITools.validateAdapterIdentifier(adapter) || !/^\d+$/.test(instance)) {
935
- showHelp();
936
- return void callback(EXIT_CODES.INVALID_ADAPTER_ID);
937
- }
938
- }
939
- if (instance) {
940
- dbConnect(params, async ({ objects, states }) => {
941
- const { Install } = await import('../lib/setup/setupInstall.js');
942
- const install = new Install({
943
- objects,
944
- states,
945
- processExit: callback,
946
- params,
947
- });
948
- console.log(`Delete instance "${adapter}.${instance}"`);
949
- await install.deleteInstance(adapter, parseInt(instance));
950
- callback();
951
- });
952
- }
953
- else {
954
- dbConnect(params, async ({ objects, states }) => {
955
- const { Install } = await import('../lib/setup/setupInstall.js');
956
- const install = new Install({
957
- objects,
958
- states,
959
- processExit: callback,
960
- params,
961
- });
962
- console.log(`Delete adapter "${adapter}"`);
963
- const resultCode = await install.deleteAdapter(adapter);
964
- callback(resultCode);
965
- });
966
- }
967
- break;
968
- }
969
- case 'unsetup': {
970
- const rl = (await import('node:readline')).createInterface({
971
- input: process.stdin,
972
- output: process.stdout,
973
- });
974
- if (params.yes || params.y || params.Y) {
975
- unsetup(params, callback);
976
- }
977
- else {
978
- rl.question('UUID will be deleted. Are you sure? [y/N]: ', answer => {
979
- rl.close();
980
- answer = answer.toLowerCase();
981
- if (answer === 'y' || answer === 'yes' || answer === 'ja' || answer === 'j') {
982
- unsetup(params, callback);
983
- }
984
- else {
985
- console.log('Nothing deleted');
986
- return void callback();
987
- }
988
- });
989
- }
990
- break;
504
+ const { processCommandDelete } = await import('../lib/setup/setupInstall.js');
505
+ return processCommandDelete(options);
991
506
  }
507
+ case 'unsetup':
508
+ return processCommandUnsetup(options);
992
509
  case 'o':
993
510
  case 'object': {
994
511
  const objectsCommand = new CLIObjects(commandOptions);
@@ -1013,159 +530,26 @@ async function processCommand(command, args, params, callback) {
1013
530
  break;
1014
531
  }
1015
532
  case 'upgrade': {
1016
- let adapter = CLITools.normalizeAdapterName(args[0]);
1017
- if (adapter === 'all') {
1018
- adapter = null;
1019
- }
1020
- dbConnect(params, async ({ objects, states }) => {
1021
- const { Upgrade } = await import('./setup/setupUpgrade.js');
1022
- const upgrade = new Upgrade({
1023
- objects,
1024
- states,
1025
- params,
1026
- processExit: callback,
1027
- });
1028
- if (adapter) {
1029
- try {
1030
- if (adapter.split('@')[0] === 'self') {
1031
- const hostAlive = await states.getStateAsync(`system.host.${tools.getHostName()}.alive`);
1032
- const version = adapter.split('@')[1];
1033
- await upgrade.upgradeController({
1034
- forceDowngrade: params.force || params.f,
1035
- controllerRunning: !!hostAlive?.val,
1036
- version,
1037
- });
1038
- }
1039
- else {
1040
- await upgrade.upgradeAdapter('', adapter, params.force || params.f, params.y || params.yes, false);
1041
- }
1042
- return void callback();
1043
- }
1044
- catch (err) {
1045
- console.error(`Cannot upgrade: ${err.message}`);
1046
- return void callback(EXIT_CODES.INVALID_REPO);
1047
- }
1048
- }
1049
- else {
1050
- // upgrade all
1051
- try {
1052
- const links = await getRepository({ objects });
1053
- if (!links) {
1054
- return void callback(EXIT_CODES.INVALID_REPO);
1055
- }
1056
- await upgrade.upgradeAdapterHelper(links, Object.keys(links).sort(), false, params.y || params.yes);
1057
- return void callback();
1058
- }
1059
- catch (e) {
1060
- console.error(`Cannot upgrade: ${e.message}`);
1061
- return void callback(e instanceof IoBrokerError ? e.code : EXIT_CODES.INVALID_REPO);
1062
- }
1063
- }
1064
- });
1065
- break;
1066
- }
1067
- case 'clean': {
1068
- const yes = args[0];
1069
- if (yes !== 'yes') {
1070
- console.log(`Command "clean" clears all Objects and States. To execute it write "${tools.appName} clean yes"`);
1071
- }
1072
- else {
1073
- dbConnect(params, async ({ isOffline }) => {
1074
- if (!isOffline) {
1075
- console.error(`Stop ${tools.appName} first!`);
1076
- return void callback(EXIT_CODES.CONTROLLER_RUNNING);
1077
- }
1078
- try {
1079
- const count = await cleanDatabase(true);
1080
- console.log(`Deleted ${count} states`);
1081
- }
1082
- catch {
1083
- // ignore
1084
- }
1085
- restartController();
1086
- console.log(`Restarting ${tools.appName}...`);
1087
- callback();
1088
- });
1089
- }
1090
- break;
533
+ const { processCommandUpgrade } = await import('../lib/setup/setupUpgrade.js');
534
+ return processCommandUpgrade(options);
1091
535
  }
536
+ case 'clean':
537
+ return processCommandClean(options);
1092
538
  case 'restore': {
1093
- const { BackupRestore } = await import('./setup/setupBackup.js');
1094
- dbConnect(params, async ({ isOffline, objects, states }) => {
1095
- if (!isOffline) {
1096
- console.error(`Stop ${tools.appName} first!`);
1097
- return void callback(EXIT_CODES.CONTROLLER_RUNNING);
1098
- }
1099
- const backup = new BackupRestore({
1100
- states,
1101
- objects,
1102
- cleanDatabase,
1103
- restartController,
1104
- processExit: callback,
1105
- });
1106
- const { exitCode } = await backup.restoreBackup({
1107
- name: args[0],
1108
- force: !!params.force,
1109
- dontDeleteAdapters: false,
1110
- });
1111
- if (exitCode === EXIT_CODES.NO_ERROR) {
1112
- console.log('System successfully restored!');
1113
- }
1114
- return void callback(exitCode);
1115
- });
1116
- break;
539
+ const { processCommandRestore } = await import('../lib/setup/setupBackup.js');
540
+ return processCommandRestore(options);
1117
541
  }
1118
542
  case 'backup': {
1119
- const name = args[0];
1120
- const { BackupRestore } = await import('./setup/setupBackup.js');
1121
- dbConnect(params, async ({ states, objects }) => {
1122
- const backup = new BackupRestore({
1123
- states,
1124
- objects,
1125
- cleanDatabase,
1126
- restartController,
1127
- processExit: callback,
1128
- });
1129
- try {
1130
- const filePath = await backup.createBackup(name);
1131
- console.log(`Backup created: ${filePath}`);
1132
- console.log('This backup can only be restored with js-controller version 7.0 or higher');
1133
- return void callback(EXIT_CODES.NO_ERROR);
1134
- }
1135
- catch (e) {
1136
- console.log(`Cannot create backup: ${e.message}`);
1137
- return void callback(e instanceof IoBrokerError ? e.code : EXIT_CODES.CANNOT_EXTRACT_FROM_ZIP);
1138
- }
1139
- });
1140
- break;
543
+ const { processCommandBackup } = await import('../lib/setup/setupBackup.js');
544
+ return processCommandBackup(options);
1141
545
  }
1142
546
  case 'validate': {
1143
- const name = args[0];
1144
- const { BackupRestore } = await import('./setup/setupBackup.js');
1145
- dbConnect(params, async ({ objects, states }) => {
1146
- const backup = new BackupRestore({
1147
- states,
1148
- objects,
1149
- cleanDatabase,
1150
- restartController,
1151
- processExit: callback,
1152
- });
1153
- try {
1154
- await backup.validateBackup(name);
1155
- console.log('Backup OK');
1156
- return void exitApplicationSave(0);
1157
- }
1158
- catch (e) {
1159
- console.log(`Backup check failed: ${e.message}`);
1160
- return void exitApplicationSave(e instanceof IoBrokerError ? e.code : 1);
1161
- }
1162
- });
1163
- break;
547
+ const { processCommandValidate } = await import('../lib/setup/setupBackup.js');
548
+ return processCommandValidate(options);
1164
549
  }
1165
550
  case 'l':
1166
551
  case 'list': {
1167
- dbConnect(params, async ({ objects, states }) => {
1168
- const { List } = await import('./setup/setupList.js');
552
+ dbConnect(params, ({ objects, states }) => {
1169
553
  const list = new List({
1170
554
  states,
1171
555
  objects,
@@ -1175,1343 +559,1011 @@ async function processCommand(command, args, params, callback) {
1175
559
  });
1176
560
  break;
1177
561
  }
1178
- case 'touch': {
1179
- let pattern = args[0];
1180
- if (!pattern) {
1181
- console.log('No file path found. Example: "touch /vis-2.0/main/*"');
1182
- return void callback(EXIT_CODES.INVALID_ARGUMENTS);
1183
- }
1184
- dbConnect(params, ({ states, objects }) => {
1185
- // extract id
1186
- pattern = pattern.replace(/\\/g, '/');
1187
- if (pattern[0] === '/') {
1188
- pattern = pattern.substring(1);
1189
- }
1190
- if (pattern === '*') {
1191
- objects.getObjectList({
1192
- startkey: 'system.adapter.',
1193
- endkey: 'system.adapter.\u9999',
1194
- }, (err, arr) => {
1195
- if (!err && arr?.rows) {
1196
- const files = [];
1197
- let count = 0;
1198
- for (const row of arr.rows) {
1199
- if (row.value.type !== 'adapter') {
1200
- continue;
1201
- }
1202
- count++;
1203
- objects.touch(row.value.common.name, '*', { user: 'system.user.admin' },
1204
- // @ts-expect-error todo this looks wrong, we have no cb args other than err
1205
- async (err, processed, _id) => {
1206
- if (!err && processed) {
1207
- files.push({ id: _id, processed: processed });
1208
- }
1209
- if (!--count) {
1210
- const { List } = await import('./setup/setupList.js');
1211
- const list = new List({
1212
- states,
1213
- objects,
1214
- processExit: callback,
1215
- });
1216
- files.sort((a, b) => a.id.localeCompare(b.id));
1217
- for (const file of files) {
1218
- for (const processedFile of processed) {
1219
- list.showFile(file.id, processedFile.path, processedFile);
1220
- }
1221
- }
1222
- setTimeout(callback, 1_000);
1223
- }
1224
- });
1225
- }
1226
- if (!count) {
1227
- console.log('Nothing found');
1228
- return void callback();
1229
- }
1230
- }
1231
- });
1232
- }
1233
- else {
1234
- const parts = pattern.split('/');
1235
- const id = parts.shift();
1236
- const path = parts.join('/');
1237
- // @ts-expect-error todo processed should not exist, how to proceed?
1238
- objects.touch(id, path, { user: 'system.user.admin' }, async (err, processed) => {
1239
- if (err) {
1240
- console.error(err);
1241
- }
1242
- else {
1243
- if (processed) {
1244
- const { List } = await import('./setup/setupList.js');
1245
- const list = new List({
1246
- states,
1247
- objects,
1248
- processExit: callback,
1249
- });
1250
- for (const processedFile of processed) {
1251
- list.showFile(id, processedFile.path, processedFile);
1252
- }
1253
- }
1254
- }
1255
- setTimeout(callback, 1_000);
1256
- });
1257
- }
1258
- });
562
+ case 'touch':
563
+ return processCommandTouch(options);
564
+ case 'rm':
565
+ return processCommandRm(options);
566
+ case 'chmod':
567
+ return processCommandChmod(options);
568
+ case 'chown':
569
+ return processCommandChown(options);
570
+ case 'user': {
571
+ const { processCommandUser } = await import('../lib/setup/setupUsers.js');
572
+ return processCommandUser(options);
573
+ }
574
+ case 'g':
575
+ case 'group': {
576
+ const { processCommandGroup } = await import('../lib/setup/setupUsers.js');
577
+ return processCommandGroup(options);
578
+ }
579
+ case 'adduser': {
580
+ const { processCommandAddUser } = await import('../lib/setup/setupUsers.js');
581
+ return processCommandAddUser(options);
582
+ }
583
+ case 'passwd': {
584
+ const { processCommandPassword } = await import('../lib/setup/setupUsers.js');
585
+ return processCommandPassword(options);
586
+ }
587
+ case 'ud':
588
+ case 'udel':
589
+ case 'userdel':
590
+ case 'deluser': {
591
+ const { processCommandUserDel } = await import('../lib/setup/setupUsers.js');
592
+ return processCommandUserDel(options);
593
+ }
594
+ case 'package':
595
+ return processCommandPackage(options);
596
+ case 'set':
597
+ return processCommandSet(options);
598
+ case 'host': {
599
+ const hostCommand = new CLIHost(commandOptions);
600
+ hostCommand.execute(args);
1259
601
  break;
1260
602
  }
1261
- case 'rm': {
1262
- let pattern = args[0];
1263
- if (!pattern) {
1264
- console.log('No file path found. Example: "touch /vis-2.0/main/*"');
1265
- return void callback(EXIT_CODES.INVALID_ARGUMENTS);
1266
- }
1267
- dbConnect(params, ({ objects, states }) => {
1268
- // extract id
1269
- pattern = pattern.replace(/\\/g, '/');
1270
- if (pattern[0] === '/') {
1271
- pattern = pattern.substring(1);
1272
- }
1273
- if (pattern === '*') {
1274
- objects.getObjectList({
1275
- startkey: 'system.adapter.',
1276
- endkey: 'system.adapter.\u9999',
1277
- }, (err, arr) => {
1278
- if (!err && arr?.rows) {
1279
- const files = [];
1280
- let count = 0;
1281
- for (const row of arr.rows) {
1282
- if (row.value.type !== 'adapter') {
1283
- continue;
1284
- }
1285
- count++;
1286
- objects.rm(row.value.common.name, '*', { user: 'system.user.admin' },
1287
- // @ts-expect-error todo id should not exist according to types check it
1288
- async (err, processed, _id) => {
1289
- if (!err && processed) {
1290
- files.push({ id: _id, processed: processed });
1291
- }
1292
- if (!--count) {
1293
- const { List } = await import('./setup/setupList.js');
1294
- const list = new List({
1295
- states,
1296
- objects,
1297
- processExit: callback,
1298
- });
1299
- files.sort((a, b) => a.id.localeCompare(b.id));
1300
- list.showFileHeader();
1301
- for (const file of files) {
1302
- for (const processedFile of processed) {
1303
- list.showFile(file.id, processedFile.path, processedFile);
1304
- }
1305
- }
1306
- setTimeout(callback, 1_000);
1307
- }
1308
- });
1309
- }
1310
- if (!count) {
1311
- console.log('Nothing found');
1312
- return void callback();
1313
- }
1314
- }
1315
- });
1316
- }
1317
- else {
1318
- const parts = pattern.split('/');
1319
- const id = parts.shift();
1320
- const path = parts.join('/');
1321
- objects.rm(id, path, { user: 'system.user.admin' }, async (err, processed) => {
1322
- if (err) {
1323
- console.error(err);
1324
- }
1325
- else {
1326
- if (processed) {
1327
- const { List } = await import('./setup/setupList.js');
1328
- const list = new List({
1329
- states,
1330
- objects,
1331
- processExit: callback,
1332
- });
1333
- list.showFileHeader();
1334
- for (const file of processed) {
1335
- // @ts-expect-error todo types adjustment needed
1336
- list.showFile(id, file.path, file);
1337
- }
1338
- }
1339
- }
1340
- setTimeout(callback, 1_000);
1341
- });
1342
- }
1343
- });
1344
- break;
603
+ case 'visdebug': {
604
+ const { processCommandVisDebug } = await import('../lib/setup/setupVisDebug.js');
605
+ return processCommandVisDebug(options);
1345
606
  }
1346
- case 'chmod': {
1347
- let mode = args[0];
1348
- let pattern = args[1];
1349
- if (!mode) {
1350
- CLIError.requiredArgumentMissing('mode', 'chmod 777 /vis-2.0/main/*');
1351
- return void callback(EXIT_CODES.INVALID_ARGUMENTS);
1352
- }
1353
- // yargs has converted it to number
1354
- mode = parseInt(mode.toString(), 16);
1355
- if (!pattern) {
1356
- CLIError.requiredArgumentMissing('file path', 'chmod 777 /vis-2.0/main/*');
1357
- return void callback(EXIT_CODES.INVALID_ARGUMENTS);
1358
- }
1359
- dbConnect(params, ({ objects, states }) => {
1360
- // extract id
1361
- pattern = pattern.replace(/\\/g, '/');
1362
- if (pattern[0] === '/') {
1363
- pattern = pattern.substring(1);
1364
- }
1365
- if (pattern === '*') {
1366
- objects.getObjectList({
1367
- startkey: 'system.adapter.',
1368
- endkey: 'system.adapter.\u9999',
1369
- }, (err, arr) => {
1370
- if (!err && arr?.rows) {
1371
- const files = [];
1372
- let count = 0;
1373
- for (const row of arr.rows) {
1374
- if (row.value.type !== 'adapter') {
1375
- continue;
1376
- }
1377
- count++;
1378
- objects.chmodFile(row.value.common.name, '*', {
1379
- user: 'system.user.admin',
1380
- mode,
1381
- },
1382
- // @ts-expect-error todo _id should not exist how to handle
1383
- async (err, processed, _id) => {
1384
- if (!err && processed) {
1385
- files.push({ id: _id, processed: processed });
1386
- }
1387
- if (!--count) {
1388
- const { List } = await import('./setup/setupList.js');
1389
- const list = new List({
1390
- states,
1391
- objects,
1392
- processExit: callback,
1393
- });
1394
- files.sort((a, b) => a.id.localeCompare(b.id));
1395
- list.showFileHeader();
1396
- for (const file of files) {
1397
- for (const processedFile of file.processed) {
1398
- list.showFile(file.id, processedFile.path, processedFile);
1399
- }
1400
- }
1401
- setTimeout(callback, 1_000);
1402
- }
1403
- });
1404
- }
1405
- if (!count) {
1406
- console.log('Nothing found');
1407
- return void callback();
1408
- }
1409
- }
1410
- });
1411
- }
1412
- else {
1413
- const parts = pattern.split('/');
1414
- const id = parts.shift();
1415
- const path = parts.join('/');
1416
- objects.chmodFile(id, path, { user: 'system.user.admin', mode: mode }, async (err, processed) => {
1417
- if (err) {
1418
- console.error(err);
1419
- }
1420
- else {
1421
- if (processed) {
1422
- const { List } = await import('./setup/setupList.js');
1423
- const list = new List({
1424
- states,
1425
- objects,
1426
- processExit: callback,
1427
- });
1428
- list.showFileHeader();
1429
- for (const file of processed) {
1430
- // @ts-expect-error todo types adjustment needed
1431
- list.showFile(id, file.path, file);
1432
- }
1433
- }
1434
- }
1435
- setTimeout(callback, 1_000);
1436
- });
1437
- }
1438
- });
607
+ case 'file':
608
+ case 'f':
609
+ return processCommandFile(options);
610
+ case 'id':
611
+ case 'uuid':
612
+ return processCommandUuid(options);
613
+ case 'v':
614
+ case 'version':
615
+ return processCommandVersion(options);
616
+ case 'checklog':
617
+ return processCommandCheckLog(options);
618
+ case 'repo': {
619
+ const { processCommandRepo } = await import('../lib/setup/setupRepo.js');
620
+ return processCommandRepo(options);
621
+ }
622
+ case 'multihost':
623
+ case 'mh': {
624
+ const { processCommandMultihost } = await import('../lib/setup/setupMultihost.js');
625
+ return processCommandMultihost(options);
626
+ }
627
+ case 'vendor': {
628
+ const { processCommandVendor } = await import('../lib/setup/setupVendor.js');
629
+ return processCommandVendor(options);
630
+ }
631
+ case 'cert': {
632
+ const certCommand = new CLICert(commandOptions);
633
+ Promise.resolve(certCommand.execute(args)).catch(e => console.error(`Cannot execute cert command: ${e.message}`));
1439
634
  break;
1440
635
  }
1441
- case 'chown': {
1442
- let user = args[0];
1443
- let group = args[1];
1444
- let pattern = args[2];
1445
- if (!pattern) {
1446
- pattern = group;
1447
- group = undefined;
1448
- }
1449
- if (!user) {
1450
- CLIError.requiredArgumentMissing('user', 'chown user /vis-2.0/main/*');
1451
- return void callback(EXIT_CODES.INVALID_ARGUMENTS);
1452
- }
1453
- else if (user.substring(12) !== 'system.user.') {
1454
- user = `system.user.${user}`;
1455
- }
1456
- if (group && group.substring(13) !== 'system.group.') {
1457
- group = `system.group.${group}`;
1458
- }
1459
- if (!pattern) {
1460
- CLIError.requiredArgumentMissing('file path', 'chown user /vis-2.0/main/*');
1461
- return void callback(EXIT_CODES.INVALID_ARGUMENTS);
1462
- }
1463
- dbConnect(params, ({ objects, states }) => {
1464
- // extract id
1465
- pattern = pattern.replace(/\\/g, '/');
1466
- if (pattern[0] === '/') {
1467
- pattern = pattern.substring(1);
1468
- }
1469
- if (pattern === '*') {
1470
- objects.getObjectList({
1471
- startkey: 'system.adapter.',
1472
- endkey: 'system.adapter.\u9999',
1473
- }, (err, arr) => {
1474
- if (!err && arr?.rows) {
1475
- const files = [];
1476
- let count = 0;
1477
- for (const row of arr.rows) {
1478
- if (row.value.type !== 'adapter') {
1479
- continue;
1480
- }
1481
- count++;
1482
- objects.chownFile(row.value.common.name, '*', {
1483
- user: 'system.user.admin',
1484
- owner: user,
1485
- ownerGroup: group,
1486
- },
1487
- // @ts-expect-error todo _id should not exist how to handle
1488
- async (err, processed, _id) => {
1489
- if (!err && processed) {
1490
- files.push({ id: _id, processed: processed });
1491
- }
1492
- if (!--count) {
1493
- const { List } = await import('./setup/setupList.js');
1494
- const list = new List({
1495
- states,
1496
- objects,
1497
- processExit: callback,
1498
- });
1499
- files.sort((a, b) => a.id.localeCompare(b.id));
1500
- list.showFileHeader();
1501
- for (let k = 0; k < files.length; k++) {
1502
- for (let t = 0; t < files[k].processed.length; t++) {
1503
- list.showFile(files[k].id, files[k].processed[t].path, files[k].processed[t]);
1504
- }
1505
- }
1506
- setTimeout(callback, 1_000);
1507
- }
1508
- });
1509
- }
1510
- if (!count) {
1511
- console.log('Nothing found');
1512
- return void callback();
1513
- }
1514
- }
1515
- });
1516
- }
1517
- else {
1518
- const parts = pattern.split('/');
1519
- const id = parts.shift();
1520
- const path = parts.join('/');
1521
- objects.chownFile(id, path, {
1522
- user: 'system.user.admin',
1523
- owner: user,
1524
- ownerGroup: group,
1525
- }, async (err, processed) => {
1526
- if (err) {
1527
- console.error(err);
1528
- }
1529
- else {
1530
- // call here list
1531
- if (processed) {
1532
- const { List } = await import('./setup/setupList.js');
1533
- const list = new List({
1534
- states,
1535
- objects,
1536
- processExit: callback,
1537
- });
1538
- list.showFileHeader();
1539
- for (const file of processed) {
1540
- // @ts-expect-error todo types adjustment needed
1541
- list.showFile(id, file.path, file);
1542
- }
1543
- }
1544
- }
1545
- setTimeout(callback, 1_000);
1546
- });
1547
- }
1548
- });
636
+ case 'compact': {
637
+ const compactCommand = new CLICompact(commandOptions);
638
+ compactCommand.execute(args);
1549
639
  break;
1550
640
  }
1551
- case 'user': {
1552
- const command = args[0] || '';
1553
- let user = args[1] || '';
1554
- if (user && user.startsWith('system.user.')) {
1555
- user = user.substring('system.user.'.length);
1556
- }
1557
- dbConnect(params, async ({ objects }) => {
1558
- const { Users } = await import('./setup/setupUsers.js');
1559
- const users = new Users({
1560
- objects,
1561
- processExit: callback,
1562
- });
1563
- const password = params.password;
1564
- const group = params.ingroup || 'system.group.administrator';
1565
- if (command === 'add') {
1566
- users.addUserPrompt(user, group, password, (err) => {
1567
- if (err) {
1568
- console.error(err);
1569
- return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP);
1570
- }
1571
- console.log(`User "${user}" created (Group: ${group.replace('system.group.', '')})`);
1572
- return void callback();
1573
- });
1574
- }
1575
- else if (command === 'del' || command === 'delete') {
1576
- users.delUser(user, (err) => {
1577
- if (err) {
1578
- console.error(err);
1579
- return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP);
1580
- }
1581
- console.log(`User "${user}" deleted`);
1582
- return void callback();
1583
- });
1584
- }
1585
- else if (command === 'check') {
1586
- users.checkUserPassword(user, password, err => {
1587
- if (err) {
1588
- console.error(err.message);
1589
- return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP);
1590
- }
1591
- console.log(`Password for user "${user}" matches.`);
1592
- return void callback();
1593
- });
1594
- }
1595
- else if (command === 'set' || command === 'passwd') {
1596
- users.setUserPassword(user, password, err => {
1597
- if (err) {
1598
- console.error(err.message);
1599
- return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP);
1600
- }
1601
- console.log(`Password for "${user}" was successfully set.`);
1602
- return void callback();
1603
- });
1604
- }
1605
- else if (command === 'enable' || command === 'e') {
1606
- users.enableUser(user, true, err => {
1607
- if (err) {
1608
- console.error(err.message);
1609
- return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP);
1610
- }
1611
- console.log(`User "${user}" was successfully enabled.`);
1612
- return void callback();
1613
- });
1614
- }
1615
- else if (command === 'disable' || command === 'd') {
1616
- users.enableUser(user, false, err => {
1617
- if (err) {
1618
- console.error(err.message);
1619
- return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP);
1620
- }
1621
- console.log(`User "${user}" was successfully disabled.`);
1622
- return void callback();
1623
- });
1624
- }
1625
- else if (command === 'get') {
1626
- users.getUser(user, (err, isEnabled) => {
1627
- if (err) {
1628
- console.error(err.message);
1629
- return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP);
1630
- }
1631
- console.log(`User "${user}" is ${isEnabled ? 'enabled' : 'disabled'}`);
1632
- return void callback();
1633
- });
1634
- }
1635
- else {
1636
- console.warn(`Unknown command "${command}". Available commands are: add, del, passwd, enable, disable, check, get`);
1637
- return void callback(EXIT_CODES.INVALID_ARGUMENTS);
1638
- }
1639
- });
641
+ case 'plugin': {
642
+ const pluginCommand = new CLIPlugin(commandOptions);
643
+ pluginCommand.execute(args);
1640
644
  break;
1641
645
  }
1642
- case 'g':
1643
- case 'group': {
1644
- const command = args[0] || '';
1645
- let group = args[1] || '';
1646
- let user = args[2] || '';
1647
- if (group && group.startsWith('system.group.')) {
1648
- group = group.substring('system.group.'.length);
1649
- }
1650
- if (user && user.startsWith('system.user.')) {
1651
- user = user.substring('system.user.'.length);
1652
- }
1653
- if (!command) {
1654
- console.warn(`Unknown command "${command}". Available commands are: add, del, passwd, enable, disable, list, get`);
1655
- return void callback(EXIT_CODES.INVALID_ARGUMENTS);
1656
- }
1657
- if (!group) {
1658
- console.warn(`Please define group name: group ${command} groupName`);
1659
- return callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP);
1660
- }
1661
- dbConnect(params, async ({ objects }) => {
1662
- const { Users } = await import('./setup/setupUsers.js');
1663
- const users = new Users({
1664
- objects,
1665
- processExit: callback,
1666
- });
1667
- if (command === 'useradd' || command === 'adduser') {
1668
- if (!user) {
1669
- console.warn('Please define user name: "group useradd groupName userName"');
1670
- return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP);
1671
- }
1672
- users.addUserToGroup(user, group, (err) => {
1673
- if (err) {
1674
- console.error(err);
1675
- return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP);
1676
- }
1677
- console.log(`User "${user}" was added to group "${group}"`);
1678
- return void callback();
1679
- });
1680
- }
1681
- else if (command === 'userdel' || command === 'deluser') {
1682
- if (!user) {
1683
- console.warn('Please define user name: "group userdel groupName userName"');
1684
- return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP);
1685
- }
1686
- users.removeUserFromGroup(user, group, (err) => {
1687
- if (err) {
1688
- console.error(err);
1689
- return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP);
1690
- }
1691
- console.log(`User "${user}" was deleted from group "${group}"`);
1692
- return void callback();
1693
- });
1694
- }
1695
- else if (command === 'add') {
646
+ case 'license': {
647
+ const { processCommandLicense } = await import('../lib/setup/setupLicense.js');
648
+ return processCommandLicense(options);
649
+ }
650
+ default: {
651
+ if (params.v || params.version) {
652
+ let pckg;
653
+ if (command) {
1696
654
  try {
1697
- await users.addGroup(group);
1698
- console.log(`Group "${group}" was created`);
1699
- return void callback();
655
+ pckg = require(`${tools.appName.toLowerCase()}.${command}/package.json`);
1700
656
  }
1701
657
  catch {
1702
- return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP);
658
+ pckg = { version: `"${command}" not found` };
1703
659
  }
1704
660
  }
1705
- else if (command === 'del' || command === 'delete') {
1706
- users.delGroup(group, (err) => {
1707
- if (err) {
1708
- console.error(err);
1709
- return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP);
1710
- }
1711
- console.log(`Group "${group}" was deleted`);
1712
- return void callback();
1713
- });
661
+ else {
662
+ pckg = require(`@iobroker/js-controller-common/package.json`);
1714
663
  }
1715
- else if (command === 'list' || command === 'l') {
1716
- users.getGroup(group, (err, isEnabled, members) => {
1717
- if (err) {
1718
- console.error(err);
1719
- return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP);
1720
- }
1721
- console.log(`Group "${group}" is ${isEnabled ? 'enabled' : 'disabled'} and has following members:`);
1722
- if (members) {
1723
- for (const member of members) {
1724
- console.log(member.substring('system.user.'.length));
1725
- }
1726
- }
1727
- return void callback();
1728
- });
664
+ console.log(pckg.version);
665
+ }
666
+ else {
667
+ showHelp();
668
+ return void callback(EXIT_CODES.INVALID_ARGUMENTS);
669
+ }
670
+ return void callback();
671
+ }
672
+ }
673
+ }
674
+ /**
675
+ * Process the `info` CLI command: print host information
676
+ *
677
+ * @param options command options provided by the dispatcher
678
+ */
679
+ function processCommandInfo(options) {
680
+ const { params, callback } = options;
681
+ dbConnect(params, async ({ objects }) => {
682
+ try {
683
+ const data = await tools.getHostInfo(objects);
684
+ for (const attr in data) {
685
+ let info;
686
+ if (attr === 'Uptime' || attr === 'System uptime') {
687
+ info = formatters.formatSeconds(data[attr]);
1729
688
  }
1730
- else if (command === 'enable' || command === 'e') {
1731
- users.enableGroup(group, true, (err) => {
1732
- if (err) {
1733
- console.error(err);
1734
- return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP);
1735
- }
1736
- console.log(`Group "${group}" was successfully enabled.`);
1737
- return void callback();
1738
- });
689
+ else if (attr === 'RAM') {
690
+ info = formatters.formatRam(data[attr]);
1739
691
  }
1740
- else if (command === 'disable' || command === 'd') {
1741
- users.enableGroup(group, false, (err) => {
1742
- if (err) {
1743
- console.error(err);
1744
- return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP);
1745
- }
1746
- console.log(`Group "${group}" was successfully disabled.`);
1747
- return void callback();
1748
- });
692
+ else if (attr === 'Speed') {
693
+ info = formatters.formatSpeed(data[attr]);
1749
694
  }
1750
- else if (command === 'get') {
1751
- users.getGroup(group, (err, isEnabled) => {
1752
- if (err) {
1753
- console.error(err);
1754
- return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP);
1755
- }
1756
- console.log(`Group "${group}" is ${isEnabled ? 'enabled' : 'disabled'}`);
1757
- return void callback();
1758
- });
695
+ else if (attr === 'Disk size' || attr === 'Disk free') {
696
+ info = formatters.formatBytes(data[attr]);
1759
697
  }
1760
698
  else {
1761
- console.warn(`Unknown command "${command}". Available commands are: add, del, passwd, enable, disable, list, get`);
1762
- return void callback(EXIT_CODES.INVALID_ARGUMENTS);
699
+ info = data[attr];
1763
700
  }
1764
- });
1765
- break;
701
+ console.log(`${attr}${attr.length < 16 ? new Array(16 - attr.length).join(' ') : ''}: ${info || ''}`);
702
+ }
1766
703
  }
1767
- case 'adduser': {
1768
- const user = args[0];
1769
- const group = params.ingroup || 'system.group.administrator';
1770
- const password = params.password;
1771
- dbConnect(params, async ({ objects }) => {
1772
- const { Users } = await import('./setup/setupUsers.js');
1773
- const users = new Users({
1774
- objects,
1775
- processExit: callback,
1776
- });
1777
- users.addUserPrompt(user, group, password, (err) => {
1778
- if (err) {
1779
- console.error(err);
1780
- return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP);
1781
- }
1782
- console.log(`User "${user}" created (Group: ${group.replace('system.group.', '')})`);
1783
- return void callback();
1784
- });
1785
- });
1786
- break;
704
+ catch (err) {
705
+ console.error(`Cannot read host info: ${typeof err === 'object' ? JSON.stringify(err) : err}`);
706
+ return callback(EXIT_CODES.CANNOT_GET_HOST_INFO);
1787
707
  }
1788
- case 'passwd': {
1789
- const user = args[0];
1790
- const password = params.password;
1791
- dbConnect(params, async ({ objects }) => {
1792
- const { Users } = await import('./setup/setupUsers.js');
1793
- const users = new Users({
1794
- objects,
1795
- processExit: callback,
1796
- });
1797
- users.setUserPassword(user, password, (err) => {
1798
- if (err) {
1799
- console.error(err);
1800
- return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP);
1801
- }
1802
- console.log(`Password for "${user}" was successfully set.`);
1803
- return void callback();
1804
- });
1805
- });
1806
- break;
708
+ return void callback();
709
+ });
710
+ }
711
+ /**
712
+ * Process the `rebuild` CLI command: rebuild all native modules or a single native module
713
+ *
714
+ * @param options command options provided by the dispatcher
715
+ */
716
+ async function processCommandRebuild(options) {
717
+ const { callback, commandOptions } = options;
718
+ const rebuildOptions = { debug: process.argv.includes('--debug') };
719
+ if (commandOptions.path) {
720
+ if (path.isAbsolute(commandOptions.path)) {
721
+ rebuildOptions.cwd = commandOptions.path;
1807
722
  }
1808
- case 'ud':
1809
- case 'udel':
1810
- case 'userdel':
1811
- case 'deluser': {
1812
- const user = args[0];
1813
- dbConnect(params, async ({ objects }) => {
1814
- const { Users } = await import('./setup/setupUsers.js');
1815
- const users = new Users({
1816
- objects,
1817
- processExit: callback,
1818
- });
1819
- users.delUser(user, (err) => {
1820
- if (err) {
1821
- console.error(err);
1822
- return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP);
1823
- }
1824
- console.log(`User "${user}" deleted`);
1825
- return void callback();
1826
- });
1827
- });
1828
- break;
723
+ else {
724
+ console.log('Path argument needs to be an absolute path!');
725
+ return void exitApplicationSave(EXIT_CODES.INVALID_ARGUMENTS);
726
+ }
727
+ }
728
+ if (commandOptions.module) {
729
+ rebuildOptions.module = commandOptions.module;
730
+ console.log(`Rebuilding native module "${commandOptions.module}"${rebuildOptions.cwd ? ` in ${rebuildOptions.cwd}` : ''} ...`);
731
+ }
732
+ else {
733
+ console.log(`Rebuilding native modules${rebuildOptions.cwd ? ` in ${rebuildOptions.cwd}` : ''} ...`);
734
+ }
735
+ const result = await tools.rebuildNodeModules(rebuildOptions);
736
+ if (result.success) {
737
+ console.log();
738
+ console.log(`Rebuilding native modules done`);
739
+ return void callback();
740
+ }
741
+ console.error('Rebuilding native modules failed');
742
+ return void exitApplicationSave(result.exitCode);
743
+ }
744
+ /**
745
+ * Process the `unsetup` CLI command: delete the installation UUID after confirmation
746
+ *
747
+ * @param options command options provided by the dispatcher
748
+ */
749
+ async function processCommandUnsetup(options) {
750
+ const { params, callback } = options;
751
+ const rl = (await import('node:readline')).createInterface({
752
+ input: process.stdin,
753
+ output: process.stdout,
754
+ });
755
+ if (params.yes || params.y || params.Y) {
756
+ unsetup(params, callback).catch(e => console.error(`Cannot unsetup: ${e.message}`));
757
+ }
758
+ else {
759
+ rl.question('UUID will be deleted. Are you sure? [y/N]: ', answer => {
760
+ rl.close();
761
+ answer = answer.toLowerCase();
762
+ if (answer === 'y' || answer === 'yes' || answer === 'ja' || answer === 'j') {
763
+ unsetup(params, callback).catch(e => console.error(`Cannot unsetup: ${e.message}`));
764
+ return;
765
+ }
766
+ console.log('Nothing deleted');
767
+ return void callback();
768
+ });
769
+ }
770
+ }
771
+ /**
772
+ * Process the `clean` CLI command: clear all objects and states
773
+ *
774
+ * @param options command options provided by the dispatcher
775
+ */
776
+ function processCommandClean(options) {
777
+ const { args, params, callback } = options;
778
+ const yes = args[0];
779
+ if (yes !== 'yes') {
780
+ console.log(`Command "clean" clears all Objects and States. To execute it write "${tools.appName} clean yes"`);
781
+ return void callback(EXIT_CODES.INVALID_ARGUMENTS);
782
+ }
783
+ dbConnect(params, async ({ isOffline }) => {
784
+ if (!isOffline) {
785
+ console.error(`Stop ${tools.appName} first!`);
786
+ return void callback(EXIT_CODES.CONTROLLER_RUNNING);
787
+ }
788
+ try {
789
+ const count = await cleanDatabase(true);
790
+ console.log(`Deleted ${count} states`);
791
+ }
792
+ catch {
793
+ // ignore
1829
794
  }
1830
- // Create package.json in /opt/' + tools.appName + '
1831
- case 'package': {
1832
- const json = {
1833
- name: tools.appName,
1834
- engines: {
1835
- node: '>=12',
1836
- },
1837
- optionalDependencies: {},
1838
- dependencies: {
1839
- [`${tools.appName.toLowerCase()}.js-controller`]: '*',
1840
- [`${tools.appName.toLowerCase()}.admin`]: '*',
1841
- },
1842
- author: 'bluefox <dogafox@gmail.com>',
1843
- };
1844
- // @ts-expect-error todo fix it
1845
- tools.getRepositoryFile(null, null, (_err, sources, _sourcesHash) => {
1846
- if (sources) {
1847
- for (const s in sources) {
1848
- if (Object.prototype.hasOwnProperty.call(sources, s)) {
1849
- if (sources[s].url) {
1850
- if (!json.dependencies[`${tools.appName}.${s}`]) {
1851
- json.optionalDependencies[`${tools.appName}.${s}`] = sources[s].url;
1852
- }
795
+ restartController().catch(e => console.error(`Cannot restart controller: ${e.message}`));
796
+ console.log(`Restarting ${tools.appName}...`);
797
+ callback();
798
+ });
799
+ }
800
+ /**
801
+ * Process the `touch` CLI command: touch files matching the given pattern
802
+ *
803
+ * @param options command options provided by the dispatcher
804
+ */
805
+ function processCommandTouch(options) {
806
+ const { args, params, callback } = options;
807
+ let pattern = args[0];
808
+ if (!pattern) {
809
+ console.log('No file path found. Example: "touch /vis-2.0/main/*"');
810
+ return void callback(EXIT_CODES.INVALID_ARGUMENTS);
811
+ }
812
+ dbConnect(params, ({ objects }) => {
813
+ // extract id
814
+ pattern = pattern.replace(/\\/g, '/');
815
+ if (pattern[0] === '/') {
816
+ pattern = pattern.substring(1);
817
+ }
818
+ if (pattern === '*') {
819
+ objects.getObjectList({
820
+ startkey: 'system.adapter.',
821
+ endkey: 'system.adapter.\u9999',
822
+ }, async (err, arr) => {
823
+ if (!err && arr?.rows) {
824
+ const files = [];
825
+ for (const row of arr.rows) {
826
+ const rowTyped = row.value;
827
+ if (rowTyped.type !== 'adapter') {
828
+ continue;
829
+ }
830
+ await new Promise(resolve => objects.touch(rowTyped.common.name, '*', { user: 'system.user.admin' }, err => {
831
+ if (!err) {
832
+ files.push(rowTyped.common.name);
1853
833
  }
1854
834
  else {
1855
- if (!json.dependencies[`${tools.appName}.${s}`]) {
1856
- json.optionalDependencies[`${tools.appName}.${s}`] = '*';
1857
- }
835
+ console.warn(`Cannot touch file ${rowTyped.common.name}`);
1858
836
  }
1859
- }
837
+ resolve();
838
+ }));
1860
839
  }
840
+ files.sort((a, b) => a.localeCompare(b));
841
+ for (const file of files) {
842
+ console.log(`Touched ${file}`);
843
+ }
844
+ setTimeout(callback, 1_000);
1861
845
  }
1862
- fs.writeFileSync(path.join(tools.getRootDir(), 'package.json'), JSON.stringify(json, null, 2));
1863
- return void callback();
1864
846
  });
1865
- break;
1866
847
  }
1867
- case 'set': {
1868
- const instance = args[0];
1869
- if (!instance) {
1870
- console.warn('please specify instance.');
1871
- return void callback(EXIT_CODES.INVALID_ADAPTER_ID);
1872
- }
1873
- if (instance.indexOf('.') === -1) {
1874
- console.warn(`please specify instance, like "${instance}.0"`);
1875
- return void callback(EXIT_CODES.INVALID_ADAPTER_ID);
1876
- }
1877
- const { objects } = await dbConnectAsync(false, params);
1878
- let obj;
1879
- try {
1880
- obj = await objects.getObjectAsync(`system.adapter.${instance}`);
1881
- }
1882
- catch {
1883
- // ignore
1884
- }
1885
- if (obj) {
1886
- let changed = false;
1887
- for (let a = 0; a < process.argv.length; a++) {
1888
- if (process.argv[a].startsWith('--') &&
1889
- process.argv[a + 1] &&
1890
- !process.argv[a + 1].startsWith('--')) {
1891
- const attr = process.argv[a].substring(2);
1892
- let val = process.argv[a + 1];
1893
- if (val === '__EMPTY__') {
1894
- val = '';
1895
- }
1896
- else if (val === 'true') {
1897
- val = true;
1898
- }
1899
- else if (val === 'false') {
1900
- val = false;
1901
- }
1902
- else if (parseFloat(val).toString() === val) {
1903
- val = parseFloat(val);
1904
- }
1905
- if (attr.indexOf('.') !== -1) {
1906
- const parts = attr.split('.');
1907
- if (!obj.native[parts[0]] || obj.native[parts[0]][parts[1]] === undefined) {
1908
- console.warn(`Adapter "${instance}" has no setting "${attr}".`);
1909
- }
1910
- else {
1911
- changed = true;
1912
- obj.native[parts[0]][parts[1]] = val;
1913
- console.log(`New ${attr} for "${instance}" is: ${val}`);
1914
- }
848
+ else {
849
+ const parts = pattern.split('/');
850
+ const id = parts.shift();
851
+ const path = parts.join('/');
852
+ objects.touch(id, path, { user: 'system.user.admin' }, err => {
853
+ if (err) {
854
+ console.error(err);
855
+ }
856
+ else {
857
+ console.log(`Touched ${id}/${path}`);
858
+ }
859
+ setTimeout(callback, 1_000);
860
+ });
861
+ }
862
+ });
863
+ }
864
+ /**
865
+ * Process the `rm` CLI command: remove files matching the given pattern
866
+ *
867
+ * @param options command options provided by the dispatcher
868
+ */
869
+ function processCommandRm(options) {
870
+ const { args, params, callback } = options;
871
+ let pattern = args[0];
872
+ if (!pattern) {
873
+ console.log('No file path found. Example: "touch /vis-2.0/main/*"');
874
+ return void callback(EXIT_CODES.INVALID_ARGUMENTS);
875
+ }
876
+ dbConnect(params, ({ objects }) => {
877
+ // extract id
878
+ pattern = pattern.replace(/\\/g, '/');
879
+ if (pattern[0] === '/') {
880
+ pattern = pattern.substring(1);
881
+ }
882
+ if (pattern === '*') {
883
+ objects.getObjectList({
884
+ startkey: 'system.adapter.',
885
+ endkey: 'system.adapter.\u9999',
886
+ }, async (err, arr) => {
887
+ if (!err && arr?.rows) {
888
+ const files = [];
889
+ for (const row of arr.rows) {
890
+ const rowTyped = row.value;
891
+ if (rowTyped.type !== 'adapter') {
892
+ continue;
1915
893
  }
1916
- else {
1917
- if (obj.native[attr] === undefined) {
1918
- console.warn(`Adapter "${instance}" has no setting "${attr}".`);
1919
- }
1920
- else {
1921
- changed = true;
1922
- obj.native[attr] = val;
1923
- console.log(`New ${attr} for "${instance}" is: ${val}`);
894
+ await new Promise(resolve => objects.rm(rowTyped.common.name, '*', { user: 'system.user.admin' }, (err, processed) => {
895
+ if (!err && processed) {
896
+ files.push({ id: rowTyped.common.name, processed });
1924
897
  }
898
+ resolve();
899
+ }));
900
+ }
901
+ files.sort((a, b) => a.id.localeCompare(b.id));
902
+ for (const file of files) {
903
+ for (const processedFile of file.processed) {
904
+ console.log(`Removed ${processedFile.path}/${processedFile.file}`);
1925
905
  }
1926
- a++;
1927
906
  }
907
+ setTimeout(callback, 1_000);
1928
908
  }
1929
- if (changed) {
1930
- obj.from = `system.host.${tools.getHostName()}.cli`;
1931
- obj.ts = new Date().getTime();
1932
- objects.setObject(`system.adapter.${instance}`, obj, () => {
1933
- console.log(`Instance settings for "${instance}" are changed.`);
1934
- return void callback();
1935
- });
909
+ });
910
+ }
911
+ else {
912
+ const parts = pattern.split('/');
913
+ const id = parts.shift();
914
+ const path = parts.join('/');
915
+ objects.rm(id, path, { user: 'system.user.admin' }, (err, processed) => {
916
+ if (err) {
917
+ console.error(err);
1936
918
  }
1937
919
  else {
1938
- console.log('No parameters set.');
1939
- return void callback();
920
+ if (processed) {
921
+ for (const file of processed) {
922
+ console.log(`Removed ${file.path}/${file.file}`);
923
+ }
924
+ }
1940
925
  }
1941
- }
1942
- else {
1943
- CLIError.invalidInstance(instance);
1944
- return void callback(EXIT_CODES.INVALID_ADAPTER_ID);
1945
- }
1946
- break;
1947
- }
1948
- case 'host': {
1949
- const hostCommand = new CLIHost(commandOptions);
1950
- hostCommand.execute(args);
1951
- break;
1952
- }
1953
- case 'visdebug': {
1954
- let widgetset = args[0];
1955
- if (widgetset && widgetset.startsWith('vis-')) {
1956
- widgetset = widgetset.substring(4);
1957
- }
1958
- const { VisDebug } = await import('./setup/setupVisDebug.js');
1959
- dbConnect(params, ({ objects }) => {
1960
- const visDebug = new VisDebug({
1961
- objects,
1962
- processExit: callback,
1963
- });
1964
- visDebug.enableDebug(widgetset);
926
+ setTimeout(callback, 1_000);
1965
927
  });
1966
- break;
1967
928
  }
1968
- case 'file':
1969
- case 'f': {
1970
- const cmd = args[0];
1971
- if (cmd !== 'read' &&
1972
- cmd !== 'r' &&
1973
- cmd !== 'w' &&
1974
- cmd !== 'write' &&
1975
- cmd !== 'sync' &&
1976
- cmd !== 'rm' &&
1977
- cmd !== 'unlink' &&
1978
- cmd !== 'del') {
1979
- console.log('Invalid parameters: write "file read /vis-2.0/main/img/picture.png /opt/picture/image.png" to read the file');
1980
- return void callback(EXIT_CODES.INVALID_ARGUMENTS);
1981
- }
1982
- if (cmd !== 'sync' && !args[1]) {
1983
- console.log('Invalid parameters: write "file read /vis-2.0/main/img/picture.png /opt/picture/image.png" to read the file from DB and store it on disk');
1984
- console.log('or "file write /opt/SOURCE/image.png /vis-2.0/main/DESTINATION/picture.png" to write the file into DB from disk');
1985
- console.log('or "file rm /vis-2.0/main/img/picture.png" to delete the file in DB');
1986
- return void callback(EXIT_CODES.INVALID_ARGUMENTS);
1987
- }
1988
- dbConnect(params, async ({ objects, objectsDBType }) => {
1989
- if (cmd === 'read' || cmd === 'r') {
1990
- const toRead = args[1];
1991
- const parts = toRead.replace(/\\/g, '/').split('/');
1992
- const path = (args[2] || process.cwd()).replace(/\\/g, '/').split('/');
1993
- const file = path[path.length - 1];
1994
- if (!file.match(/\.[a-zA-Z0-9]+$/)) {
1995
- // If destination location seems to be a directory, add filename
1996
- if (file !== '') {
1997
- path.push(parts[parts.length - 1]);
1998
- }
1999
- else {
2000
- // trailing slash
2001
- path[path.length - 1] = parts[parts.length - 1];
929
+ });
930
+ }
931
+ /**
932
+ * Process the `chmod` CLI command: change file rights for files matching the given pattern
933
+ *
934
+ * @param options command options provided by the dispatcher
935
+ */
936
+ function processCommandChmod(options) {
937
+ const { args, params, callback } = options;
938
+ let mode = args[0];
939
+ let pattern = args[1];
940
+ if (!mode) {
941
+ CLIError.requiredArgumentMissing('mode', 'chmod 777 /vis-2.0/main/*');
942
+ return void callback(EXIT_CODES.INVALID_ARGUMENTS);
943
+ }
944
+ // yargs has converted it to number
945
+ mode = parseInt(mode.toString(), 16);
946
+ if (!pattern) {
947
+ CLIError.requiredArgumentMissing('file path', 'chmod 777 /vis-2.0/main/*');
948
+ return void callback(EXIT_CODES.INVALID_ARGUMENTS);
949
+ }
950
+ dbConnect(params, ({ objects, states }) => {
951
+ // extract id
952
+ pattern = pattern.replace(/\\/g, '/');
953
+ if (pattern[0] === '/') {
954
+ pattern = pattern.substring(1);
955
+ }
956
+ if (pattern === '*') {
957
+ objects.getObjectList({
958
+ startkey: 'system.adapter.',
959
+ endkey: 'system.adapter.\u9999',
960
+ }, async (err, arr) => {
961
+ if (!err && arr?.rows) {
962
+ const files = [];
963
+ for (const row of arr.rows) {
964
+ const rowTyped = row.value;
965
+ if (rowTyped.type !== 'adapter') {
966
+ continue;
2002
967
  }
968
+ await new Promise(resolve => objects.chmodFile(rowTyped.common.name, '*', {
969
+ user: 'system.user.admin',
970
+ mode,
971
+ }, (err, processed) => {
972
+ if (!err && processed) {
973
+ files.push({ id: rowTyped.common.name, processed });
974
+ }
975
+ resolve();
976
+ }));
2003
977
  }
2004
- let adapt = parts.shift();
2005
- if (!adapt) {
2006
- // leading slash
2007
- adapt = parts.shift();
2008
- }
2009
- if (!adapt) {
2010
- console.log(`Invalid parameters: adapter cannot be found!`);
2011
- return void callback(EXIT_CODES.INVALID_ARGUMENTS);
2012
- }
2013
- if (!parts.length) {
2014
- console.log('Invalid parameters: file cannot be found: file not provided');
2015
- return void callback(EXIT_CODES.INVALID_ARGUMENTS);
2016
- }
2017
- objects.readFile(adapt, parts.join('/'), null, (err, data) => {
2018
- err && console.error(err);
2019
- if (data) {
2020
- const destFilename = path.join('/');
2021
- fs.writeFileSync(destFilename, data);
2022
- console.log(`File "${toRead}" stored as "${destFilename}"`);
2023
- }
2024
- return void callback(EXIT_CODES.NO_ERROR);
978
+ const list = new List({
979
+ states,
980
+ objects,
981
+ processExit: callback,
2025
982
  });
2026
- }
2027
- else if (cmd === 'write' || cmd === 'w') {
2028
- const toRead = args[1] || '';
2029
- const parts = toRead.replace(/\\/g, '/').split('/');
2030
- const path = (args[2] || '').replace(/\\/g, '/').split('/');
2031
- let adapt = path.shift();
2032
- if (!adapt) {
2033
- adapt = path.shift();
2034
- }
2035
- if (!path.length) {
2036
- path.push('');
2037
- }
2038
- const fileSrc = parts[parts.length - 1];
2039
- let fileDest = path[path.length - 1];
2040
- if (!fileDest || !fileDest.match(/\.[a-zA-Z0-9]+$/)) {
2041
- // last portion of destination has no extension, consider being a directory
2042
- fileDest = '';
2043
- }
2044
- if (!fileSrc || !fs.existsSync(toRead)) {
2045
- console.log(`Please provide a valid file name as source file: "file write /opt/SOURCE/script.js /vis-2/DESTINATION/script.js"`);
2046
- return void callback(EXIT_CODES.INVALID_ARGUMENTS);
2047
- }
2048
- const srcStat = fs.statSync(toRead);
2049
- if (!srcStat.isFile()) {
2050
- console.log(`Please provide a valid file name as source file: "file write /opt/SOURCE/script.js /vis-2/DESTINATION/script.js"`);
2051
- return void callback(EXIT_CODES.INVALID_ARGUMENTS);
2052
- }
2053
- if (!fileDest) {
2054
- // destination filename is not given, use same name as source file
2055
- fileDest = fileSrc;
2056
- }
2057
- if (fileDest !== path[path.length - 1]) {
2058
- // if last part of path is different then filename, add filename
2059
- if (path[path.length - 1] !== '') {
2060
- path.push(fileDest);
2061
- }
2062
- else {
2063
- // trailing slash
2064
- path[path.length - 1] = fileDest;
983
+ files.sort((a, b) => a.id.localeCompare(b.id));
984
+ list.showFileHeader();
985
+ for (const file of files) {
986
+ for (const processedFile of file.processed) {
987
+ list.showFile(file.id, processedFile.path, processedFile);
2065
988
  }
2066
989
  }
2067
- const destFilename = path.length ? path.join('/') : '/';
2068
- const data = fs.readFileSync(toRead);
2069
- if (!adapt) {
2070
- console.log('Invalid parameters: destination adapter cannot be found!');
2071
- return void callback(EXIT_CODES.INVALID_ARGUMENTS);
2072
- }
2073
- objects.writeFile(adapt, destFilename, data, _err => {
2074
- console.log(`File "${toRead}" stored as "${destFilename}"`);
2075
- return void callback(EXIT_CODES.NO_ERROR);
2076
- });
990
+ setTimeout(callback, 1_000);
2077
991
  }
2078
- else if (cmd === 'del' || cmd === 'rm' || cmd === 'unlink') {
2079
- const toDelete = args[1];
2080
- const parts = toDelete.replace(/\\/g, '/').split('/');
2081
- let adapt = parts.shift();
2082
- if (!adapt) {
2083
- // leading slash
2084
- adapt = parts.shift();
2085
- }
2086
- if (!adapt) {
2087
- console.log('Invalid parameters: adapter cannot be found!');
2088
- return void callback(EXIT_CODES.INVALID_ARGUMENTS);
2089
- }
2090
- if (!parts.length) {
2091
- console.log('Invalid parameters: file cannot be found: file not provided');
2092
- return void callback(EXIT_CODES.INVALID_ARGUMENTS);
2093
- }
2094
- objects.unlink(adapt, parts.join('/'), null, err => {
2095
- err && console.error(err);
2096
- !err && console.log(`File "${toDelete}" was deleted`);
2097
- return void callback(EXIT_CODES.NO_ERROR);
2098
- });
992
+ });
993
+ }
994
+ else {
995
+ const parts = pattern.split('/');
996
+ const id = parts.shift();
997
+ const path = parts.join('/');
998
+ objects.chmodFile(id, path, { user: 'system.user.admin', mode: mode }, (err, processed) => {
999
+ if (err) {
1000
+ console.error(err);
2099
1001
  }
2100
- else if (cmd === 'sync') {
2101
- // Sync
2102
- if (objectsDBType === 'redis') {
2103
- console.log('File Sync is not available when database type "redis" is used.');
2104
- return void callback(EXIT_CODES.INVALID_ARGUMENTS);
2105
- }
2106
- // @ts-expect-error todo look in depth how to handle
2107
- if (!objects.syncFileDirectory || !objects.dirExists) {
2108
- // functionality only exists in server class
2109
- console.log('Please stop ioBroker before syncing files and only use this command on the ioBroker master host!');
2110
- return void callback(EXIT_CODES.CONTROLLER_RUNNING);
1002
+ else {
1003
+ if (processed) {
1004
+ const list = new List({
1005
+ states,
1006
+ objects,
1007
+ processExit: callback,
1008
+ });
1009
+ list.showFileHeader();
1010
+ for (const file of processed) {
1011
+ list.showFile(id, file.path, file);
1012
+ }
2111
1013
  }
2112
- // check meta.user
2113
- try {
2114
- const objExists = await objects.objectExists('meta.user');
2115
- if (objExists) {
2116
- // check if dir is missing
2117
- // @ts-expect-error todo look in depth how to handle
2118
- const dirExists = objects.dirExists('meta.user');
2119
- if (!dirExists) {
2120
- // create meta.user, so users see them as upload target
2121
- await objects.mkdirAsync('meta.user');
2122
- console.log('Successfully created "meta.user" directory');
2123
- }
1014
+ }
1015
+ setTimeout(callback, 1_000);
1016
+ });
1017
+ }
1018
+ });
1019
+ }
1020
+ /**
1021
+ * Process the `chown` CLI command: change file ownership for files matching the given pattern
1022
+ *
1023
+ * @param options command options provided by the dispatcher
1024
+ */
1025
+ function processCommandChown(options) {
1026
+ const { args, params, callback } = options;
1027
+ let user = args[0];
1028
+ let group = args[1];
1029
+ let pattern = args[2];
1030
+ if (!pattern) {
1031
+ pattern = group;
1032
+ group = undefined;
1033
+ }
1034
+ if (!user) {
1035
+ CLIError.requiredArgumentMissing('user', 'chown user /vis-2.0/main/*');
1036
+ return void callback(EXIT_CODES.INVALID_ARGUMENTS);
1037
+ }
1038
+ if (!user.startsWith('system.user.')) {
1039
+ user = `system.user.${user}`;
1040
+ }
1041
+ if (group && !group.startsWith('system.group.')) {
1042
+ group = `system.group.${group}`;
1043
+ }
1044
+ if (!pattern) {
1045
+ CLIError.requiredArgumentMissing('file path', 'chown user /vis-2.0/main/*');
1046
+ return void callback(EXIT_CODES.INVALID_ARGUMENTS);
1047
+ }
1048
+ dbConnect(params, ({ objects, states }) => {
1049
+ // extract id
1050
+ pattern = pattern.replace(/\\/g, '/');
1051
+ if (pattern[0] === '/') {
1052
+ pattern = pattern.substring(1);
1053
+ }
1054
+ if (pattern === '*') {
1055
+ objects.getObjectList({
1056
+ startkey: 'system.adapter.',
1057
+ endkey: 'system.adapter.\u9999',
1058
+ }, async (err, arr) => {
1059
+ if (!err && arr?.rows) {
1060
+ const files = [];
1061
+ for (const row of arr.rows) {
1062
+ const rowTyped = row.value;
1063
+ if (rowTyped.type !== 'adapter') {
1064
+ continue;
2124
1065
  }
1066
+ await new Promise(resolve => objects.chownFile(rowTyped.common.name, '*', {
1067
+ user: 'system.user.admin',
1068
+ owner: user,
1069
+ ownerGroup: group,
1070
+ }, (err, processed) => {
1071
+ if (!err && processed) {
1072
+ files.push({ id: rowTyped.common.name, processed });
1073
+ }
1074
+ resolve();
1075
+ }));
2125
1076
  }
2126
- catch (err) {
2127
- console.warn(`Could not create directory "meta.user": ${err.message}`);
1077
+ if (!files.length) {
1078
+ console.log('Nothing found');
1079
+ return void callback();
2128
1080
  }
2129
- try {
2130
- // @ts-expect-error todo types needed for this one? but only exists sometimes..
2131
- const { numberSuccess, notifications } = objects.syncFileDirectory(args[1]);
2132
- console.log(`${numberSuccess} file(s) successfully synchronized with ioBroker storage`);
2133
- if (notifications.length) {
2134
- console.log();
2135
- console.log('The following notifications happened during sync: ');
2136
- notifications.forEach((el) => console.log(`- ${el}`));
1081
+ const list = new List({
1082
+ states,
1083
+ objects,
1084
+ processExit: callback,
1085
+ });
1086
+ files.sort((a, b) => a.id.localeCompare(b.id));
1087
+ list.showFileHeader();
1088
+ for (let k = 0; k < files.length; k++) {
1089
+ for (let t = 0; t < files[k].processed.length; t++) {
1090
+ list.showFile(files[k].id, files[k].processed[t].path, files[k].processed[t]);
2137
1091
  }
2138
- return void callback(EXIT_CODES.NO_ERROR);
2139
- }
2140
- catch (err) {
2141
- console.error(`Error on sync: ${err.message}. Partial content might have been synced.`);
2142
- return void callback(EXIT_CODES.CANNOT_SYNC_FILES);
2143
1092
  }
1093
+ setTimeout(callback, 1_000);
1094
+ }
1095
+ });
1096
+ }
1097
+ else {
1098
+ const parts = pattern.split('/');
1099
+ const id = parts.shift();
1100
+ const path = parts.join('/');
1101
+ objects.chownFile(id, path, {
1102
+ user: 'system.user.admin',
1103
+ owner: user,
1104
+ ownerGroup: group,
1105
+ }, (err, processed) => {
1106
+ if (err) {
1107
+ console.error(err);
2144
1108
  }
2145
1109
  else {
2146
- console.log('Invalid parameters: write "file read /vis-2.0/main/img/picture.png /opt/picture/image.png" to read the file from DB and store it on disk');
2147
- console.log('or "file write /opt/SOURCE/image.png /vis-2.0/main/DESTINATION/picture.png" to write the file into DB from disk');
2148
- console.log('or "file rm /vis-2.0/main/img/picture.png" to delete the file in DB');
2149
- return void callback(EXIT_CODES.INVALID_ARGUMENTS);
1110
+ // call here list
1111
+ if (processed) {
1112
+ const list = new List({
1113
+ states,
1114
+ objects,
1115
+ processExit: callback,
1116
+ });
1117
+ list.showFileHeader();
1118
+ for (const file of processed) {
1119
+ list.showFile(id, file.path, file);
1120
+ }
1121
+ }
2150
1122
  }
1123
+ setTimeout(callback, 1_000);
2151
1124
  });
2152
- break;
2153
1125
  }
2154
- case 'id':
2155
- case 'uuid': {
2156
- dbConnect(params, ({ objects }) => {
2157
- objects.getObject('system.meta.uuid', (err, obj) => {
2158
- if (err) {
2159
- console.error(`Error: ${err.message}`);
2160
- return void callback(EXIT_CODES.CANNOT_GET_UUID);
1126
+ });
1127
+ }
1128
+ /**
1129
+ * Process the `package` CLI command: create the package.json for the ioBroker installation
1130
+ *
1131
+ * @param options command options provided by the dispatcher
1132
+ */
1133
+ async function processCommandPackage(options) {
1134
+ const { callback } = options;
1135
+ // Create package.json in /opt/' + tools.appName + '
1136
+ const json = {
1137
+ name: tools.appName,
1138
+ engines: {
1139
+ node: '>=22',
1140
+ },
1141
+ optionalDependencies: {},
1142
+ dependencies: {
1143
+ [`${tools.appName.toLowerCase()}.js-controller`]: '*',
1144
+ [`${tools.appName.toLowerCase()}.admin`]: '*',
1145
+ },
1146
+ author: 'bluefox <dogafox@gmail.com>',
1147
+ };
1148
+ try {
1149
+ const sources = await tools.getRepositoryFile();
1150
+ if (sources) {
1151
+ for (const s in sources) {
1152
+ if (Object.prototype.hasOwnProperty.call(sources, s)) {
1153
+ if (sources[s].url) {
1154
+ if (!json.dependencies[`${tools.appName}.${s}`]) {
1155
+ json.optionalDependencies[`${tools.appName}.${s}`] = sources[s].url;
1156
+ }
2161
1157
  }
2162
- if (obj?.native) {
2163
- console.log(obj.native.uuid);
2164
- return void callback();
1158
+ else {
1159
+ if (!json.dependencies[`${tools.appName}.${s}`]) {
1160
+ json.optionalDependencies[`${tools.appName}.${s}`] = '*';
1161
+ }
2165
1162
  }
2166
- console.error('Error: no UUID found');
2167
- return void callback(EXIT_CODES.CANNOT_GET_UUID);
2168
- });
2169
- });
2170
- break;
2171
- }
2172
- case 'v':
2173
- case 'version': {
2174
- const adapter = params.adapter;
2175
- if (params.ignore) {
2176
- try {
2177
- const { objects } = await dbConnectAsync(false, params);
2178
- await ignoreVersion({ adapterName: adapter, version: params.ignore, objects });
2179
- }
2180
- catch (e) {
2181
- console.error(e.message);
2182
- callback(e instanceof IoBrokerError ? e.code : EXIT_CODES.UNKNOWN_ERROR);
2183
- return;
2184
- }
2185
- console.log(`Successfully ignored version "${params.ignore}" of adapter "${params.adapter}"!`);
2186
- callback();
2187
- return;
2188
- }
2189
- if (params.recognize) {
2190
- try {
2191
- const { objects } = await dbConnectAsync(false, params);
2192
- await recognizeVersion({ adapterName: adapter, objects });
2193
- }
2194
- catch (e) {
2195
- console.error(e.message);
2196
- callback(e instanceof IoBrokerError ? e.code : EXIT_CODES.UNKNOWN_ERROR);
2197
- }
2198
- console.log(`Successfully recognized all versions of adapter "${params.adapter}" again!`);
2199
- callback();
2200
- return;
2201
- }
2202
- let packJson;
2203
- if (adapter) {
2204
- try {
2205
- packJson = require(`${tools.appName.toLowerCase()}.${adapter}/package.json`);
2206
- }
2207
- catch {
2208
- packJson = { version: `"${adapter}" not found` };
2209
1163
  }
2210
1164
  }
2211
- else {
2212
- packJson = require(`@iobroker/js-controller-common/package.json`);
2213
- }
2214
- console.log(packJson.version);
2215
- return void callback();
2216
- }
2217
- case 'checklog': {
2218
- dbConnect(params, async ({ objects, states, isOffline, objectsDBType }) => {
2219
- const hasLocalObjectsServer = await objectsDbHasServer(objectsDBType);
2220
- if (isOffline && hasLocalObjectsServer) {
2221
- console.log(`${tools.appName} is not running`);
2222
- return void callback(EXIT_CODES.CONTROLLER_NOT_RUNNING);
2223
- }
2224
- console.log(`${tools.appName} is running`);
2225
- objects.getObjectList({
2226
- startkey: 'system.host.',
2227
- endkey: `system.host.\u9999`,
2228
- }, null, (err, res) => {
2229
- if (!err && res?.rows.length) {
2230
- for (const row of res.rows) {
2231
- const parts = row.id.split('.');
2232
- // ignore system.host.name.alive and so on
2233
- if (parts.length === 3) {
2234
- states.pushMessage(row.id, {
2235
- command: 'checkLogging',
2236
- message: null,
2237
- from: 'console',
2238
- });
2239
- }
2240
- }
2241
- }
2242
- setTimeout(callback, 200);
2243
- });
2244
- });
2245
- break;
2246
1165
  }
2247
- case 'repo': {
2248
- let repoUrlOrCommand = args[0]; // Repo url or name or "add" / "del" / "set" / "show" / "addset" / "unset"
2249
- let repoName = args[1]; // Repo url or name
2250
- let repoUrl = args[2]; // Repo url or name
2251
- if (repoUrlOrCommand !== 'add' &&
2252
- repoUrlOrCommand !== 'del' &&
2253
- repoUrlOrCommand !== 'set' &&
2254
- repoUrlOrCommand !== 'show' &&
2255
- repoUrlOrCommand !== 'addset' &&
2256
- repoUrlOrCommand !== 'unset') {
2257
- repoUrl = repoUrlOrCommand;
2258
- repoUrlOrCommand = 'show';
2259
- }
2260
- const { objects, states } = await dbConnectAsync(false, params);
2261
- const { Repo } = await import('./setup/setupRepo.js');
2262
- const repo = new Repo({
2263
- objects,
2264
- states,
2265
- });
2266
- if (repoUrlOrCommand === 'show') {
2267
- try {
2268
- await repo.showRepoStatus();
2269
- return void callback();
2270
- }
2271
- catch (e) {
2272
- console.error(`Cannot show repository status: ${e.message}`);
2273
- return void callback(EXIT_CODES.INVALID_REPO);
1166
+ fs.writeFileSync(path.join(tools.getRootDir(), 'package.json'), JSON.stringify(json, null, 2));
1167
+ return void callback();
1168
+ }
1169
+ catch (e) {
1170
+ console.error(`Cannot read repository file: ${e}`);
1171
+ return void callback(EXIT_CODES.UNKNOWN_ERROR);
1172
+ }
1173
+ }
1174
+ /**
1175
+ * Process the `set` CLI command: change settings of an adapter instance config
1176
+ *
1177
+ * @param options command options provided by the dispatcher
1178
+ */
1179
+ async function processCommandSet(options) {
1180
+ const { args, params, callback } = options;
1181
+ const instance = args[0];
1182
+ if (!instance) {
1183
+ console.warn('please specify instance.');
1184
+ return void callback(EXIT_CODES.INVALID_ADAPTER_ID);
1185
+ }
1186
+ if (!instance.includes('.')) {
1187
+ console.warn(`please specify instance, like "${instance}.0"`);
1188
+ return void callback(EXIT_CODES.INVALID_ADAPTER_ID);
1189
+ }
1190
+ const { objects } = await dbConnectAsync(false, params);
1191
+ let obj;
1192
+ try {
1193
+ obj = await objects.getObjectAsync(`system.adapter.${instance}`);
1194
+ }
1195
+ catch {
1196
+ // ignore
1197
+ }
1198
+ if (obj) {
1199
+ let changed = false;
1200
+ for (let a = 0; a < process.argv.length; a++) {
1201
+ if (process.argv[a].startsWith('--') && process.argv[a + 1] && !process.argv[a + 1].startsWith('--')) {
1202
+ const attr = process.argv[a].substring(2);
1203
+ let val = process.argv[a + 1];
1204
+ if (val === '__EMPTY__') {
1205
+ val = '';
2274
1206
  }
2275
- }
2276
- if (repoUrlOrCommand === 'add' ||
2277
- repoUrlOrCommand === 'del' ||
2278
- repoUrlOrCommand === 'set' ||
2279
- repoUrlOrCommand === 'addset' ||
2280
- repoUrlOrCommand === 'unset') {
2281
- if (!repoName || !repoName.match(/[-_\w]+/)) {
2282
- console.error(`Invalid repository name: "${repoName}"`);
2283
- return void callback(EXIT_CODES.INVALID_ARGUMENTS);
1207
+ else if (val === 'true') {
1208
+ val = true;
2284
1209
  }
2285
- if (isIntegerLikeInput(repoName)) {
2286
- try {
2287
- repoName = await repo.getNameByIndex(parseInt(repoName));
2288
- }
2289
- catch (e) {
2290
- console.error(e.message);
2291
- return void callback(EXIT_CODES.INVALID_ARGUMENTS);
2292
- }
1210
+ else if (val === 'false') {
1211
+ val = false;
2293
1212
  }
2294
- if (repoUrlOrCommand === 'add' || repoUrlOrCommand === 'addset') {
2295
- if (!repoUrl) {
2296
- console.warn(`Please define repository URL or path: ${tools.appName.toLowerCase()} add <repoName> <repoUrlOrPath>`);
2297
- return void callback(EXIT_CODES.INVALID_ARGUMENTS);
2298
- }
2299
- try {
2300
- await repo.add(repoName, repoUrl);
2301
- if (repoUrlOrCommand === 'addset') {
2302
- await repo.setActive(repoName);
2303
- console.log(`Repository "${repoName}" set as active: "${repoUrl}"`);
2304
- await repo.showRepoStatus();
2305
- return void callback();
2306
- }
2307
- console.log(`Repository "${repoName}" added as "${repoUrl}"`);
2308
- await repo.showRepoStatus();
2309
- return void callback();
2310
- }
2311
- catch (e) {
2312
- console.error(`Cannot add repository location: ${e.message}`);
2313
- return void callback(EXIT_CODES.INVALID_REPO);
2314
- }
1213
+ else if (parseFloat(val).toString() === val) {
1214
+ val = parseFloat(val);
2315
1215
  }
2316
- else if (repoUrlOrCommand === 'set') {
2317
- try {
2318
- await repo.setActive(repoName);
2319
- console.log(`Repository "${repoName}" set as active.`);
2320
- await repo.showRepoStatus();
2321
- return void callback();
1216
+ if (attr.indexOf('.') !== -1) {
1217
+ const parts = attr.split('.');
1218
+ if (!obj.native[parts[0]] || obj.native[parts[0]][parts[1]] === undefined) {
1219
+ console.warn(`Adapter "${instance}" has no setting "${attr}".`);
2322
1220
  }
2323
- catch (e) {
2324
- console.error(`Cannot activate repository: ${e.message}`);
2325
- return void callback(EXIT_CODES.INVALID_REPO);
1221
+ else {
1222
+ changed = true;
1223
+ obj.native[parts[0]][parts[1]] = val;
1224
+ console.log(`New ${attr} for "${instance}" is: ${val}`);
2326
1225
  }
2327
1226
  }
2328
- else if (repoUrlOrCommand === 'del') {
2329
- try {
2330
- await repo.del(repoName);
2331
- console.log(`Repository "${repoName}" deleted.`);
2332
- await repo.showRepoStatus();
2333
- return void callback();
1227
+ else {
1228
+ if (obj.native[attr] === undefined) {
1229
+ console.warn(`Adapter "${instance}" has no setting "${attr}".`);
2334
1230
  }
2335
- catch (e) {
2336
- console.error(`Cannot remove repository: ${e.message}`);
2337
- return void callback(EXIT_CODES.INVALID_REPO);
1231
+ else {
1232
+ changed = true;
1233
+ obj.native[attr] = val;
1234
+ console.log(`New ${attr} for "${instance}" is: ${val}`);
2338
1235
  }
2339
1236
  }
2340
- else if (repoUrlOrCommand === 'unset') {
2341
- try {
2342
- await repo.setInactive(repoName);
2343
- console.log(`Repository "${repoName}" deactivated.`);
2344
- await repo.showRepoStatus();
2345
- return void callback();
2346
- }
2347
- catch (e) {
2348
- console.error(`Cannot deactivate repository: ${e.message}`);
2349
- return void callback(EXIT_CODES.INVALID_REPO);
2350
- }
1237
+ a++;
1238
+ }
1239
+ }
1240
+ if (changed) {
1241
+ obj.from = `system.host.${tools.getHostName()}.cli`;
1242
+ obj.ts = new Date().getTime();
1243
+ objects.setObject(`system.adapter.${instance}`, obj, () => {
1244
+ console.log(`Instance settings for "${instance}" are changed.`);
1245
+ return void callback();
1246
+ });
1247
+ }
1248
+ else {
1249
+ console.log('No parameters set.');
1250
+ return void callback();
1251
+ }
1252
+ }
1253
+ else {
1254
+ CLIError.invalidInstance(instance);
1255
+ return void callback(EXIT_CODES.INVALID_ADAPTER_ID);
1256
+ }
1257
+ }
1258
+ /**
1259
+ * Process the `file` CLI command: read, write, delete or sync files in the objects DB
1260
+ *
1261
+ * @param options command options provided by the dispatcher
1262
+ */
1263
+ function processCommandFile(options) {
1264
+ const { args, params, callback } = options;
1265
+ const cmd = args[0];
1266
+ if (cmd !== 'read' &&
1267
+ cmd !== 'r' &&
1268
+ cmd !== 'w' &&
1269
+ cmd !== 'write' &&
1270
+ cmd !== 'sync' &&
1271
+ cmd !== 'rm' &&
1272
+ cmd !== 'unlink' &&
1273
+ cmd !== 'del') {
1274
+ console.log('Invalid parameters: write "file read /vis-2.0/main/img/picture.png /opt/picture/image.png" to read the file');
1275
+ return void callback(EXIT_CODES.INVALID_ARGUMENTS);
1276
+ }
1277
+ if (cmd !== 'sync' && !args[1]) {
1278
+ console.log('Invalid parameters: write "file read /vis-2.0/main/img/picture.png /opt/picture/image.png" to read the file from DB and store it on disk');
1279
+ console.log('or "file write /opt/SOURCE/image.png /vis-2.0/main/DESTINATION/picture.png" to write the file into DB from disk');
1280
+ console.log('or "file rm /vis-2.0/main/img/picture.png" to delete the file in DB');
1281
+ return void callback(EXIT_CODES.INVALID_ARGUMENTS);
1282
+ }
1283
+ dbConnect(params, async ({ objects, objectsDBType }) => {
1284
+ if (cmd === 'read' || cmd === 'r') {
1285
+ const toRead = args[1];
1286
+ const parts = toRead.replace(/\\/g, '/').split('/');
1287
+ const path = (args[2] || process.cwd()).replace(/\\/g, '/').split('/');
1288
+ const file = path[path.length - 1];
1289
+ if (!file.match(/\.[a-zA-Z0-9]+$/)) {
1290
+ // If destination location seems to be a directory, add filename
1291
+ if (file !== '') {
1292
+ path.push(parts[parts.length - 1]);
2351
1293
  }
2352
1294
  else {
2353
- console.warn(`Unknown repo command: ${repoUrlOrCommand}`);
2354
- return void callback(EXIT_CODES.INVALID_ARGUMENTS);
1295
+ // trailing slash
1296
+ path[path.length - 1] = parts[parts.length - 1];
2355
1297
  }
2356
1298
  }
2357
- break;
2358
- }
2359
- case 'multihost':
2360
- case 'mh': {
2361
- const cmd = args[0];
2362
- if (cmd !== 'c' &&
2363
- cmd !== 'connect' &&
2364
- cmd !== 's' &&
2365
- cmd !== 'status' &&
2366
- cmd !== 'b' &&
2367
- cmd !== 'browse' &&
2368
- cmd !== 'e' &&
2369
- cmd !== 'enable' &&
2370
- cmd !== 'd' &&
2371
- cmd !== 'disable') {
2372
- console.log('Invalid parameters. Following is possible: enable, browse, connect, status');
1299
+ let adapt = parts.shift();
1300
+ if (!adapt) {
1301
+ // leading slash
1302
+ adapt = parts.shift();
1303
+ }
1304
+ if (!adapt) {
1305
+ console.log(`Invalid parameters: adapter cannot be found!`);
2373
1306
  return void callback(EXIT_CODES.INVALID_ARGUMENTS);
2374
1307
  }
2375
- dbConnect(params, async ({ objects, states }) => {
2376
- const { Multihost } = await import('./setup/setupMultihost.js');
2377
- const mh = new Multihost({
2378
- params,
2379
- objects,
2380
- });
2381
- if (cmd === 's' || cmd === 'status') {
2382
- mh.status();
2383
- return void callback();
2384
- }
2385
- else if (cmd === 'b' || cmd === 'browse') {
2386
- try {
2387
- const list = await mh.browse();
2388
- mh.showHosts(list);
2389
- return void callback();
2390
- }
2391
- catch (e) {
2392
- console.error(e.message);
2393
- return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP);
2394
- }
2395
- }
2396
- else if (cmd === 'e' || cmd === 'enable') {
2397
- mh.enable(true, async (err) => {
2398
- if (err) {
2399
- console.error(err);
2400
- return void callback(EXIT_CODES.CANNOT_ENABLE_MULTIHOST);
2401
- }
2402
- await states.pushMessage(`system.host.${tools.getHostName()}`, {
2403
- command: 'updateMultihost',
2404
- message: null,
2405
- from: 'setup',
2406
- });
2407
- callback();
2408
- });
2409
- }
2410
- else if (cmd === 'd' || cmd === 'disable') {
2411
- mh.enable(false, async (err) => {
2412
- if (err) {
2413
- console.error(err);
2414
- return void callback(EXIT_CODES.CANNOT_ENABLE_MULTIHOST);
2415
- }
2416
- await states.pushMessage(`system.host.${tools.getHostName()}`, {
2417
- command: 'updateMultihost',
2418
- message: null,
2419
- from: 'setup',
2420
- });
2421
- callback();
2422
- });
1308
+ if (!parts.length) {
1309
+ console.log('Invalid parameters: file cannot be found: file not provided');
1310
+ return void callback(EXIT_CODES.INVALID_ARGUMENTS);
1311
+ }
1312
+ objects.readFile(adapt, parts.join('/'), null, (err, data) => {
1313
+ if (err) {
1314
+ console.error(err);
2423
1315
  }
2424
- else if (cmd === 'c' || cmd === 'connect') {
2425
- mh.connect(parseInt(args[1]), args[2], (err) => {
2426
- if (err) {
2427
- console.error(err);
2428
- }
2429
- return void callback(err ? 1 : 0);
2430
- });
1316
+ if (data) {
1317
+ const destFilename = path.join('/');
1318
+ fs.writeFileSync(destFilename, data);
1319
+ console.log(`File "${toRead}" stored as "${destFilename}"`);
2431
1320
  }
1321
+ return void callback(EXIT_CODES.NO_ERROR);
2432
1322
  });
2433
- break;
2434
1323
  }
2435
- case 'vendor': {
2436
- const password = args[0];
2437
- const file = args[1];
2438
- const javascriptPassword = args[2];
2439
- if (!password) {
2440
- console.warn(`Please specify the password to update the vendor information!\n${tools.appName.toLowerCase()} vendor <PASS_PHRASE> <vendor.json>`);
1324
+ else if (cmd === 'write' || cmd === 'w') {
1325
+ const toRead = args[1] || '';
1326
+ const parts = toRead.replace(/\\/g, '/').split('/');
1327
+ const path = (args[2] || '').replace(/\\/g, '/').split('/');
1328
+ let adapt = path.shift();
1329
+ if (!adapt) {
1330
+ adapt = path.shift();
1331
+ }
1332
+ if (!path.length) {
1333
+ path.push('');
1334
+ }
1335
+ const fileSrc = parts[parts.length - 1];
1336
+ let fileDest = path[path.length - 1];
1337
+ if (!fileDest || !fileDest.match(/\.[a-zA-Z0-9]+$/)) {
1338
+ // last portion of destination has no extension, consider being a directory
1339
+ fileDest = '';
1340
+ }
1341
+ if (!fileSrc || !fs.existsSync(toRead)) {
1342
+ console.log(`Please provide a valid file name as source file: "file write /opt/SOURCE/script.js /vis-2/DESTINATION/script.js"`);
2441
1343
  return void callback(EXIT_CODES.INVALID_ARGUMENTS);
2442
1344
  }
2443
- const { objects } = await dbConnectAsync(false, params);
2444
- const { Vendor } = await import('./setup/setupVendor.js');
2445
- const vendor = new Vendor({ objects });
2446
- try {
2447
- await vendor.checkVendor(file, password, javascriptPassword);
2448
- console.log(`Synchronised vendor information.`);
2449
- return void callback();
1345
+ const srcStat = fs.statSync(toRead);
1346
+ if (!srcStat.isFile()) {
1347
+ console.log(`Please provide a valid file name as source file: "file write /opt/SOURCE/script.js /vis-2/DESTINATION/script.js"`);
1348
+ return void callback(EXIT_CODES.INVALID_ARGUMENTS);
2450
1349
  }
2451
- catch (err) {
2452
- console.error(`Cannot update vendor information: ${err.message}`);
2453
- return void callback(EXIT_CODES.CANNOT_UPDATE_VENDOR);
1350
+ if (!fileDest) {
1351
+ // destination filename is not given, use same name as source file
1352
+ fileDest = fileSrc;
2454
1353
  }
1354
+ if (fileDest !== path[path.length - 1]) {
1355
+ // if last part of path is different then filename, add filename
1356
+ if (path[path.length - 1] !== '') {
1357
+ path.push(fileDest);
1358
+ }
1359
+ else {
1360
+ // trailing slash
1361
+ path[path.length - 1] = fileDest;
1362
+ }
1363
+ }
1364
+ const destFilename = path.length ? path.join('/') : '/';
1365
+ if (!fs.existsSync(toRead)) {
1366
+ console.log('Invalid parameters: file does not exist!');
1367
+ return void callback(EXIT_CODES.INVALID_ARGUMENTS);
1368
+ }
1369
+ const data = fs.readFileSync(toRead);
1370
+ if (!adapt) {
1371
+ console.log('Invalid parameters: destination adapter cannot be found!');
1372
+ return void callback(EXIT_CODES.INVALID_ARGUMENTS);
1373
+ }
1374
+ objects
1375
+ .writeFile(adapt, destFilename, data, _err => {
1376
+ console.log(`File "${toRead}" stored as "${destFilename}"`);
1377
+ return void callback(EXIT_CODES.NO_ERROR);
1378
+ })
1379
+ .catch(e => console.error(`Cannot write file: ${e.message}`));
2455
1380
  }
2456
- case 'cert': {
2457
- const certCommand = new CLICert(commandOptions);
2458
- certCommand.execute(args);
2459
- break;
2460
- }
2461
- case 'compact': {
2462
- const compactCommand = new CLICompact(commandOptions);
2463
- compactCommand.execute(args);
2464
- break;
2465
- }
2466
- case 'plugin': {
2467
- const pluginCommand = new CLIPlugin(commandOptions);
2468
- pluginCommand.execute(args);
2469
- break;
2470
- }
2471
- case 'license': {
2472
- const file = args[0];
2473
- if (!file) {
2474
- console.warn(`Please specify the path to the license file or place license text directly!\n${tools.appName.toLowerCase()} license <license.file or license.text>`);
1381
+ else if (cmd === 'del' || cmd === 'rm' || cmd === 'unlink') {
1382
+ const toDelete = args[1];
1383
+ const parts = toDelete.replace(/\\/g, '/').split('/');
1384
+ let adapt = parts.shift();
1385
+ if (!adapt) {
1386
+ // leading slash
1387
+ adapt = parts.shift();
1388
+ }
1389
+ if (!adapt) {
1390
+ console.log('Invalid parameters: adapter cannot be found!');
2475
1391
  return void callback(EXIT_CODES.INVALID_ARGUMENTS);
2476
1392
  }
2477
- dbConnect(params, async ({ objects }) => {
2478
- const { License } = await import('./setup/setupLicense.js');
2479
- const license = new License({ objects });
2480
- try {
2481
- await license.setLicense(file);
2482
- console.log(`License updated.`);
2483
- return void callback();
1393
+ if (!parts.length) {
1394
+ console.log('Invalid parameters: file cannot be found: file not provided');
1395
+ return void callback(EXIT_CODES.INVALID_ARGUMENTS);
1396
+ }
1397
+ objects.unlink(adapt, parts.join('/'), null, err => {
1398
+ if (err) {
1399
+ console.error(err);
2484
1400
  }
2485
- catch (err) {
2486
- console.error(`Cannot update license: ${err.message}`);
2487
- return void callback(EXIT_CODES.CANNOT_UPDATE_LICENSE);
1401
+ else {
1402
+ console.log(`File "${toDelete}" was deleted`);
2488
1403
  }
1404
+ return void callback(EXIT_CODES.NO_ERROR);
2489
1405
  });
2490
- break;
2491
1406
  }
2492
- default: {
2493
- if (params.v || params.version) {
2494
- let pckg;
2495
- if (command) {
2496
- try {
2497
- pckg = require(`${tools.appName.toLowerCase()}.${command}/package.json`);
2498
- }
2499
- catch {
2500
- pckg = { version: `"${command}" not found` };
1407
+ else if (cmd === 'sync') {
1408
+ // Sync
1409
+ if (objectsDBType === 'redis') {
1410
+ console.log('File Sync is not available when database type "redis" is used.');
1411
+ return void callback(EXIT_CODES.INVALID_ARGUMENTS);
1412
+ }
1413
+ // @ts-expect-error todo look in depth how to handle
1414
+ if (!objects.syncFileDirectory || !objects.dirExists) {
1415
+ // functionality only exists in server class
1416
+ console.log('Please stop ioBroker before syncing files and only use this command on the ioBroker master host!');
1417
+ return void callback(EXIT_CODES.CONTROLLER_RUNNING);
1418
+ }
1419
+ // check meta.user
1420
+ try {
1421
+ const objExists = await objects.objectExists('meta.user');
1422
+ if (objExists) {
1423
+ // check if dir is missing
1424
+ // @ts-expect-error todo look in depth how to handle
1425
+ const dirExists = objects.dirExists('meta.user');
1426
+ if (!dirExists) {
1427
+ // create meta.user, so users see them as upload target
1428
+ await objects.mkdirAsync('meta.user');
1429
+ console.log('Successfully created "meta.user" directory');
2501
1430
  }
2502
1431
  }
2503
- else {
2504
- pckg = require(`@iobroker/js-controller-common/package.json`);
1432
+ }
1433
+ catch (err) {
1434
+ console.warn(`Could not create directory "meta.user": ${err.message}`);
1435
+ }
1436
+ try {
1437
+ // @ts-expect-error todo types needed for this one? but only exists sometimes..
1438
+ const { numberSuccess, notifications } = objects.syncFileDirectory(args[1]);
1439
+ console.log(`${numberSuccess} file(s) successfully synchronized with ioBroker storage`);
1440
+ if (notifications.length) {
1441
+ console.log('\nThe following notifications happened during sync: ');
1442
+ notifications.forEach((el) => console.log(`- ${el}`));
2505
1443
  }
2506
- console.log(pckg.version);
1444
+ return void callback(EXIT_CODES.NO_ERROR);
2507
1445
  }
2508
- else {
2509
- showHelp();
2510
- return void callback(EXIT_CODES.INVALID_ARGUMENTS);
1446
+ catch (err) {
1447
+ console.error(`Error on sync: ${err.message}. Partial content might have been synced.`);
1448
+ return void callback(EXIT_CODES.CANNOT_SYNC_FILES);
2511
1449
  }
2512
- return void callback();
1450
+ }
1451
+ else {
1452
+ console.log('Invalid parameters: write "file read /vis-2.0/main/img/picture.png /opt/picture/image.png" to read the file from DB and store it on disk');
1453
+ console.log('or "file write /opt/SOURCE/image.png /vis-2.0/main/DESTINATION/picture.png" to write the file into DB from disk');
1454
+ console.log('or "file rm /vis-2.0/main/img/picture.png" to delete the file in DB');
1455
+ return void callback(EXIT_CODES.INVALID_ARGUMENTS);
1456
+ }
1457
+ });
1458
+ }
1459
+ /**
1460
+ * Process the `uuid` CLI command: print the installation UUID
1461
+ *
1462
+ * @param options command options provided by the dispatcher
1463
+ */
1464
+ function processCommandUuid(options) {
1465
+ const { params, callback } = options;
1466
+ dbConnect(params, ({ objects }) => {
1467
+ objects.getObject('system.meta.uuid', (err, obj) => {
1468
+ if (err) {
1469
+ console.error(`Error: ${err.message}`);
1470
+ return void callback(EXIT_CODES.CANNOT_GET_UUID);
1471
+ }
1472
+ if (obj?.native) {
1473
+ console.log(obj.native.uuid);
1474
+ return void callback();
1475
+ }
1476
+ console.error('Error: no UUID found');
1477
+ return void callback(EXIT_CODES.CANNOT_GET_UUID);
1478
+ });
1479
+ });
1480
+ }
1481
+ /**
1482
+ * Process the `version` CLI command: show version of js-controller or a specified adapter
1483
+ *
1484
+ * @param options command options provided by the dispatcher
1485
+ */
1486
+ async function processCommandVersion(options) {
1487
+ const { params, callback } = options;
1488
+ const adapter = params.adapter;
1489
+ if (params.ignore) {
1490
+ try {
1491
+ const { objects } = await dbConnectAsync(false, params);
1492
+ await ignoreVersion({ adapterName: adapter, version: params.ignore, objects });
1493
+ }
1494
+ catch (e) {
1495
+ console.error(e.message);
1496
+ callback(e instanceof IoBrokerError ? e.code : EXIT_CODES.UNKNOWN_ERROR);
1497
+ return;
1498
+ }
1499
+ console.log(`Successfully ignored version "${params.ignore}" of adapter "${params.adapter}"!`);
1500
+ callback();
1501
+ return;
1502
+ }
1503
+ if (params.recognize) {
1504
+ try {
1505
+ const { objects } = await dbConnectAsync(false, params);
1506
+ await recognizeVersion({ adapterName: adapter, objects });
1507
+ }
1508
+ catch (e) {
1509
+ console.error(e.message);
1510
+ callback(e instanceof IoBrokerError ? e.code : EXIT_CODES.UNKNOWN_ERROR);
1511
+ }
1512
+ console.log(`Successfully recognized all versions of adapter "${params.adapter}" again!`);
1513
+ callback();
1514
+ return;
1515
+ }
1516
+ let packJson;
1517
+ if (adapter) {
1518
+ try {
1519
+ packJson = require(`${tools.appName.toLowerCase()}.${adapter}/package.json`);
1520
+ }
1521
+ catch {
1522
+ packJson = { version: `"${adapter}" not found` };
2513
1523
  }
2514
1524
  }
1525
+ else {
1526
+ packJson = require(`@iobroker/js-controller-common/package.json`);
1527
+ }
1528
+ console.log(packJson.version);
1529
+ return void callback();
1530
+ }
1531
+ /**
1532
+ * Process the `checklog` CLI command: trigger a logging check on all hosts
1533
+ *
1534
+ * @param options command options provided by the dispatcher
1535
+ */
1536
+ function processCommandCheckLog(options) {
1537
+ const { params, callback } = options;
1538
+ dbConnect(params, async ({ objects, states, isOffline, objectsDBType }) => {
1539
+ const hasLocalObjectsServer = await objectsDbHasServer(objectsDBType);
1540
+ if (isOffline && hasLocalObjectsServer) {
1541
+ console.log(`${tools.appName} is not running`);
1542
+ return void callback(EXIT_CODES.CONTROLLER_NOT_RUNNING);
1543
+ }
1544
+ console.log(`${tools.appName} is running`);
1545
+ objects.getObjectList({
1546
+ startkey: 'system.host.',
1547
+ endkey: `system.host.\u9999`,
1548
+ }, null, (err, res) => {
1549
+ if (!err && res?.rows.length) {
1550
+ for (const row of res.rows) {
1551
+ const parts = row.id.split('.');
1552
+ // ignore system.host.name.alive and so on
1553
+ if (parts.length === 3) {
1554
+ states
1555
+ .pushMessage(row.id, {
1556
+ command: 'checkLogging',
1557
+ message: null,
1558
+ from: 'console',
1559
+ })
1560
+ .catch(e => console.error(`Cannot push checkLogging message: ${e.message}`));
1561
+ }
1562
+ }
1563
+ }
1564
+ setTimeout(callback, 200);
1565
+ });
1566
+ });
2515
1567
  }
2516
1568
  const OBJECTS_THAT_CANNOT_BE_DELETED = [
2517
1569
  '0_userdata.0',
@@ -2572,9 +1624,8 @@ async function cleanDatabase(isDeleteDb) {
2572
1624
  const { objects } = await dbConnectAsync(false);
2573
1625
  if (isDeleteDb) {
2574
1626
  await objects.destroyDBAsync();
2575
- // Clean up states
2576
- const keysCount = await delStates();
2577
- return keysCount;
1627
+ // Clean up states and return keys count
1628
+ return await delStates();
2578
1629
  }
2579
1630
  // Clean only objects, not the views
2580
1631
  let ids = [];
@@ -2589,9 +1640,8 @@ async function cleanDatabase(isDeleteDb) {
2589
1640
  // ignore
2590
1641
  }
2591
1642
  await delObjects(ids);
2592
- // Clean up states
2593
- const keysCount = await delStates();
2594
- return keysCount;
1643
+ // Clean up states and return the key count
1644
+ return await delStates();
2595
1645
  }
2596
1646
  async function unsetup(params, callback) {
2597
1647
  const { objects } = await dbConnectAsync(false, params);
@@ -2645,12 +1695,11 @@ async function restartController() {
2645
1695
  child.unref();
2646
1696
  }
2647
1697
  /**
2648
- * Method which should be called from CLI to initialize the handling of all args
1698
+ * Method that should be called from CLI to initialize the handling of all args
2649
1699
  */
2650
1700
  export function execute() {
2651
1701
  // direct call
2652
1702
  const _yargs = initYargs();
2653
- // @ts-expect-error todo fix it
2654
1703
  const command = _yargs.argv._[0];
2655
1704
  const args = [];
2656
1705
  // skip interpreter, filename and command
@@ -2661,10 +1710,10 @@ export function execute() {
2661
1710
  }
2662
1711
  args.push(process.argv[i]);
2663
1712
  }
2664
- processCommand(command, args, _yargs.argv, exitApplicationSave);
1713
+ processCommand(command, args, _yargs.argv, exitApplicationSave).catch(e => console.error(`Cannot process command: ${e.message}`));
2665
1714
  }
2666
1715
  process.on('unhandledRejection', (e) => {
2667
1716
  console.error(`Uncaught Rejection: ${e.stack || e}`);
2668
- exitApplicationSave(EXIT_CODES.UNCAUGHT_EXCEPTION);
1717
+ return exitApplicationSave(EXIT_CODES.UNCAUGHT_EXCEPTION);
2669
1718
  });
2670
1719
  //# sourceMappingURL=setup.js.map