@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.
- package/lib/api/node.js +67 -69
- package/lib/api/team.js +386 -55
- package/lib/blocklet/downloader/blocklet-downloader.js +226 -0
- package/lib/blocklet/downloader/bundle-downloader.js +272 -0
- package/lib/blocklet/downloader/constants.js +3 -0
- package/lib/blocklet/downloader/resolve-download.js +199 -0
- package/lib/blocklet/extras.js +83 -26
- package/lib/blocklet/hooks.js +18 -65
- package/lib/blocklet/manager/base.js +10 -16
- package/lib/blocklet/manager/disk.js +1680 -1566
- package/lib/blocklet/manager/helper/install-application-from-backup.js +177 -0
- package/lib/blocklet/manager/helper/install-application-from-dev.js +94 -0
- package/lib/blocklet/manager/helper/install-application-from-general.js +188 -0
- package/lib/blocklet/manager/helper/install-component-from-dev.js +84 -0
- package/lib/blocklet/manager/helper/install-component-from-upload.js +181 -0
- package/lib/blocklet/manager/helper/install-component-from-url.js +173 -0
- package/lib/blocklet/manager/helper/migrate-application-to-struct-v2.js +450 -0
- package/lib/blocklet/manager/helper/rollback-cache.js +41 -0
- package/lib/blocklet/manager/helper/upgrade-components.js +152 -0
- package/lib/blocklet/migration.js +30 -52
- package/lib/blocklet/storage/backup/audit-log.js +27 -0
- package/lib/blocklet/storage/backup/base.js +62 -0
- package/lib/blocklet/storage/backup/blocklet-extras.js +92 -0
- package/lib/blocklet/storage/backup/blocklet.js +70 -0
- package/lib/blocklet/storage/backup/blocklets.js +74 -0
- package/lib/blocklet/storage/backup/data.js +19 -0
- package/lib/blocklet/storage/backup/logs.js +24 -0
- package/lib/blocklet/storage/backup/routing-rule.js +19 -0
- package/lib/blocklet/storage/backup/spaces.js +240 -0
- package/lib/blocklet/storage/restore/base.js +67 -0
- package/lib/blocklet/storage/restore/blocklet-extras.js +86 -0
- package/lib/blocklet/storage/restore/blocklet.js +56 -0
- package/lib/blocklet/storage/restore/blocklets.js +43 -0
- package/lib/blocklet/storage/restore/logs.js +21 -0
- package/lib/blocklet/storage/restore/spaces.js +156 -0
- package/lib/blocklet/storage/utils/hash.js +51 -0
- package/lib/blocklet/storage/utils/zip.js +43 -0
- package/lib/cert.js +206 -0
- package/lib/event.js +237 -64
- package/lib/index.js +191 -83
- package/lib/migrations/1.0.21-update-config.js +1 -1
- package/lib/migrations/1.0.22-max-memory.js +1 -1
- package/lib/migrations/1.0.25.js +1 -1
- package/lib/migrations/1.0.32-update-config.js +1 -1
- package/lib/migrations/1.0.33-blocklets.js +1 -1
- package/lib/migrations/1.5.20-registry.js +15 -0
- package/lib/migrations/1.6.17-blocklet-children.js +48 -0
- package/lib/migrations/1.6.21-rename-ip-echo-domain.js +35 -0
- package/lib/migrations/1.6.4-security.js +59 -0
- package/lib/migrations/1.6.5-security.js +60 -0
- package/lib/migrations/1.6.9-update-node-info-and-certificate.js +38 -0
- package/lib/migrations/1.7.1-blocklet-setup.js +18 -0
- package/lib/migrations/1.7.12-blocklet-meta.js +51 -0
- package/lib/migrations/1.7.15-blocklet-bundle-source.js +42 -0
- package/lib/migrations/1.7.20-blocklet-component.js +41 -0
- package/lib/migrations/1.8.33-blocklet-mem-limit.js +20 -0
- package/lib/migrations/README.md +1 -1
- package/lib/migrations/index.js +6 -2
- package/lib/monitor/blocklet-runtime-monitor.js +200 -0
- package/lib/monitor/get-history-list.js +37 -0
- package/lib/monitor/node-runtime-monitor.js +228 -0
- package/lib/router/helper.js +576 -500
- package/lib/router/index.js +85 -21
- package/lib/router/manager.js +146 -187
- package/lib/states/README.md +36 -1
- package/lib/states/access-key.js +39 -17
- package/lib/states/audit-log.js +462 -0
- package/lib/states/base.js +4 -213
- package/lib/states/blocklet-extras.js +195 -138
- package/lib/states/blocklet.js +371 -110
- package/lib/states/cache.js +8 -6
- package/lib/states/challenge.js +5 -5
- package/lib/states/index.js +19 -36
- package/lib/states/migration.js +4 -4
- package/lib/states/node.js +135 -46
- package/lib/states/notification.js +22 -35
- package/lib/states/session.js +17 -9
- package/lib/states/site.js +50 -25
- package/lib/states/user.js +74 -20
- package/lib/states/webhook.js +10 -6
- package/lib/team/manager.js +124 -7
- package/lib/util/blocklet.js +1223 -246
- package/lib/util/chain.js +1 -1
- package/lib/util/default-node-config.js +5 -23
- package/lib/util/disk-monitor.js +13 -10
- package/lib/util/domain-status.js +84 -15
- package/lib/util/get-accessible-external-node-ip.js +2 -2
- package/lib/util/get-domain-for-blocklet.js +13 -0
- package/lib/util/get-meta-from-url.js +33 -0
- package/lib/util/index.js +207 -272
- package/lib/util/ip.js +6 -0
- package/lib/util/maintain.js +233 -0
- package/lib/util/public-to-store.js +85 -0
- package/lib/util/ready.js +1 -1
- package/lib/util/requirement.js +28 -9
- package/lib/util/reset-node.js +22 -7
- package/lib/util/router.js +13 -0
- package/lib/util/rpc.js +16 -0
- package/lib/util/store.js +179 -0
- package/lib/util/sysinfo.js +44 -0
- package/lib/util/ua.js +54 -0
- package/lib/validators/blocklet-extra.js +24 -0
- package/lib/validators/node.js +25 -12
- package/lib/validators/permission.js +16 -1
- package/lib/validators/role.js +17 -3
- package/lib/validators/router.js +40 -20
- package/lib/validators/trusted-passport.js +1 -0
- package/lib/validators/util.js +22 -5
- package/lib/webhook/index.js +45 -35
- package/lib/webhook/sender/index.js +5 -0
- package/lib/webhook/sender/slack/index.js +1 -1
- package/lib/webhook/sender/wallet/index.js +48 -0
- package/package.json +54 -36
- package/lib/blocklet/registry.js +0 -205
- package/lib/states/https-cert.js +0 -67
- package/lib/util/get-ip-dns-domain-for-blocklet.js +0 -19
- package/lib/util/service.js +0 -66
- package/lib/util/upgrade.js +0 -178
- /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
|
+
};
|