@abtnode/core 1.8.65-beta-5405baf2 → 1.8.65-beta-bfcc12ce

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.
@@ -11,6 +11,14 @@ const logger = require('@abtnode/logger')('@abtnode/core:install-from-backup');
11
11
 
12
12
  const { validateBlocklet, checkDuplicateAppSk, getAppDirs } = require('../../../util/blocklet');
13
13
 
14
+ /**
15
+ *
16
+ * @param {{
17
+ * manager: import('../disk'),
18
+ * states: import('../../../states/index')
19
+ * }} param0
20
+ * @returns
21
+ */
14
22
  module.exports = async ({ url, blockletSecretKey, moveDir, context = {}, states, manager } = {}) => {
15
23
  // TODO: support more url schema feature (http, did-spaces)
16
24
  if (!url.startsWith('file://')) {
@@ -24,11 +32,7 @@ module.exports = async ({ url, blockletSecretKey, moveDir, context = {}, states,
24
32
  }
25
33
 
26
34
  // parse data from source dir
27
-
28
35
  const srcBundleDirs = await getAppDirs(path.join(dir, 'blocklets'));
29
- if (!srcBundleDirs.length) {
30
- throw new Error(`bundle dirs does not found in ${dir}`);
31
- }
32
36
 
33
37
  const srcDataDir = path.join(dir, 'data');
34
38
 
@@ -61,6 +65,7 @@ module.exports = async ({ url, blockletSecretKey, moveDir, context = {}, states,
61
65
 
62
66
  // FIXME: meta.did and meta.name should be dynamic created when installing multiple blocklet is supported
63
67
  const existState = await states.blocklet.hasBlocklet(did);
68
+
64
69
  if (existState) {
65
70
  logger.error('blocklet is already exist', { did });
66
71
  throw new Error('blocklet is already exist');
@@ -71,6 +76,8 @@ module.exports = async ({ url, blockletSecretKey, moveDir, context = {}, states,
71
76
  const skConfig = extra.configs.find((x) => x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SK);
72
77
  if (skConfig) {
73
78
  skConfig.value = blockletSecretKey;
79
+ skConfig.secure = true;
80
+ skConfig.shared = false;
74
81
  } else {
75
82
  extra.configs.push({
76
83
  key: BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SK,
@@ -131,6 +138,8 @@ module.exports = async ({ url, blockletSecretKey, moveDir, context = {}, states,
131
138
  logger.info(`bundle is ${moveDir ? 'moved' : 'copied'} successfully`, { installDir });
132
139
  })
133
140
  );
141
+ // 从 store 下载 blocklet
142
+ await manager.blockletDownloader.download(state, { skipCheckIntegrity: true });
134
143
 
135
144
  // FIXME same as copy extra
136
145
  const dataDir = path.join(manager.dataDirs.data, appName);
@@ -0,0 +1,41 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+
4
+ const DIR_NAME = 'rollback-cache';
5
+ const security = require('@abtnode/util/lib/security');
6
+
7
+ class RollbackCache {
8
+ constructor({ dir, dek }) {
9
+ this.dir = dir;
10
+ this.dek = dek;
11
+ }
12
+
13
+ async backup({ did, action, oldBlocklet }) {
14
+ const file = this._getFile(did);
15
+ const data = this.dek ? security.encrypt(JSON.stringify(oldBlocklet), did, this.dek) : oldBlocklet;
16
+ await fs.outputJSON(file, { action, oldBlocklet: data });
17
+ }
18
+
19
+ async remove({ did }) {
20
+ const file = this._getFile(did);
21
+ await fs.remove(file);
22
+ }
23
+
24
+ async restore({ did }) {
25
+ const file = this._getFile(did);
26
+ if (fs.existsSync(file)) {
27
+ const data = await fs.readJSON(file);
28
+ if (data?.oldBlocklet && this.dek) {
29
+ data.oldBlocklet = JSON.parse(security.decrypt(data.oldBlocklet, did, this.dek));
30
+ }
31
+ return data;
32
+ }
33
+ return null;
34
+ }
35
+
36
+ _getFile(did) {
37
+ return path.join(this.dir, DIR_NAME, `${did}.json`);
38
+ }
39
+ }
40
+
41
+ module.exports = RollbackCache;
@@ -75,6 +75,10 @@ class BlockletExtrasBackup extends BaseBackup {
75
75
  * @memberof BlockletExtrasBackup
76
76
  */
77
77
  useBlockletEncryptConfigs(configs) {
78
+ if (isEmpty(configs)) {
79
+ return;
80
+ }
81
+
78
82
  // 准备加解密所需的参数
79
83
  // @see: https://github.com/ArcBlock/blocklet-server/blob/f561ba7290285f2e23dccb6d5323eb4d43c3cc3e/core/state/lib/index.js#L59
80
84
  const dek = readFileSync(join(this.serverDataDir, '.sock'));
@@ -1,7 +1,8 @@
1
- const { removeSync, existsSync, ensureDirSync, createWriteStream } = require('fs-extra');
2
- const { join, dirname } = require('path');
3
- const archiver = require('archiver');
1
+ const { join } = require('path');
2
+ const validUrl = require('valid-url');
4
3
  const { BaseBackup } = require('./base');
4
+ const { dirToZip } = require('../utils/zip');
5
+ const { compareAndMove } = require('../utils/hash');
5
6
 
6
7
  class BlockletsBackup extends BaseBackup {
7
8
  /**
@@ -13,21 +14,25 @@ class BlockletsBackup extends BaseBackup {
13
14
  const blockletMetas = this.getBlockletMetas(this.blocklet);
14
15
  const serverBlockletsDir = join(this.serverDataDir, 'blocklets');
15
16
 
17
+ const blockletMetasFromLocal = blockletMetas.filter(
18
+ (b) => !validUrl.isHttpUri(b.tarball) && !validUrl.isHttpsUri(b.tarball)
19
+ );
20
+
16
21
  const dirs = [];
17
- for (const blockletMeta of blockletMetas) {
22
+ for (const blockletMeta of blockletMetasFromLocal) {
18
23
  const sourceDir = join(serverBlockletsDir, blockletMeta.name, blockletMeta.version);
19
- const destDir = join(blockletMeta.name, blockletMeta.version);
20
- dirs.push({ sourceDir, destDir });
24
+ const zipPath = join(this.blockletBackupDir, 'blocklets', blockletMeta.name, `${blockletMeta.version}.zip`);
25
+ dirs.push({ sourceDir, zipPath });
21
26
  }
22
27
 
23
- await this.dirsToZip(dirs, join(this.blockletBackupDir, 'blocklets.zip'));
28
+ await this.dirsToZip(dirs);
24
29
  }
25
30
 
26
31
  /**
27
32
  *
28
33
  *
29
34
  * @param {import('@abtnode/client').BlockletState} blocklet
30
- * @returns {Array<{name: string, version: string}>}
35
+ * @returns {Array<{name: string, version: string, tarball: string}>}
31
36
  * @memberof BlockletsBackup
32
37
  */
33
38
  getBlockletMetas(blocklet) {
@@ -39,6 +44,7 @@ class BlockletsBackup extends BaseBackup {
39
44
  metas.push({
40
45
  name: blocklet.meta.bundleName,
41
46
  version: blocklet.meta.version,
47
+ tarball: blocklet?.meta?.dist?.tarball,
42
48
  });
43
49
 
44
50
  for (const child of blocklet.children) {
@@ -49,31 +55,19 @@ class BlockletsBackup extends BaseBackup {
49
55
  }
50
56
 
51
57
  /**
52
- * @param {Array<{sourceDir: string, destDir: string}>} dirs: /some/folder/to/compress
58
+ * @param {Array<{sourceDir: string, zipPath: string}>} dirs: /some/folder/to/compress
53
59
  * @param {String} zipPath: /path/to/created.zip
54
60
  * @returns {Promise}
55
61
  * @memberof BlockletsBackup
56
62
  */
57
- async dirsToZip(dirs, zipPath) {
58
- ensureDirSync(dirname(zipPath));
59
- if (existsSync(zipPath)) {
60
- removeSync(zipPath);
61
- }
62
-
63
- const archive = archiver('zip', { zlib: { level: 9 } });
64
- const stream = createWriteStream(zipPath);
65
-
66
- return new Promise((resolve, reject) => {
67
- archive.on('error', (err) => reject(err));
68
- stream.on('close', () => resolve());
69
-
70
- for (const dir of dirs) {
71
- archive.directory(dir.sourceDir, dir.destDir);
72
- }
73
-
74
- archive.pipe(stream);
75
- archive.finalize();
76
- });
63
+ async dirsToZip(dirs) {
64
+ await Promise.all(
65
+ dirs.map(async (dir) => {
66
+ const tempZipPath = `${dir.zipPath}.bak`;
67
+ await dirToZip(dir.sourceDir, tempZipPath);
68
+ await compareAndMove(dir.zipPath, tempZipPath);
69
+ })
70
+ );
77
71
  }
78
72
  }
79
73
 
@@ -1,4 +1,4 @@
1
- const { copySync } = require('fs-extra');
1
+ const { copy } = require('fs-extra');
2
2
  const { join } = require('path');
3
3
  const { BaseBackup } = require('./base');
4
4
 
@@ -12,7 +12,7 @@ class DataBackup extends BaseBackup {
12
12
  const blockletDataDir = join(this.serverDataDir, 'data', this.blocklet.meta.name);
13
13
  const blockletBackupDataDir = join(this.blockletBackupDir, 'data');
14
14
 
15
- copySync(blockletDataDir, blockletBackupDataDir, { overwrite: true });
15
+ await copy(blockletDataDir, blockletBackupDataDir, { overwrite: true });
16
16
  }
17
17
  }
18
18
 
@@ -1,7 +1,8 @@
1
- const { ensureDirSync, copySync, statSync } = require('fs-extra');
1
+ const { ensureDirSync, copy, statSync } = require('fs-extra');
2
2
  const { join } = require('path');
3
3
  const { BaseBackup } = require('./base');
4
4
 
5
+ // note: 可以不需要备份
5
6
  class LogsBackup extends BaseBackup {
6
7
  async export() {
7
8
  const sourceLogsDir = join(this.serverDataDir, 'logs', this.blocklet.meta.name);
@@ -9,7 +10,7 @@ class LogsBackup extends BaseBackup {
9
10
 
10
11
  const targetLogsDir = join(this.blockletBackupDir, 'logs');
11
12
 
12
- copySync(sourceLogsDir, targetLogsDir, {
13
+ await copy(sourceLogsDir, targetLogsDir, {
13
14
  overwrite: true,
14
15
  filter: (src) => {
15
16
  return !statSync(src).isSymbolicLink();
@@ -8,7 +8,7 @@ const { isValid } = require('@arcblock/did');
8
8
  const { BLOCKLET_CONFIGURABLE_KEY } = require('@blocklet/constant');
9
9
  const { getBlockletInfo } = require('@blocklet/meta');
10
10
  const { SpaceClient, SyncFolderPushCommand } = require('@did-space/client');
11
- const { ensureDirSync, existsSync, rmdirSync, removeSync } = require('fs-extra');
11
+ const { ensureDirSync } = require('fs-extra');
12
12
  const { isEmpty } = require('lodash');
13
13
  const { join } = require('path');
14
14
  const states = require('../../../states');
@@ -17,7 +17,6 @@ const { BlockletBackup } = require('./blocklet');
17
17
  const { BlockletExtrasBackup } = require('./blocklet-extras');
18
18
  const { BlockletsBackup } = require('./blocklets');
19
19
  const { DataBackup } = require('./data');
20
- const { LogsBackup } = require('./logs');
21
20
  const { RoutingRuleBackup } = require('./routing-rule');
22
21
 
23
22
  class SpacesBackup {
@@ -78,7 +77,6 @@ class SpacesBackup {
78
77
  this.input = input;
79
78
  this.storages = [
80
79
  new AuditLogBackup(this.input),
81
- new LogsBackup(this.input),
82
80
  new BlockletBackup(this.input),
83
81
  new BlockletsBackup(this.input),
84
82
  new BlockletExtrasBackup(this.input),
@@ -107,7 +105,6 @@ class SpacesBackup {
107
105
  await this.initialize();
108
106
  await this.export();
109
107
  await this.syncToSpaces();
110
- await this.destroy();
111
108
  }
112
109
 
113
110
  async initialize() {
@@ -121,9 +118,6 @@ class SpacesBackup {
121
118
  this.serverDataDir = process.env.ABT_NODE_DATA_DIR;
122
119
 
123
120
  this.blockletBackupDir = join(process.env.ABT_NODE_DATA_DIR, 'tmp/backup', this.blocklet.meta.did);
124
- if (existsSync(this.blockletBackupDir)) {
125
- rmdirSync(this.blockletBackupDir, { recursive: true });
126
- }
127
121
  ensureDirSync(this.blockletBackupDir);
128
122
 
129
123
  this.spacesEndpoint = this.blocklet.environments.find(
@@ -158,7 +152,8 @@ class SpacesBackup {
158
152
  source: join(this.blockletBackupDir, '/'),
159
153
  target: join('.did-objects', this.blocklet.appDid),
160
154
  debug: true,
161
- retryCount: 3,
155
+ concurrency: 64,
156
+ retryCount: 100,
162
157
  filter: (object) => {
163
158
  return object.name !== '.DS_Store';
164
159
  },
@@ -176,12 +171,6 @@ class SpacesBackup {
176
171
  const { wallet } = getBlockletInfo(blockletInfo, nodeInfo.sk);
177
172
  return wallet;
178
173
  }
179
-
180
- async destroy() {
181
- if (existsSync(this.blockletBackupDir)) {
182
- removeSync(this.blockletBackupDir);
183
- }
184
- }
185
174
  }
186
175
 
187
176
  module.exports = {
@@ -1,5 +1,5 @@
1
1
  const { removeSync, outputJsonSync, readJSONSync } = require('fs-extra');
2
- const { cloneDeep } = require('lodash');
2
+ const { cloneDeep, isArray } = require('lodash');
3
3
  const { join } = require('path');
4
4
  const security = require('@abtnode/util/lib/security');
5
5
  const { BaseRestore } = require('./base');
@@ -61,6 +61,13 @@ class BlockletExtrasRestore extends BaseRestore {
61
61
  * @memberof BlockletExtrasRestore
62
62
  */
63
63
  useBlockletDecryptConfigs(configs) {
64
+ // eslint-disable-next-line no-console
65
+ console.log('debug', configs, !configs || !isArray(configs));
66
+
67
+ if (!configs || !isArray(configs)) {
68
+ return;
69
+ }
70
+
64
71
  for (const config of configs) {
65
72
  // secure 为 true 的配置才需要被加密保存上次到 did spaces
66
73
  if (config.secure) {
@@ -76,8 +83,6 @@ class BlockletExtrasRestore extends BaseRestore {
76
83
  config.value = decryptByBlocklet;
77
84
  }
78
85
  }
79
-
80
- return configs;
81
86
  }
82
87
  }
83
88
 
@@ -1,23 +1,41 @@
1
- const { existsSync, removeSync } = require('fs-extra');
1
+ const { existsSync, remove } = require('fs-extra');
2
2
  const { join } = require('path');
3
- const StreamZip = require('node-stream-zip');
3
+ const fg = require('fast-glob');
4
4
  const { BaseRestore } = require('./base');
5
+ const { zipToDir } = require('../utils/zip');
5
6
 
6
7
  class BlockletsRestore extends BaseRestore {
7
- filename = 'blocklets.zip';
8
+ filename = 'blocklets';
8
9
 
9
10
  async import() {
10
- const blockletZipPath = join(this.blockletRestoreDir, this.filename);
11
+ const blockletsDir = join(this.blockletRestoreDir, this.filename);
11
12
 
12
- if (!existsSync(blockletZipPath)) {
13
- throw new Error(`file not found: ${blockletZipPath}`);
13
+ if (!existsSync(blockletsDir)) {
14
+ throw new Error(`dir not found: ${blockletsDir}`);
14
15
  }
15
16
 
16
- // eslint-disable-next-line new-cap
17
- const zip = new StreamZip.async({ file: blockletZipPath });
18
- await zip.extract(null, join(this.blockletRestoreDir, 'blocklets'));
19
- await zip.close();
20
- removeSync(blockletZipPath);
17
+ const paths = await fg('**/*.zip', {
18
+ cwd: blockletsDir,
19
+ onlyFiles: true,
20
+ absolute: true,
21
+ });
22
+
23
+ /**
24
+ * @type {{source: string, target: string}}
25
+ */
26
+ const zipConfigs = paths.map((path) => {
27
+ return {
28
+ source: path,
29
+ target: path.replace(/.zip$/, ''),
30
+ };
31
+ });
32
+
33
+ await Promise.all(
34
+ zipConfigs.map(async (zipConfig) => {
35
+ await zipToDir(zipConfig.source, zipConfig.target);
36
+ await remove(zipConfig.source);
37
+ })
38
+ );
21
39
  }
22
40
  }
23
41
 
@@ -0,0 +1,21 @@
1
+ const { removeSync, existsSync } = require('fs-extra');
2
+ const { join } = require('path');
3
+ const { BaseRestore } = require('./base');
4
+ const { zipToDir } = require('../utils/zip');
5
+
6
+ class LogsRestore extends BaseRestore {
7
+ filename = 'logs.zip';
8
+
9
+ async import() {
10
+ const blockletZipPath = join(this.blockletRestoreDir, this.filename);
11
+
12
+ if (!existsSync(blockletZipPath)) {
13
+ throw new Error(`file not found: ${blockletZipPath}`);
14
+ }
15
+
16
+ await zipToDir(blockletZipPath, join(this.blockletRestoreDir, 'logs'));
17
+ removeSync(blockletZipPath);
18
+ }
19
+ }
20
+
21
+ module.exports = { LogsRestore };
@@ -74,6 +74,10 @@ class SpacesRestore {
74
74
  this.serverDataDir = process.env.ABT_NODE_DATA_DIR;
75
75
  this.blockletWallet = await this.getBlockletWallet();
76
76
 
77
+ if (!this.input.endpoint.includes(this.blockletWallet.address)) {
78
+ throw new Error(`endpoint and blocklet.appDid(${this.blockletWallet.address}) do not match`);
79
+ }
80
+
77
81
  this.blockletRestoreDir = join(process.env.ABT_NODE_DATA_DIR, 'tmp/restore', this.blockletWallet.address);
78
82
  if (existsSync(this.blockletRestoreDir)) {
79
83
  rmdirSync(this.blockletRestoreDir, { recursive: true });
@@ -113,7 +117,8 @@ class SpacesRestore {
113
117
  source: join('.did-objects', this.blockletWallet.address, '/'),
114
118
  target: this.blockletRestoreDir,
115
119
  debug: true,
116
- retryCount: 3,
120
+ concurrency: 64,
121
+ retryCount: 100,
117
122
  })
118
123
  );
119
124
 
@@ -0,0 +1,51 @@
1
+ const { removeSync, existsSync, move, createReadStream } = require('fs-extra');
2
+ const hasha = require('hasha');
3
+
4
+ /**
5
+ *
6
+ *
7
+ * @param {NodeJS.ReadableStream} stream1
8
+ * @param {NodeJS.ReadableStream} stream2
9
+ * @return {Promise<boolean>}
10
+ */
11
+ async function compareHash(stream1, stream2) {
12
+ const hash1 = await hasha.fromStream(stream1, {
13
+ algorithm: 'md5',
14
+ });
15
+ const hash2 = await hasha.fromStream(stream2, {
16
+ algorithm: 'md5',
17
+ });
18
+
19
+ return hash1 === hash2;
20
+ }
21
+
22
+ /**
23
+ *
24
+ * @description 比对新文件和旧文件的hash,旧文件不存在或者hash不同时,使用新文件替换,hash相同,删除新文件
25
+ * @param {string} oldFilePath
26
+ * @param {string} newFilePath
27
+ * @returns {Promise<boolean>}
28
+ */
29
+ async function compareAndMove(oldFilePath, newFilePath) {
30
+ if (!existsSync(oldFilePath)) {
31
+ await move(newFilePath, oldFilePath, { overwrite: true });
32
+ return;
33
+ }
34
+
35
+ if (!existsSync(newFilePath)) {
36
+ throw new Error(`newFilePath(${newFilePath}) not found`);
37
+ }
38
+
39
+ const isSame = await compareHash(createReadStream(oldFilePath), createReadStream(newFilePath));
40
+ if (isSame) {
41
+ removeSync(newFilePath);
42
+ return;
43
+ }
44
+
45
+ await move(newFilePath, oldFilePath, { overwrite: true });
46
+ }
47
+
48
+ module.exports = {
49
+ compareHash,
50
+ compareAndMove,
51
+ };
@@ -0,0 +1,43 @@
1
+ const archiver = require('archiver');
2
+ const { ensureDirSync, existsSync, removeSync, createWriteStream } = require('fs-extra');
3
+ const { dirname } = require('path');
4
+ const StreamZip = require('node-stream-zip');
5
+
6
+ /**
7
+ *
8
+ *
9
+ * @param {string} source ~/abc/ 通常是一个文件夹
10
+ * @param {string} target abc.zip 压缩包的名称
11
+ * @return {*}
12
+ */
13
+ function dirToZip(source, target) {
14
+ return new Promise((resolve, reject) => {
15
+ ensureDirSync(dirname(target));
16
+
17
+ if (existsSync(target)) {
18
+ removeSync(target);
19
+ }
20
+
21
+ const output = createWriteStream(target);
22
+ const archive = archiver('zip', { level: 9 });
23
+ archive.on('error', (err) => reject(err));
24
+ output.on('close', () => resolve());
25
+
26
+ archive.directory(source, false);
27
+
28
+ archive.pipe(output);
29
+ archive.finalize();
30
+ });
31
+ }
32
+
33
+ async function zipToDir(source, target) {
34
+ // eslint-disable-next-line new-cap
35
+ const zip = new StreamZip.async({ file: source });
36
+ await zip.extract(null, target);
37
+ await zip.close();
38
+ }
39
+
40
+ module.exports = {
41
+ dirToZip,
42
+ zipToDir,
43
+ };