@iobroker/js-controller-cli 7.0.1 → 7.0.2-alpha.0-20241026-8055a2557

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.
@@ -47,6 +47,8 @@ export declare class BackupRestore {
47
47
  private readonly HOSTNAME_PLACEHOLDER_REPLACE;
48
48
  /** Regex to replace all occurrences of the HOSTNAME_PLACEHOLDER */
49
49
  private readonly HOSTNAME_PLACEHOLDER_REGEX;
50
+ /** Postfix for backup name */
51
+ private readonly BACKUP_POSTFIX;
50
52
  constructor(options: CLIBackupRestoreOptions);
51
53
  private _copyFile;
52
54
  copyDir(id: string, srcPath: string, destPath: string): Promise<void>;
@@ -138,11 +140,11 @@ export declare class BackupRestore {
138
140
  */
139
141
  listBackups(): string[];
140
142
  /**
141
- * Validates the backup.json and all json files inside the backup after (in temporary directory), here we only abort if backup.json is corrupted
143
+ * Validates a JSONL-style backup and all json files inside the backup (in temporary directory)
142
144
  *
143
145
  * @param noConfig if the backup does not contain a `config.json` (used by setup custom migration)
144
146
  */
145
- private _validateBackupAfterCreation;
147
+ private _validateTempDirectory;
146
148
  /**
147
149
  * Validate that the created JSONL files in the temporary directories are parseable
148
150
  */
@@ -152,7 +154,11 @@ export declare class BackupRestore {
152
154
  *
153
155
  * @param _name - index or name of the backup
154
156
  */
155
- validateBackup(_name: string | number): Promise<void> | undefined;
157
+ validateBackup(_name: string | number): Promise<void>;
158
+ /**
159
+ * Validate an unpacked legacy backup in the temporary directory
160
+ */
161
+ private _validateLegacyTempDir;
156
162
  /**
157
163
  * Checks a directory for json files and validates them, steps down recursive in subdirectories
158
164
  *
@@ -55,6 +55,7 @@ class BackupRestore {
55
55
  HOSTNAME_PLACEHOLDER = "$$__hostname__$$";
56
56
  HOSTNAME_PLACEHOLDER_REPLACE = "$$$$__hostname__$$$$";
57
57
  HOSTNAME_PLACEHOLDER_REGEX = /\$\$__hostname__\$\$/g;
58
+ BACKUP_POSTFIX = `_backup${import_js_controller_common.tools.appNameLowerCase}`;
58
59
  constructor(options) {
59
60
  options = options || {};
60
61
  if (!options.states) {
@@ -182,7 +183,9 @@ class BackupRestore {
182
183
  async createBackup(name, noConfig = false) {
183
184
  if (!name) {
184
185
  const d = new Date();
185
- name = `${d.getFullYear()}_${`0${d.getMonth() + 1}`.slice(-2)}_${`0${d.getDate()}`.slice(-2)}-${`0${d.getHours()}`.slice(-2)}_${`0${d.getMinutes()}`.slice(-2)}_${`0${d.getSeconds()}`.slice(-2)}_backup${import_js_controller_common.tools.appName}`;
186
+ name = `${d.getFullYear()}_${`0${d.getMonth() + 1}`.slice(-2)}_${`0${d.getDate()}`.slice(-2)}-${`0${d.getHours()}`.slice(-2)}_${`0${d.getMinutes()}`.slice(-2)}_${`0${d.getSeconds()}`.slice(-2)}${this.BACKUP_POSTFIX}`;
187
+ } else if (!name.endsWith(this.BACKUP_POSTFIX) && !name.endsWith(`${this.BACKUP_POSTFIX}.tar.gz`)) {
188
+ name += this.BACKUP_POSTFIX;
186
189
  }
187
190
  name = name.toString().replace(/\\/g, "/");
188
191
  if (!name.includes("/")) {
@@ -260,7 +263,7 @@ class BackupRestore {
260
263
  }
261
264
  console.log(`host.${hostname} Validating backup ...`);
262
265
  try {
263
- await this._validateBackupAfterCreation(noConfig);
266
+ await this._validateTempDirectory(noConfig);
264
267
  console.log(`host.${hostname} The backup is valid!`);
265
268
  return await this._packBackup(name);
266
269
  } catch (e) {
@@ -468,8 +471,12 @@ class BackupRestore {
468
471
  const { force, dontDeleteAdapters } = options;
469
472
  const hostname = import_js_controller_common.tools.getHostName();
470
473
  const backupBaseDir = import_node_path.default.join(this.tmpDir, "backup");
471
- const config = await import_fs_extra.default.readJSON(import_node_path.default.join(backupBaseDir, "config.json"));
472
- const backupHostName = config.system?.hostname || hostname;
474
+ let backupHostName = hostname;
475
+ let config;
476
+ if (await import_fs_extra.default.pathExists(import_node_path.default.join(backupBaseDir, "config.json"))) {
477
+ config = await import_fs_extra.default.readJSON(import_node_path.default.join(backupBaseDir, "config.json"));
478
+ backupHostName = config.system?.hostname || hostname;
479
+ }
473
480
  const objFd = await (0, import_promises.open)(import_node_path.default.join(backupBaseDir, "objects.jsonl"));
474
481
  const rlObj = objFd.readLines();
475
482
  const hostObjArr = [];
@@ -489,8 +496,10 @@ class BackupRestore {
489
496
  if (!dontDeleteAdapters) {
490
497
  await this._removeAllAdapters();
491
498
  }
492
- import_fs_extra.default.writeFileSync(import_js_controller_common.tools.getConfigFileName(), JSON.stringify(config, null, 2));
493
- await this.connectToNewDatabase(config);
499
+ if (config) {
500
+ import_fs_extra.default.writeFileSync(import_js_controller_common.tools.getConfigFileName(), JSON.stringify(config, null, 2));
501
+ await this.connectToNewDatabase(config);
502
+ }
494
503
  console.log(`host.${hostname} Clear all objects and states...`);
495
504
  await this.cleanDatabase(false);
496
505
  console.log(`host.${hostname} done.`);
@@ -521,7 +530,7 @@ class BackupRestore {
521
530
  async _restoreAfterStop(options) {
522
531
  const { force, restartOnFinish, dontDeleteAdapters } = options;
523
532
  const backupBaseDir = import_node_path.default.join(this.tmpDir, "backup");
524
- const isJsonl = await import_fs_extra.default.pathExists(import_node_path.default.join(backupBaseDir, "config.json"));
533
+ const isJsonl = await import_fs_extra.default.pathExists(import_node_path.default.join(backupBaseDir, "objects.jsonl"));
525
534
  if (isJsonl) {
526
535
  const exitCode = await this._restoreJsonlBackup(options);
527
536
  if (exitCode !== import_js_controller_common.EXIT_CODES.NO_ERROR) {
@@ -655,18 +664,22 @@ class BackupRestore {
655
664
  }
656
665
  return result;
657
666
  }
658
- async _validateBackupAfterCreation(noConfig = false) {
667
+ async _validateTempDirectory(noConfig = false) {
659
668
  const backupBaseDir = import_node_path.default.join(this.tmpDir, "backup");
660
669
  if (!noConfig) {
661
670
  await import_fs_extra.default.readJSON(import_node_path.default.join(backupBaseDir, "config.json"));
671
+ console.log(`host.${this.hostname} "config.json" is valid`);
662
672
  }
663
673
  if (!await import_fs_extra.default.pathExists(import_node_path.default.join(backupBaseDir, "objects.jsonl"))) {
664
674
  throw new Error("Backup does not contain valid objects");
665
675
  }
676
+ console.log(`host.${this.hostname} "objects.jsonl" exists`);
666
677
  if (!await import_fs_extra.default.pathExists(import_node_path.default.join(backupBaseDir, "states.jsonl"))) {
667
678
  throw new Error("Backup does not contain valid states");
668
679
  }
680
+ console.log(`host.${this.hostname} "states.jsonl" exists`);
669
681
  await this._validateDatabaseFiles();
682
+ console.log(`host.${this.hostname} JSONL lines are valid`);
670
683
  try {
671
684
  this._checkDirectory(import_node_path.default.join(backupBaseDir, "files"));
672
685
  } catch (e) {
@@ -697,7 +710,7 @@ class BackupRestore {
697
710
  }
698
711
  await statesFd.close();
699
712
  }
700
- validateBackup(_name) {
713
+ async validateBackup(_name) {
701
714
  let backups;
702
715
  let name = typeof _name === "number" ? _name.toString() : _name;
703
716
  if (!name) {
@@ -706,12 +719,12 @@ class BackupRestore {
706
719
  if (backups.length) {
707
720
  console.log("Please specify one of the backup names:");
708
721
  for (const t in backups) {
709
- console.log(`${backups[t]} or ${backups[t].replace(`_backup${import_js_controller_common.tools.appName}.tar.gz`, "")} or ${t}`);
722
+ console.log(`${backups[t]} or ${backups[t].replace(`${this.BACKUP_POSTFIX}.tar.gz`, "")} or ${t}`);
710
723
  }
711
724
  } else {
712
725
  console.warn(`No backups found. Create a backup, using "${import_js_controller_common.tools.appName} backup" first`);
713
726
  }
714
- return void this.processExit(import_js_controller_common.EXIT_CODES.INVALID_ARGUMENTS);
727
+ throw new import_customError.IoBrokerError({ message: "Backup not found", code: import_js_controller_common.EXIT_CODES.INVALID_ARGUMENTS });
715
728
  }
716
729
  if (parseInt(name, 10).toString() === name.toString()) {
717
730
  backups = this.listBackups();
@@ -722,21 +735,21 @@ class BackupRestore {
722
735
  if (backups.length) {
723
736
  console.log("Please specify one of the backup names:");
724
737
  for (const t in backups) {
725
- console.log(`${backups[t]} or ${backups[t].replace(`_backup${import_js_controller_common.tools.appName}.tar.gz`, "")} or ${t}`);
738
+ console.log(`${backups[t]} or ${backups[t].replace(`${this.BACKUP_POSTFIX}.tar.gz`, "")} or ${t}`);
726
739
  }
727
740
  } else {
728
741
  console.log(`No existing backups. Create a backup, using "${import_js_controller_common.tools.appName} backup" first`);
729
742
  }
730
- return void this.processExit(import_js_controller_common.EXIT_CODES.INVALID_ARGUMENTS);
743
+ throw new import_customError.IoBrokerError({ message: "Backup not found", code: import_js_controller_common.EXIT_CODES.INVALID_ARGUMENTS });
731
744
  }
732
745
  console.log(`host.${this.hostname} Using backup file ${name}`);
733
746
  }
734
747
  name = name.toString().replace(/\\/g, "/");
735
748
  if (!name.includes("/")) {
736
749
  name = BackupRestore.getBackupDir() + name;
737
- const regEx = new RegExp(`_backup${import_js_controller_common.tools.appName}`, "i");
750
+ const regEx = new RegExp(this.BACKUP_POSTFIX, "i");
738
751
  if (!regEx.test(name)) {
739
- name += `_backup${import_js_controller_common.tools.appName}`;
752
+ name += this.BACKUP_POSTFIX;
740
753
  }
741
754
  if (!name.match(/\.tar\.gz$/i)) {
742
755
  name += ".tar.gz";
@@ -744,53 +757,60 @@ class BackupRestore {
744
757
  }
745
758
  if (!import_fs_extra.default.existsSync(name)) {
746
759
  console.error(`host.${this.hostname} Cannot find ${name}`);
747
- return void this.processExit(import_js_controller_common.EXIT_CODES.INVALID_ARGUMENTS);
760
+ throw new import_customError.IoBrokerError({ message: "Backup not found", code: import_js_controller_common.EXIT_CODES.INVALID_ARGUMENTS });
748
761
  }
749
762
  if (import_fs_extra.default.existsSync(`${this.tmpDir}/backup/backup.json`)) {
750
763
  import_fs_extra.default.unlinkSync(`${this.tmpDir}/backup/backup.json`);
751
764
  }
752
- return new Promise((resolve) => {
753
- import_tar.default.extract({
765
+ try {
766
+ await import_tar.default.extract({
754
767
  file: name,
755
768
  cwd: this.tmpDir
756
- }, void 0, (err) => {
757
- if (err) {
758
- console.error(`host.${this.hostname} Cannot extract from file "${name}": ${err.message}`);
759
- return void this.processExit(import_js_controller_common.EXIT_CODES.INVALID_ARGUMENTS);
760
- }
761
- if (!import_fs_extra.default.existsSync(`${this.tmpDir}/backup/backup.json`)) {
762
- console.error(`host.${this.hostname} Validation failed. Cannot find extracted file from file "${this.tmpDir}/backup/backup.json"`);
763
- return void this.processExit(import_js_controller_common.EXIT_CODES.CANNOT_EXTRACT_FROM_ZIP);
764
- }
765
- console.log(`host.${this.hostname} Starting validation ...`);
766
- let backupJSON;
767
- try {
768
- backupJSON = import_fs_extra.default.readJSONSync(`${this.tmpDir}/backup/backup.json`);
769
- } catch (err2) {
770
- console.error(`host.${this.hostname} Backup corrupted. Backup ${name} does not contain a valid backup.json file: ${err2.message}`);
771
- this.removeTempBackupDir();
772
- return void this.processExit(import_js_controller_common.EXIT_CODES.CANNOT_EXTRACT_FROM_ZIP);
773
- }
774
- if (!backupJSON || !backupJSON.objects || !backupJSON.objects.length) {
775
- console.error(`host.${this.hostname} Backup corrupted. Backup does not contain valid objects`);
776
- try {
777
- this.removeTempBackupDir();
778
- } catch (e) {
779
- console.error(`host.${this.hostname} Cannot clear temporary backup directory: ${e.message}`);
780
- }
781
- return void this.processExit(import_js_controller_common.EXIT_CODES.CANNOT_EXTRACT_FROM_ZIP);
782
- }
783
- console.log(`host.${this.hostname} backup.json OK`);
784
- try {
785
- this._checkDirectory(`${this.tmpDir}/backup/files`, true);
786
- this.removeTempBackupDir();
787
- resolve();
788
- } catch (err2) {
789
- console.error(`host.${this.hostname} Backup corrupted: ${err2.message}`);
790
- return void this.processExit(import_js_controller_common.EXIT_CODES.CANNOT_EXTRACT_FROM_ZIP);
791
- }
792
769
  });
793
- });
770
+ } catch (e) {
771
+ const errMessage = `Cannot extract from file "${name}": ${e.message}`;
772
+ console.error(`host.${this.hostname} ${errMessage}`);
773
+ throw new import_customError.IoBrokerError({ message: "Backup not found", code: import_js_controller_common.EXIT_CODES.INVALID_ARGUMENTS });
774
+ }
775
+ try {
776
+ if (import_fs_extra.default.existsSync(import_node_path.default.join(this.tmpDir, "backup", "backup.json"))) {
777
+ this._validateLegacyTempDir();
778
+ } else {
779
+ await this._validateTempDirectory();
780
+ }
781
+ } catch (e) {
782
+ console.error(`host.${this.hostname} ${e.message}`);
783
+ try {
784
+ this.removeTempBackupDir();
785
+ } catch (e2) {
786
+ console.error(`host.${this.hostname} Cannot clear temporary backup directory: ${e2.message}`);
787
+ }
788
+ throw new import_customError.IoBrokerError({ message: e.message, code: import_js_controller_common.EXIT_CODES.CANNOT_EXTRACT_FROM_ZIP });
789
+ }
790
+ try {
791
+ this.removeTempBackupDir();
792
+ } catch (e) {
793
+ console.error(`host.${this.hostname} Cannot clear temporary backup directory: ${e.message}`);
794
+ throw new import_customError.IoBrokerError({ message: e.message, code: import_js_controller_common.EXIT_CODES.CANNOT_EXTRACT_FROM_ZIP });
795
+ }
796
+ }
797
+ _validateLegacyTempDir() {
798
+ console.log(`host.${this.hostname} Starting validation ...`);
799
+ let backupJSON;
800
+ try {
801
+ backupJSON = import_fs_extra.default.readJSONSync(`${this.tmpDir}/backup/backup.json`);
802
+ } catch (e) {
803
+ throw new Error(`Backup corrupted. Backup does not contain a valid backup.json file: ${e.message}`);
804
+ }
805
+ if (!backupJSON || !backupJSON.objects || !backupJSON.objects.length) {
806
+ throw new Error(`host.${this.hostname} Backup corrupted. Backup does not contain valid objects`);
807
+ }
808
+ console.log(`host.${this.hostname} backup.json OK`);
809
+ try {
810
+ this._checkDirectory(`${this.tmpDir}/backup/files`, true);
811
+ } catch (e) {
812
+ throw new Error(`Backup corrupted: ${e.message}`);
813
+ }
794
814
  }
795
815
  _checkDirectory(path2, verbose = false) {
796
816
  if (import_fs_extra.default.existsSync(path2)) {
@@ -824,7 +844,7 @@ class BackupRestore {
824
844
  backups = this.listBackups();
825
845
  backups.sort((a, b) => b > a ? 1 : b === a ? 0 : -1);
826
846
  if (backups.length) {
827
- backups.forEach((backup, i) => console.log(`${backup} or ${backup.replace(`_backup${import_js_controller_common.tools.appName}.tar.gz`, "")} or ${i}`));
847
+ backups.forEach((backup, i) => console.log(`${backup} or ${backup.replace(`${this.BACKUP_POSTFIX}.tar.gz`, "")} or ${i}`));
828
848
  } else {
829
849
  console.warn("No backups found");
830
850
  }
@@ -844,7 +864,7 @@ class BackupRestore {
844
864
  console.log("No matching backup found");
845
865
  if (backups.length) {
846
866
  console.log("Please specify one of the backup names:");
847
- backups.forEach((backup, i) => console.log(`${backup} or ${backup.replace(`_backup${import_js_controller_common.tools.appName}.tar.gz`, "")} or ${i}`));
867
+ backups.forEach((backup, i) => console.log(`${backup} or ${backup.replace(`${this.BACKUP_POSTFIX}.tar.gz`, "")} or ${i}`));
848
868
  }
849
869
  } else {
850
870
  console.log(`host.${this.hostname} Using backup file ${name}`);
@@ -853,9 +873,9 @@ class BackupRestore {
853
873
  name = name.toString().replace(/\\/g, "/");
854
874
  if (!name.includes("/")) {
855
875
  name = BackupRestore.getBackupDir() + name;
856
- const regEx = new RegExp(`_backup${import_js_controller_common.tools.appName}`, "i");
876
+ const regEx = new RegExp(this.BACKUP_POSTFIX, "i");
857
877
  if (!regEx.test(name)) {
858
- name += `_backup${import_js_controller_common.tools.appName}`;
878
+ name += this.BACKUP_POSTFIX;
859
879
  }
860
880
  if (!name.match(/\.tar\.gz$/i)) {
861
881
  name += ".tar.gz";
@@ -878,8 +898,8 @@ class BackupRestore {
878
898
  console.error(`host.${this.hostname} Cannot extract from file "${name}": ${e.message}`);
879
899
  return { exitCode: import_js_controller_common.EXIT_CODES.CANNOT_EXTRACT_FROM_ZIP, objects: this.objects, states: this.states };
880
900
  }
881
- if (!await import_fs_extra.default.pathExists(import_node_path.default.join(backupBasePath, "backup.json")) && !await import_fs_extra.default.pathExists(import_node_path.default.join(backupBasePath, "config.json"))) {
882
- console.error(`host.${this.hostname} Cannot find extracted file "${import_node_path.default.join(backupBasePath, "backup.json")}" or "${import_node_path.default.join(backupBasePath, "config.json")}"`);
901
+ if (!await import_fs_extra.default.pathExists(import_node_path.default.join(backupBasePath, "backup.json")) && !await import_fs_extra.default.pathExists(import_node_path.default.join(backupBasePath, "objects.jsonl"))) {
902
+ console.error(`host.${this.hostname} Cannot find extracted file "${import_node_path.default.join(backupBasePath, "backup.json")}" or "${import_node_path.default.join(backupBasePath, "objects.jsonl")}"`);
883
903
  return { exitCode: import_js_controller_common.EXIT_CODES.CANNOT_EXTRACT_FROM_ZIP, objects: this.objects, states: this.states };
884
904
  }
885
905
  await import_cliProcess.CLIProcess.stopJSController();