@abtnode/core 1.15.17 → 1.16.0-beta-8ee536d7

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 (119) hide show
  1. package/lib/api/node.js +67 -69
  2. package/lib/api/team.js +386 -55
  3. package/lib/blocklet/downloader/blocklet-downloader.js +226 -0
  4. package/lib/blocklet/downloader/bundle-downloader.js +272 -0
  5. package/lib/blocklet/downloader/constants.js +3 -0
  6. package/lib/blocklet/downloader/resolve-download.js +199 -0
  7. package/lib/blocklet/extras.js +83 -26
  8. package/lib/blocklet/hooks.js +18 -65
  9. package/lib/blocklet/manager/base.js +10 -16
  10. package/lib/blocklet/manager/disk.js +1680 -1566
  11. package/lib/blocklet/manager/helper/install-application-from-backup.js +177 -0
  12. package/lib/blocklet/manager/helper/install-application-from-dev.js +94 -0
  13. package/lib/blocklet/manager/helper/install-application-from-general.js +188 -0
  14. package/lib/blocklet/manager/helper/install-component-from-dev.js +84 -0
  15. package/lib/blocklet/manager/helper/install-component-from-upload.js +181 -0
  16. package/lib/blocklet/manager/helper/install-component-from-url.js +173 -0
  17. package/lib/blocklet/manager/helper/migrate-application-to-struct-v2.js +450 -0
  18. package/lib/blocklet/manager/helper/rollback-cache.js +41 -0
  19. package/lib/blocklet/manager/helper/upgrade-components.js +152 -0
  20. package/lib/blocklet/migration.js +30 -52
  21. package/lib/blocklet/storage/backup/audit-log.js +27 -0
  22. package/lib/blocklet/storage/backup/base.js +62 -0
  23. package/lib/blocklet/storage/backup/blocklet-extras.js +92 -0
  24. package/lib/blocklet/storage/backup/blocklet.js +70 -0
  25. package/lib/blocklet/storage/backup/blocklets.js +74 -0
  26. package/lib/blocklet/storage/backup/data.js +19 -0
  27. package/lib/blocklet/storage/backup/logs.js +24 -0
  28. package/lib/blocklet/storage/backup/routing-rule.js +19 -0
  29. package/lib/blocklet/storage/backup/spaces.js +240 -0
  30. package/lib/blocklet/storage/restore/base.js +67 -0
  31. package/lib/blocklet/storage/restore/blocklet-extras.js +86 -0
  32. package/lib/blocklet/storage/restore/blocklet.js +56 -0
  33. package/lib/blocklet/storage/restore/blocklets.js +43 -0
  34. package/lib/blocklet/storage/restore/logs.js +21 -0
  35. package/lib/blocklet/storage/restore/spaces.js +156 -0
  36. package/lib/blocklet/storage/utils/hash.js +51 -0
  37. package/lib/blocklet/storage/utils/zip.js +43 -0
  38. package/lib/cert.js +206 -0
  39. package/lib/event.js +237 -64
  40. package/lib/index.js +191 -83
  41. package/lib/migrations/1.0.21-update-config.js +1 -1
  42. package/lib/migrations/1.0.22-max-memory.js +1 -1
  43. package/lib/migrations/1.0.25.js +1 -1
  44. package/lib/migrations/1.0.32-update-config.js +1 -1
  45. package/lib/migrations/1.0.33-blocklets.js +1 -1
  46. package/lib/migrations/1.5.20-registry.js +15 -0
  47. package/lib/migrations/1.6.17-blocklet-children.js +48 -0
  48. package/lib/migrations/1.6.21-rename-ip-echo-domain.js +35 -0
  49. package/lib/migrations/1.6.4-security.js +59 -0
  50. package/lib/migrations/1.6.5-security.js +60 -0
  51. package/lib/migrations/1.6.9-update-node-info-and-certificate.js +38 -0
  52. package/lib/migrations/1.7.1-blocklet-setup.js +18 -0
  53. package/lib/migrations/1.7.12-blocklet-meta.js +51 -0
  54. package/lib/migrations/1.7.15-blocklet-bundle-source.js +42 -0
  55. package/lib/migrations/1.7.20-blocklet-component.js +41 -0
  56. package/lib/migrations/1.8.33-blocklet-mem-limit.js +20 -0
  57. package/lib/migrations/README.md +1 -1
  58. package/lib/migrations/index.js +6 -2
  59. package/lib/monitor/blocklet-runtime-monitor.js +200 -0
  60. package/lib/monitor/get-history-list.js +37 -0
  61. package/lib/monitor/node-runtime-monitor.js +228 -0
  62. package/lib/router/helper.js +576 -500
  63. package/lib/router/index.js +85 -21
  64. package/lib/router/manager.js +146 -187
  65. package/lib/states/README.md +36 -1
  66. package/lib/states/access-key.js +39 -17
  67. package/lib/states/audit-log.js +462 -0
  68. package/lib/states/base.js +4 -213
  69. package/lib/states/blocklet-extras.js +195 -138
  70. package/lib/states/blocklet.js +371 -110
  71. package/lib/states/cache.js +8 -6
  72. package/lib/states/challenge.js +5 -5
  73. package/lib/states/index.js +19 -36
  74. package/lib/states/migration.js +4 -4
  75. package/lib/states/node.js +135 -46
  76. package/lib/states/notification.js +22 -35
  77. package/lib/states/session.js +17 -9
  78. package/lib/states/site.js +50 -25
  79. package/lib/states/user.js +74 -20
  80. package/lib/states/webhook.js +10 -6
  81. package/lib/team/manager.js +124 -7
  82. package/lib/util/blocklet.js +1223 -246
  83. package/lib/util/chain.js +1 -1
  84. package/lib/util/default-node-config.js +5 -23
  85. package/lib/util/disk-monitor.js +13 -10
  86. package/lib/util/domain-status.js +84 -15
  87. package/lib/util/get-accessible-external-node-ip.js +2 -2
  88. package/lib/util/get-domain-for-blocklet.js +13 -0
  89. package/lib/util/get-meta-from-url.js +33 -0
  90. package/lib/util/index.js +207 -272
  91. package/lib/util/ip.js +6 -0
  92. package/lib/util/maintain.js +233 -0
  93. package/lib/util/public-to-store.js +85 -0
  94. package/lib/util/ready.js +1 -1
  95. package/lib/util/requirement.js +28 -9
  96. package/lib/util/reset-node.js +22 -7
  97. package/lib/util/router.js +13 -0
  98. package/lib/util/rpc.js +16 -0
  99. package/lib/util/store.js +179 -0
  100. package/lib/util/sysinfo.js +44 -0
  101. package/lib/util/ua.js +54 -0
  102. package/lib/validators/blocklet-extra.js +24 -0
  103. package/lib/validators/node.js +25 -12
  104. package/lib/validators/permission.js +16 -1
  105. package/lib/validators/role.js +17 -3
  106. package/lib/validators/router.js +40 -20
  107. package/lib/validators/trusted-passport.js +1 -0
  108. package/lib/validators/util.js +22 -5
  109. package/lib/webhook/index.js +45 -35
  110. package/lib/webhook/sender/index.js +5 -0
  111. package/lib/webhook/sender/slack/index.js +1 -1
  112. package/lib/webhook/sender/wallet/index.js +48 -0
  113. package/package.json +54 -36
  114. package/lib/blocklet/registry.js +0 -205
  115. package/lib/states/https-cert.js +0 -67
  116. package/lib/util/get-ip-dns-domain-for-blocklet.js +0 -19
  117. package/lib/util/service.js +0 -66
  118. package/lib/util/upgrade.js +0 -178
  119. /package/lib/{queue.js → util/queue.js} +0 -0
@@ -0,0 +1,240 @@
1
+ /**
2
+ * @typedef {{
3
+ * appDid: string
4
+ * event: import('events').EventEmitter,
5
+ * userDid: string,
6
+ * referrer: string,
7
+ * }} SpaceBackupInput
8
+ *
9
+ * @typedef {{
10
+ * signer: import('@ocap/wallet').WalletObject
11
+ * encrypt: (v: string) => string,
12
+ * decrypt: (v: string) => string,
13
+ * delegation: string,
14
+ * }} SecurityContext
15
+ */
16
+
17
+ const { isValid } = require('@arcblock/did');
18
+ const { BLOCKLET_CONFIGURABLE_KEY, BlockletEvents } = require('@blocklet/constant');
19
+ const { SpaceClient, BackupBlockletCommand } = require('@did-space/client');
20
+ const { ensureDirSync } = require('fs-extra');
21
+ const { isEmpty } = require('lodash');
22
+ const { join, basename } = require('path');
23
+ const { Hasher } = require('@ocap/mcrypto');
24
+ const { toBuffer } = require('@ocap/util');
25
+ const { getAppName, getAppDescription } = require('@blocklet/meta/lib/util');
26
+ const getBlockletInfo = require('@blocklet/meta/lib/info');
27
+ const security = require('@abtnode/util/lib/security');
28
+
29
+ const logger = require('@abtnode/logger')('@abtnode/core:storage:backup');
30
+
31
+ const states = require('../../../states');
32
+ const { AuditLogBackup } = require('./audit-log');
33
+ const { BlockletBackup } = require('./blocklet');
34
+ const { BlockletExtrasBackup } = require('./blocklet-extras');
35
+ const { BlockletsBackup } = require('./blocklets');
36
+ const { DataBackup } = require('./data');
37
+ const { RoutingRuleBackup } = require('./routing-rule');
38
+
39
+ class SpacesBackup {
40
+ /**
41
+ *
42
+ * @type {SpaceBackupInput}
43
+ * @memberof SpacesBackup
44
+ */
45
+ input;
46
+
47
+ /**
48
+ * @description blocklet state 对象
49
+ * @type {import('@abtnode/client').BlockletState}
50
+ * @memberof SpacesBackup
51
+ */
52
+ blocklet;
53
+
54
+ /**
55
+ * @description 当前 blocklet 的数据目录
56
+ * @type {string}
57
+ * @memberof SpacesBackup
58
+ */
59
+ backupDir;
60
+
61
+ /**
62
+ *
63
+ * @description server 的数据目录
64
+ * @type {string}
65
+ * @memberof SpacesBackup
66
+ */
67
+ serverDir;
68
+
69
+ /**
70
+ *
71
+ * @description spaces 的 endpoint
72
+ * @type {string}
73
+ * @memberof SpacesBackup
74
+ */
75
+ spaceEndpoint;
76
+
77
+ /**
78
+ *
79
+ * @description spaces 安全相关的上下文
80
+ * @type SecurityContext
81
+ * @memberof SpacesBackup
82
+ */
83
+ securityContext;
84
+
85
+ storages;
86
+
87
+ /**
88
+ *
89
+ * @param {SpaceBackupInput} input
90
+ * @memberof SpacesBackup
91
+ */
92
+ constructor(input) {
93
+ this.verify(input);
94
+ this.input = input;
95
+ this.storages = [
96
+ new AuditLogBackup(this.input),
97
+ new BlockletBackup(this.input),
98
+ new BlockletsBackup(this.input),
99
+ new BlockletExtrasBackup(this.input),
100
+ new RoutingRuleBackup(this.input),
101
+ new DataBackup(this.input),
102
+ ];
103
+ }
104
+
105
+ /**
106
+ * @param {SpaceBackupInput} input
107
+ * @returns {void}
108
+ * @memberof SpacesBackup
109
+ */
110
+ verify(input) {
111
+ if (isEmpty(input?.appDid) || !isValid(input?.appDid)) {
112
+ throw new Error(`input.appDid(${input?.appDid}) is not a valid did`);
113
+ }
114
+ }
115
+
116
+ /**
117
+ *
118
+ * @returns {Promise<void>}
119
+ * @memberof SpacesBackup
120
+ */
121
+ async backup() {
122
+ await this.initialize();
123
+ await this.export();
124
+ await this.syncToSpaces();
125
+ }
126
+
127
+ async initialize() {
128
+ this.blocklet = await states.blocklet.getBlocklet(this.input.appDid);
129
+ if (isEmpty(this.blocklet)) {
130
+ throw new Error('blocklet cannot be empty');
131
+ }
132
+
133
+ this.serverDir = process.env.ABT_NODE_DATA_DIR;
134
+ this.backupDir = join(this.serverDir, 'tmp/backup', this.blocklet.appDid);
135
+ ensureDirSync(this.backupDir);
136
+
137
+ this.spaceEndpoint = this.blocklet.environments.find(
138
+ (e) => e.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SPACE_ENDPOINT
139
+ )?.value;
140
+ if (isEmpty(this.spaceEndpoint)) {
141
+ throw new Error('spaceEndpoint cannot be empty');
142
+ }
143
+
144
+ this.securityContext = await this.getSecurityContext();
145
+ }
146
+
147
+ async export() {
148
+ this.input.event.emit(BlockletEvents.backupProgress, {
149
+ appDid: this.input.appDid,
150
+ message: 'Preparing data for backup...',
151
+ progress: 15,
152
+ completed: false,
153
+ });
154
+ await Promise.all(
155
+ this.storages.map((storage) => {
156
+ storage.ensureParams(this);
157
+ return storage.export();
158
+ })
159
+ );
160
+ this.input.event.emit(BlockletEvents.backupProgress, {
161
+ appDid: this.input.appDid,
162
+ message: 'Data ready, start backup...',
163
+ progress: 20,
164
+ completed: false,
165
+ });
166
+ }
167
+
168
+ async syncToSpaces() {
169
+ /**
170
+ * @type {import('@abtnode/client').NodeState}
171
+ */
172
+ const node = await states.node.read();
173
+ const serverDid = node.did;
174
+
175
+ const spaceClient = new SpaceClient({
176
+ endpoint: this.spaceEndpoint,
177
+ wallet: this.securityContext.signer,
178
+ delegation: this.securityContext.delegation,
179
+ });
180
+
181
+ const { errorCount, message } = await spaceClient.send(
182
+ new BackupBlockletCommand({
183
+ appDid: this.blocklet.appDid,
184
+ appName: getAppName(this.blocklet),
185
+ appDescription: getAppDescription(this.blocklet),
186
+ userDid: this.input.userDid,
187
+ referrer: this.input.referrer,
188
+ serverDid,
189
+ signerDid: this.securityContext.signer.address,
190
+
191
+ source: join(this.backupDir, '/'),
192
+ debug: true,
193
+ concurrency: 32,
194
+ retryCount: 10,
195
+ filter: (object) => {
196
+ // FIXME: @yejianchao 这里需要更完整的黑名单
197
+ return object.name !== '.DS_Store';
198
+ },
199
+ onProgress: (data) => {
200
+ logger.info('backup progress', { appDid: this.input.appDid, data });
201
+ const percent = (data.completed * 100) / data.total;
202
+ this.input.event.emit(BlockletEvents.backupProgress, {
203
+ appDid: this.input.appDid,
204
+ message: `Uploaded file ${basename(data.key)} (${data.completed}/${data.total})`,
205
+ // 0.8 是因为上传文件到 spaces 占进度的 80%,+ 20 是因为需要累加之前的进度
206
+ progress: +Math.ceil(percent * 0.8).toFixed(2) + 20,
207
+ completed: false,
208
+ });
209
+ },
210
+ })
211
+ );
212
+
213
+ if (errorCount !== 0) {
214
+ throw new Error(`Sync to spaces encountered error: ${message}`);
215
+ }
216
+ }
217
+
218
+ async getSecurityContext() {
219
+ const blocklet = await states.blocklet.getBlocklet(this.input.appDid);
220
+ const nodeInfo = await states.node.read();
221
+
222
+ const { wallet } = getBlockletInfo(blocklet, nodeInfo.sk);
223
+
224
+ const { secretKey, address } = wallet; // we encrypt using latest wallet, not the permanent wallet
225
+ const password = toBuffer(Hasher.SHA3.hash256(Buffer.concat([secretKey, address].map(toBuffer))));
226
+ const encrypt = (v) => security.encrypt(v, address, password);
227
+ const decrypt = (v) => security.decrypt(v, address, password);
228
+
229
+ return {
230
+ signer: wallet,
231
+ delegation: '',
232
+ encrypt,
233
+ decrypt,
234
+ };
235
+ }
236
+ }
237
+
238
+ module.exports = {
239
+ SpacesBackup,
240
+ };
@@ -0,0 +1,67 @@
1
+ class BaseRestore {
2
+ /**
3
+ *
4
+ * @type {import('./spaces').SpaceRestoreInput}
5
+ * @memberof BaseRestore
6
+ */
7
+ input;
8
+
9
+ /**
10
+ * @description 当前 blocklet 的数据目录
11
+ * @type {string}
12
+ * @memberof BaseRestore
13
+ */
14
+ restoreDir;
15
+
16
+ /**
17
+ *
18
+ * @description server 的数据目录
19
+ * @type {string}
20
+ * @memberof BaseRestore
21
+ */
22
+ serverDir;
23
+
24
+ constructor(input) {
25
+ this.input = input;
26
+ }
27
+
28
+ /**
29
+ *
30
+ *
31
+ * @param {import('./spaces').SpacesRestore} spaces
32
+ * @memberof BaseRestore
33
+ */
34
+ ensureParams(spaces) {
35
+ this.restoreDir = spaces.restoreDir;
36
+ this.serverDir = spaces.serverDir;
37
+ }
38
+
39
+ // eslint-disable-next-line
40
+ async import(params) {
41
+ throw new Error('not implemented');
42
+ }
43
+
44
+ /**
45
+ * Generate params for BlockletManager to install
46
+ *
47
+ * @return {object}
48
+ * @memberof BaseRestore
49
+ */
50
+ getInstallParams() {
51
+ return {};
52
+ }
53
+
54
+ /**
55
+ * Generate params for other restorers
56
+ *
57
+ * @return {object}
58
+ * @memberof BaseRestore
59
+ */
60
+ getImportParams() {
61
+ return {};
62
+ }
63
+ }
64
+
65
+ module.exports = {
66
+ BaseRestore,
67
+ };
@@ -0,0 +1,86 @@
1
+ const { removeSync, outputJsonSync, readJSONSync } = require('fs-extra');
2
+ const { cloneDeep, isEmpty } = require('lodash');
3
+ const { join } = require('path');
4
+ const security = require('@abtnode/util/lib/security');
5
+ const { BLOCKLET_CONFIGURABLE_KEY } = require('@blocklet/constant');
6
+ const { BaseRestore } = require('./base');
7
+
8
+ class BlockletExtrasRestore extends BaseRestore {
9
+ filename = 'blocklet-extras.json';
10
+
11
+ async import(params) {
12
+ const extras = this.getExtras();
13
+ this.cleanExtras(extras, params);
14
+ removeSync(join(this.restoreDir, this.filename));
15
+ outputJsonSync(join(this.restoreDir, this.filename), extras);
16
+ }
17
+
18
+ /**
19
+ *
20
+ * @description
21
+ * @return {import('@abtnode/client').BlockletState}
22
+ * @memberof BlockletExtrasRestore
23
+ */
24
+ getExtras() {
25
+ /**
26
+ * @type {import('@abtnode/client').BlockletState}
27
+ */
28
+ return readJSONSync(join(this.restoreDir, this.filename));
29
+ }
30
+
31
+ /**
32
+ *
33
+ * @description 清理数据并加密
34
+ * @param {import('@abtnode/client').BlockletState} raw
35
+ * @memberof BlockletExtrasRestore
36
+ */
37
+ async cleanExtras(raw, params) {
38
+ const blockletExtra = cloneDeep(raw);
39
+
40
+ const queue = [blockletExtra];
41
+ while (queue.length) {
42
+ const current = queue.pop();
43
+
44
+ // 加解密
45
+ this.decryptConfigs(current.configs, params);
46
+
47
+ if (current?.children) {
48
+ queue.push(...current.children);
49
+ }
50
+ }
51
+ }
52
+
53
+ /**
54
+ *
55
+ * @description 解密加密的数据
56
+ * @param {import('@abtnode/client').ConfigEntry[]} configs
57
+ * @param {object} params
58
+ * @return {void}
59
+ * @memberof BlockletExtrasRestore
60
+ */
61
+ decryptConfigs(configs, params) {
62
+ if (isEmpty(configs)) {
63
+ return;
64
+ }
65
+
66
+ const { password } = this.input;
67
+ for (const config of configs) {
68
+ if (config.secure) {
69
+ config.value = security.decrypt(config.value, params.salt, password);
70
+ }
71
+ }
72
+
73
+ const skConfig = configs.find((x) => x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SK);
74
+ if (skConfig) {
75
+ this.appSk = skConfig.value;
76
+ }
77
+ }
78
+
79
+ getInstallParams() {
80
+ return {
81
+ appSk: this.appSk,
82
+ };
83
+ }
84
+ }
85
+
86
+ module.exports = { BlockletExtrasRestore };
@@ -0,0 +1,56 @@
1
+ const { removeSync, outputJsonSync, readJSONSync } = require('fs-extra');
2
+ const { join } = require('path');
3
+ const { APP_STRUCT_VERSION } = require('@abtnode/constant');
4
+ const security = require('@abtnode/util/lib/security');
5
+
6
+ const { BaseRestore } = require('./base');
7
+
8
+ class BlockletRestore extends BaseRestore {
9
+ filename = 'blocklet.json';
10
+
11
+ async import(params) {
12
+ const blocklet = this.decrypt(this.getBlocklet(), params);
13
+ removeSync(join(this.restoreDir, this.filename));
14
+ outputJsonSync(join(this.restoreDir, this.filename), blocklet);
15
+ }
16
+
17
+ /**
18
+ *
19
+ * @description
20
+ * @return {import('@abtnode/client').BlockletState}
21
+ * @memberof BlockletRestore
22
+ */
23
+ getBlocklet() {
24
+ return readJSONSync(join(this.restoreDir, this.filename));
25
+ }
26
+
27
+ /**
28
+ *
29
+ * @description 解密加密的数据
30
+ * @param {import('@abtnode/client').BlockletState} blocklet
31
+ * @return {import('@abtnode/client').BlockletState}
32
+ * @memberof BlockletRestore
33
+ */
34
+ decrypt(blocklet, params) {
35
+ const { password } = this.input;
36
+ if (Array.isArray(blocklet.migratedFrom)) {
37
+ blocklet.migratedFrom = blocklet.migratedFrom.map((x) => {
38
+ x.appSk = security.decrypt(x.appSk, params.salt, password);
39
+ return x;
40
+ });
41
+ }
42
+
43
+ return blocklet;
44
+ }
45
+
46
+ getImportParams() {
47
+ const blocklet = this.getBlocklet();
48
+ if (blocklet.structVersion !== APP_STRUCT_VERSION) {
49
+ throw new Error('Only new version backup can be restored to this server');
50
+ }
51
+
52
+ return { salt: blocklet.appDid };
53
+ }
54
+ }
55
+
56
+ module.exports = { BlockletRestore };
@@ -0,0 +1,43 @@
1
+ const { existsSync, remove, ensureDirSync } = require('fs-extra');
2
+ const { join } = require('path');
3
+ const fg = require('fast-glob');
4
+ const { BaseRestore } = require('./base');
5
+ const { zipToDir } = require('../utils/zip');
6
+
7
+ class BlockletsRestore extends BaseRestore {
8
+ filename = 'blocklets';
9
+
10
+ async import() {
11
+ const blockletsDir = join(this.restoreDir, this.filename);
12
+
13
+ if (!existsSync(blockletsDir)) {
14
+ ensureDirSync(blockletsDir);
15
+ return;
16
+ }
17
+
18
+ const paths = await fg('**/*.zip', {
19
+ cwd: blockletsDir,
20
+ onlyFiles: true,
21
+ absolute: true,
22
+ });
23
+
24
+ const configs = paths.map((path) => {
25
+ return {
26
+ source: path,
27
+ target: path.replace(/.zip$/, ''),
28
+ };
29
+ });
30
+
31
+ await Promise.all(
32
+ configs.map(async ({ source, target }) => {
33
+ if (!existsSync(target)) {
34
+ ensureDirSync(target);
35
+ }
36
+ await zipToDir(source, target);
37
+ await remove(source);
38
+ })
39
+ );
40
+ }
41
+ }
42
+
43
+ module.exports = { BlockletsRestore };
@@ -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.restoreDir, this.filename);
11
+
12
+ if (!existsSync(blockletZipPath)) {
13
+ throw new Error(`file not found: ${blockletZipPath}`);
14
+ }
15
+
16
+ await zipToDir(blockletZipPath, join(this.restoreDir, 'logs'));
17
+ removeSync(blockletZipPath);
18
+ }
19
+ }
20
+
21
+ module.exports = { LogsRestore };
@@ -0,0 +1,156 @@
1
+ /**
2
+ * @typedef {{
3
+ * appDid: string; // --> appDid
4
+ * endpoint: string;
5
+ * password: Buffer; // derived from (appSk, appDid)
6
+ * delegation: string; // from appDid --> serverDid for downloading
7
+ * wallet: import('@ocap/wallet').WalletObject; // server wallet
8
+ * event: import('events').EventEmitter,
9
+ * userDid: string,
10
+ * referrer: string,
11
+ * }} SpaceRestoreInput
12
+ */
13
+
14
+ const validUrl = require('valid-url');
15
+ const merge = require('lodash/merge');
16
+ const { BlockletEvents } = require('@blocklet/constant');
17
+ const { SpaceClient, RestoreBlockletCommand } = require('@did-space/client');
18
+ const { ensureDirSync, existsSync, rmSync } = require('fs-extra');
19
+ const { join, basename } = require('path');
20
+
21
+ const logger = require('@abtnode/logger')('@abtnode/core:storage:restore');
22
+
23
+ const { BlockletExtrasRestore } = require('./blocklet-extras');
24
+ const { BlockletRestore } = require('./blocklet');
25
+ const { BlockletsRestore } = require('./blocklets');
26
+
27
+ class SpacesRestore {
28
+ /**
29
+ *
30
+ * @type {SpaceRestoreInput}
31
+ * @memberof SpacesRestore
32
+ */
33
+ input;
34
+
35
+ /**
36
+ * @description 当前 blocklet 的数据目录
37
+ * @type {string}
38
+ * @memberof SpacesRestore
39
+ */
40
+ restoreDir;
41
+
42
+ /**
43
+ *
44
+ * @description server 的数据目录
45
+ * @type {string}
46
+ * @memberof SpacesRestore
47
+ */
48
+ serverDir;
49
+
50
+ storages;
51
+
52
+ /**
53
+ *
54
+ * @param {SpaceRestoreInput} input
55
+ * @memberof SpacesRestore
56
+ */
57
+ constructor(input) {
58
+ this.verify(input);
59
+ this.input = input;
60
+ this.storages = [
61
+ new BlockletExtrasRestore(this.input),
62
+ new BlockletRestore(this.input),
63
+ new BlockletsRestore(this.input),
64
+ ];
65
+ }
66
+
67
+ /**
68
+ *
69
+ * @param {SpaceRestoreInput} input
70
+ * @returns {void}
71
+ * @memberof SpacesRestore
72
+ */
73
+ verify(input) {
74
+ if (!validUrl.isWebUri(input.endpoint)) {
75
+ throw new Error(`endpoint(${input.endpoint}) must be a WebUri`);
76
+ }
77
+ if (!input.endpoint.includes(input.appDid)) {
78
+ throw new Error(`endpoint and blocklet.appDid(${input.appDid}) do not match`);
79
+ }
80
+ }
81
+
82
+ async initialize() {
83
+ this.serverDir = process.env.ABT_NODE_DATA_DIR;
84
+ this.restoreDir = join(this.serverDir, 'tmp/restore', this.input.appDid);
85
+ if (existsSync(this.restoreDir)) {
86
+ rmSync(this.restoreDir, { recursive: true });
87
+ }
88
+ ensureDirSync(this.restoreDir);
89
+
90
+ this.storages.map((x) => x.ensureParams(this));
91
+ }
92
+
93
+ async restore() {
94
+ await this.initialize();
95
+ await this.syncFromSpaces();
96
+
97
+ const params = await Promise.all(this.storages.map((x) => x.getImportParams()));
98
+ await this.import(merge(...params));
99
+
100
+ return this.storages.map((x) => x.getInstallParams());
101
+ }
102
+
103
+ async syncFromSpaces() {
104
+ const { endpoint, wallet, delegation } = this.input;
105
+
106
+ const spaceClient = new SpaceClient({
107
+ endpoint,
108
+ delegation,
109
+ wallet,
110
+ });
111
+
112
+ const { errorCount, message } = await spaceClient.send(
113
+ new RestoreBlockletCommand({
114
+ appDid: this.input.appDid,
115
+ target: join(this.restoreDir, '/'),
116
+ debug: true,
117
+ concurrency: 32,
118
+ retryCount: 10,
119
+ onProgress: (data) => {
120
+ logger.info('restore progress', { appDid: this.input.appDid, data });
121
+ this.input.event.emit(BlockletEvents.restoreProgress, {
122
+ appDid: this.input.appDid,
123
+ message: `Downloaded file ${basename(data.key)} (${data.completed}/${data.total})`,
124
+ });
125
+ },
126
+
127
+ userDid: this.input.userDid,
128
+ referrer: this.input.referrer,
129
+ })
130
+ );
131
+
132
+ if (errorCount !== 0) {
133
+ throw new Error(`Sync from spaces encountered error: ${message}`);
134
+ }
135
+ }
136
+
137
+ async import(params) {
138
+ this.input.event.emit(BlockletEvents.restoreProgress, {
139
+ appDid: this.input.appDid,
140
+ message: 'Preparing to import data...',
141
+ });
142
+ await Promise.all(
143
+ this.storages.map((storage) => {
144
+ return storage.import(params);
145
+ })
146
+ );
147
+ this.input.event.emit(BlockletEvents.restoreProgress, {
148
+ appDid: this.input.appDid,
149
+ message: 'Importing data successfully...',
150
+ });
151
+ }
152
+ }
153
+
154
+ module.exports = {
155
+ SpacesRestore,
156
+ };