@iobroker/js-controller-cli 7.0.0 → 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,9 +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)
144
+ *
145
+ * @param noConfig if the backup does not contain a `config.json` (used by setup custom migration)
142
146
  */
143
- private _validateBackupAfterCreation;
147
+ private _validateTempDirectory;
144
148
  /**
145
149
  * Validate that the created JSONL files in the temporary directories are parseable
146
150
  */
@@ -150,7 +154,11 @@ export declare class BackupRestore {
150
154
  *
151
155
  * @param _name - index or name of the backup
152
156
  */
153
- 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;
154
162
  /**
155
163
  * Checks a directory for json files and validates them, steps down recursive in subdirectories
156
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) {
@@ -179,10 +180,12 @@ class BackupRestore {
179
180
  }
180
181
  });
181
182
  }
182
- async createBackup(name, noConfig) {
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();
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,16 +664,22 @@ class BackupRestore {
655
664
  }
656
665
  return result;
657
666
  }
658
- async _validateBackupAfterCreation() {
667
+ async _validateTempDirectory(noConfig = false) {
659
668
  const backupBaseDir = import_node_path.default.join(this.tmpDir, "backup");
660
- await import_fs_extra.default.readJSON(import_node_path.default.join(backupBaseDir, "config.json"));
669
+ if (!noConfig) {
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`);
672
+ }
661
673
  if (!await import_fs_extra.default.pathExists(import_node_path.default.join(backupBaseDir, "objects.jsonl"))) {
662
674
  throw new Error("Backup does not contain valid objects");
663
675
  }
676
+ console.log(`host.${this.hostname} "objects.jsonl" exists`);
664
677
  if (!await import_fs_extra.default.pathExists(import_node_path.default.join(backupBaseDir, "states.jsonl"))) {
665
678
  throw new Error("Backup does not contain valid states");
666
679
  }
680
+ console.log(`host.${this.hostname} "states.jsonl" exists`);
667
681
  await this._validateDatabaseFiles();
682
+ console.log(`host.${this.hostname} JSONL lines are valid`);
668
683
  try {
669
684
  this._checkDirectory(import_node_path.default.join(backupBaseDir, "files"));
670
685
  } catch (e) {
@@ -695,7 +710,7 @@ class BackupRestore {
695
710
  }
696
711
  await statesFd.close();
697
712
  }
698
- validateBackup(_name) {
713
+ async validateBackup(_name) {
699
714
  let backups;
700
715
  let name = typeof _name === "number" ? _name.toString() : _name;
701
716
  if (!name) {
@@ -704,12 +719,12 @@ class BackupRestore {
704
719
  if (backups.length) {
705
720
  console.log("Please specify one of the backup names:");
706
721
  for (const t in backups) {
707
- 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}`);
708
723
  }
709
724
  } else {
710
725
  console.warn(`No backups found. Create a backup, using "${import_js_controller_common.tools.appName} backup" first`);
711
726
  }
712
- 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 });
713
728
  }
714
729
  if (parseInt(name, 10).toString() === name.toString()) {
715
730
  backups = this.listBackups();
@@ -720,21 +735,21 @@ class BackupRestore {
720
735
  if (backups.length) {
721
736
  console.log("Please specify one of the backup names:");
722
737
  for (const t in backups) {
723
- 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}`);
724
739
  }
725
740
  } else {
726
741
  console.log(`No existing backups. Create a backup, using "${import_js_controller_common.tools.appName} backup" first`);
727
742
  }
728
- 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 });
729
744
  }
730
745
  console.log(`host.${this.hostname} Using backup file ${name}`);
731
746
  }
732
747
  name = name.toString().replace(/\\/g, "/");
733
748
  if (!name.includes("/")) {
734
749
  name = BackupRestore.getBackupDir() + name;
735
- const regEx = new RegExp(`_backup${import_js_controller_common.tools.appName}`, "i");
750
+ const regEx = new RegExp(this.BACKUP_POSTFIX, "i");
736
751
  if (!regEx.test(name)) {
737
- name += `_backup${import_js_controller_common.tools.appName}`;
752
+ name += this.BACKUP_POSTFIX;
738
753
  }
739
754
  if (!name.match(/\.tar\.gz$/i)) {
740
755
  name += ".tar.gz";
@@ -742,53 +757,60 @@ class BackupRestore {
742
757
  }
743
758
  if (!import_fs_extra.default.existsSync(name)) {
744
759
  console.error(`host.${this.hostname} Cannot find ${name}`);
745
- 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 });
746
761
  }
747
762
  if (import_fs_extra.default.existsSync(`${this.tmpDir}/backup/backup.json`)) {
748
763
  import_fs_extra.default.unlinkSync(`${this.tmpDir}/backup/backup.json`);
749
764
  }
750
- return new Promise((resolve) => {
751
- import_tar.default.extract({
765
+ try {
766
+ await import_tar.default.extract({
752
767
  file: name,
753
768
  cwd: this.tmpDir
754
- }, void 0, (err) => {
755
- if (err) {
756
- console.error(`host.${this.hostname} Cannot extract from file "${name}": ${err.message}`);
757
- return void this.processExit(import_js_controller_common.EXIT_CODES.INVALID_ARGUMENTS);
758
- }
759
- if (!import_fs_extra.default.existsSync(`${this.tmpDir}/backup/backup.json`)) {
760
- console.error(`host.${this.hostname} Validation failed. Cannot find extracted file from file "${this.tmpDir}/backup/backup.json"`);
761
- return void this.processExit(import_js_controller_common.EXIT_CODES.CANNOT_EXTRACT_FROM_ZIP);
762
- }
763
- console.log(`host.${this.hostname} Starting validation ...`);
764
- let backupJSON;
765
- try {
766
- backupJSON = import_fs_extra.default.readJSONSync(`${this.tmpDir}/backup/backup.json`);
767
- } catch (err2) {
768
- console.error(`host.${this.hostname} Backup corrupted. Backup ${name} does not contain a valid backup.json file: ${err2.message}`);
769
- this.removeTempBackupDir();
770
- return void this.processExit(import_js_controller_common.EXIT_CODES.CANNOT_EXTRACT_FROM_ZIP);
771
- }
772
- if (!backupJSON || !backupJSON.objects || !backupJSON.objects.length) {
773
- console.error(`host.${this.hostname} Backup corrupted. Backup does not contain valid objects`);
774
- try {
775
- this.removeTempBackupDir();
776
- } catch (e) {
777
- console.error(`host.${this.hostname} Cannot clear temporary backup directory: ${e.message}`);
778
- }
779
- return void this.processExit(import_js_controller_common.EXIT_CODES.CANNOT_EXTRACT_FROM_ZIP);
780
- }
781
- console.log(`host.${this.hostname} backup.json OK`);
782
- try {
783
- this._checkDirectory(`${this.tmpDir}/backup/files`, true);
784
- this.removeTempBackupDir();
785
- resolve();
786
- } catch (err2) {
787
- console.error(`host.${this.hostname} Backup corrupted: ${err2.message}`);
788
- return void this.processExit(import_js_controller_common.EXIT_CODES.CANNOT_EXTRACT_FROM_ZIP);
789
- }
790
769
  });
791
- });
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
+ }
792
814
  }
793
815
  _checkDirectory(path2, verbose = false) {
794
816
  if (import_fs_extra.default.existsSync(path2)) {
@@ -822,7 +844,7 @@ class BackupRestore {
822
844
  backups = this.listBackups();
823
845
  backups.sort((a, b) => b > a ? 1 : b === a ? 0 : -1);
824
846
  if (backups.length) {
825
- 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}`));
826
848
  } else {
827
849
  console.warn("No backups found");
828
850
  }
@@ -842,7 +864,7 @@ class BackupRestore {
842
864
  console.log("No matching backup found");
843
865
  if (backups.length) {
844
866
  console.log("Please specify one of the backup names:");
845
- 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}`));
846
868
  }
847
869
  } else {
848
870
  console.log(`host.${this.hostname} Using backup file ${name}`);
@@ -851,9 +873,9 @@ class BackupRestore {
851
873
  name = name.toString().replace(/\\/g, "/");
852
874
  if (!name.includes("/")) {
853
875
  name = BackupRestore.getBackupDir() + name;
854
- const regEx = new RegExp(`_backup${import_js_controller_common.tools.appName}`, "i");
876
+ const regEx = new RegExp(this.BACKUP_POSTFIX, "i");
855
877
  if (!regEx.test(name)) {
856
- name += `_backup${import_js_controller_common.tools.appName}`;
878
+ name += this.BACKUP_POSTFIX;
857
879
  }
858
880
  if (!name.match(/\.tar\.gz$/i)) {
859
881
  name += ".tar.gz";
@@ -876,8 +898,8 @@ class BackupRestore {
876
898
  console.error(`host.${this.hostname} Cannot extract from file "${name}": ${e.message}`);
877
899
  return { exitCode: import_js_controller_common.EXIT_CODES.CANNOT_EXTRACT_FROM_ZIP, objects: this.objects, states: this.states };
878
900
  }
879
- 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"))) {
880
- 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")}"`);
881
903
  return { exitCode: import_js_controller_common.EXIT_CODES.CANNOT_EXTRACT_FROM_ZIP, objects: this.objects, states: this.states };
882
904
  }
883
905
  await import_cliProcess.CLIProcess.stopJSController();