@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.
- package/build/cjs/lib/setup/setupBackup.d.ts +9 -3
- package/build/cjs/lib/setup/setupBackup.js +81 -61
- package/build/cjs/lib/setup/setupBackup.js.map +3 -3
- package/build/cjs/lib/setup.js +3 -3
- package/build/cjs/lib/setup.js.map +2 -2
- package/build/esm/lib/setup/setupBackup.d.ts +9 -3
- package/build/esm/lib/setup/setupBackup.d.ts.map +1 -1
- package/build/esm/lib/setup/setupBackup.js +95 -65
- package/build/esm/lib/setup/setupBackup.js.map +1 -1
- package/build/esm/lib/setup.js +3 -3
- package/build/esm/lib/setup.js.map +1 -1
- package/build/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +4 -4
|
@@ -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
|
|
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
|
|
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
|
|
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)}
|
|
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.
|
|
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
|
-
|
|
472
|
-
|
|
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
|
-
|
|
493
|
-
|
|
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, "
|
|
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
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
750
|
+
const regEx = new RegExp(this.BACKUP_POSTFIX, "i");
|
|
738
751
|
if (!regEx.test(name)) {
|
|
739
|
-
name +=
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
876
|
+
const regEx = new RegExp(this.BACKUP_POSTFIX, "i");
|
|
857
877
|
if (!regEx.test(name)) {
|
|
858
|
-
name +=
|
|
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, "
|
|
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, "
|
|
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();
|