@abtnode/core 1.8.68-beta-500af7e5 → 1.8.69-beta-e0666d0d
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/blocklet/manager/disk.js +20 -17
- package/lib/blocklet/manager/helper/install-from-backup.js +3 -2
- package/lib/blocklet/storage/backup/audit-log.js +2 -2
- package/lib/blocklet/storage/backup/base.js +7 -8
- package/lib/blocklet/storage/backup/blocklet-extras.js +20 -33
- package/lib/blocklet/storage/backup/blocklet.js +34 -17
- package/lib/blocklet/storage/backup/blocklets.js +2 -2
- package/lib/blocklet/storage/backup/data.js +2 -2
- package/lib/blocklet/storage/backup/logs.js +2 -2
- package/lib/blocklet/storage/backup/routing-rule.js +2 -2
- package/lib/blocklet/storage/backup/spaces.js +75 -37
- package/lib/blocklet/storage/restore/base.js +16 -5
- package/lib/blocklet/storage/restore/blocklet-extras.js +24 -29
- package/lib/blocklet/storage/restore/blocklet.js +49 -0
- package/lib/blocklet/storage/restore/blocklets.js +1 -1
- package/lib/blocklet/storage/restore/logs.js +2 -2
- package/lib/blocklet/storage/restore/spaces.js +28 -21
- package/lib/event.js +10 -0
- package/lib/router/helper.js +20 -1
- package/lib/states/blocklet.js +1 -1
- package/package.json +18 -18
|
@@ -615,23 +615,23 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
615
615
|
* @memberof BlockletManager
|
|
616
616
|
*/
|
|
617
617
|
// eslint-disable-next-line no-unused-vars
|
|
618
|
-
async backupToSpaces({
|
|
618
|
+
async backupToSpaces({ appPid }, context) {
|
|
619
619
|
// add to queue
|
|
620
620
|
// const ticket = this.backupQueue.push({
|
|
621
621
|
// entity: 'blocklet',
|
|
622
622
|
// action: 'backup-to-space',
|
|
623
|
-
//
|
|
623
|
+
// appPid,
|
|
624
624
|
// context,
|
|
625
625
|
// });
|
|
626
626
|
|
|
627
627
|
// ticket.on('failed', async (err) => {
|
|
628
|
-
// logger.error('backup failed', { entity: 'blocklet',
|
|
629
|
-
// this.emit('blocklet.backup.failed', {
|
|
630
|
-
// this._createNotification(
|
|
628
|
+
// logger.error('backup failed', { entity: 'blocklet', appPid, error: err });
|
|
629
|
+
// this.emit('blocklet.backup.failed', { appPid, err });
|
|
630
|
+
// this._createNotification(appPid, {
|
|
631
631
|
// title: 'Blocklet Backup Failed',
|
|
632
632
|
// description: `Blocklet backup failed with error: ${err.message || 'queue exception'}`,
|
|
633
633
|
// entityType: 'blocklet',
|
|
634
|
-
// entityId:
|
|
634
|
+
// entityId: appPid,
|
|
635
635
|
// severity: 'error',
|
|
636
636
|
// });
|
|
637
637
|
// });
|
|
@@ -639,11 +639,10 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
639
639
|
const userDid = context.user.did;
|
|
640
640
|
const { referrer } = context;
|
|
641
641
|
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
this.emit(BlockletEvents.backupProgress, { did, message: 'Start backup...', progress: 10 });
|
|
642
|
+
const spacesBackup = new SpacesBackup({ appPid, event: this, userDid, referrer });
|
|
643
|
+
this.emit(BlockletEvents.backupProgress, { appPid, message: 'Start backup...', progress: 10, completed: false });
|
|
645
644
|
await spacesBackup.backup();
|
|
646
|
-
this.emit(BlockletEvents.backupProgress, {
|
|
645
|
+
this.emit(BlockletEvents.backupProgress, { appPid, completed: true, progress: 100 });
|
|
647
646
|
}
|
|
648
647
|
|
|
649
648
|
/**
|
|
@@ -654,8 +653,7 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
654
653
|
*/
|
|
655
654
|
// eslint-disable-next-line no-unused-vars
|
|
656
655
|
async restoreFromSpaces(input, context) {
|
|
657
|
-
|
|
658
|
-
this.emit(BlockletEvents.restoreProgress, { did: input.appDid, message: 'Start restore...' });
|
|
656
|
+
this.emit(BlockletEvents.restoreProgress, { appDid: input.appDid, message: 'Start restore...', completed: false });
|
|
659
657
|
|
|
660
658
|
const userDid = context.user.did;
|
|
661
659
|
const { referrer } = context;
|
|
@@ -663,14 +661,14 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
663
661
|
const spacesRestore = new SpacesRestore({ ...input, event: this, userDid, referrer });
|
|
664
662
|
const params = await spacesRestore.restore();
|
|
665
663
|
|
|
666
|
-
this.emit(BlockletEvents.restoreProgress, {
|
|
664
|
+
this.emit(BlockletEvents.restoreProgress, { appDid: input.appDid, message: 'Installing blocklet...' });
|
|
667
665
|
await this._installFromBackup({
|
|
668
|
-
url: `file://${spacesRestore.
|
|
666
|
+
url: `file://${spacesRestore.restoreDir}`,
|
|
669
667
|
moveDir: true,
|
|
670
668
|
...merge(...params),
|
|
671
669
|
});
|
|
672
670
|
|
|
673
|
-
this.emit(BlockletEvents.restoreProgress, {
|
|
671
|
+
this.emit(BlockletEvents.restoreProgress, { appDid: input.appDid, completed: true });
|
|
674
672
|
}
|
|
675
673
|
|
|
676
674
|
async restart({ did }, context) {
|
|
@@ -1104,7 +1102,7 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
1104
1102
|
if (willAppSkChange) {
|
|
1105
1103
|
const info = await states.node.read();
|
|
1106
1104
|
const { wallet } = getBlockletInfo(blocklet, info.sk);
|
|
1107
|
-
const migratedFrom = blocklet.migratedFrom
|
|
1105
|
+
const migratedFrom = Array.isArray(blocklet.migratedFrom) ? blocklet.migratedFrom : [];
|
|
1108
1106
|
await states.blocklet.updateBlocklet(rootDid, {
|
|
1109
1107
|
migratedFrom: [
|
|
1110
1108
|
...migratedFrom,
|
|
@@ -1113,6 +1111,11 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
1113
1111
|
});
|
|
1114
1112
|
}
|
|
1115
1113
|
|
|
1114
|
+
// Reload nginx to make sure did-space can embed content from this app
|
|
1115
|
+
if (newConfigs.find((x) => x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SPACE_ENDPOINT)?.value) {
|
|
1116
|
+
this.emit(BlockletEvents.spaceConnected, blocklet);
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1116
1119
|
// FIXME: @zhenqiang best way to handle the did document: allow all appDids to resolve
|
|
1117
1120
|
if (willAppDidChange && !skipDidDocument) {
|
|
1118
1121
|
await this._updateDidDocument(blocklet);
|
|
@@ -2117,7 +2120,7 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
2117
2120
|
// put BLOCKLET_APP_ID at root level for indexing
|
|
2118
2121
|
blocklet.appDid = appSystemEnvironments.BLOCKLET_APP_ID;
|
|
2119
2122
|
|
|
2120
|
-
if (!blocklet.migratedFrom) {
|
|
2123
|
+
if (!Array.isArray(blocklet.migratedFrom)) {
|
|
2121
2124
|
blocklet.migratedFrom = [];
|
|
2122
2125
|
}
|
|
2123
2126
|
// This can only be set once, can be used for indexing, will not change ever
|
|
@@ -2,7 +2,7 @@ const fs = require('fs-extra');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const omit = require('lodash/omit');
|
|
4
4
|
|
|
5
|
-
const { forEachBlockletSync } = require('@blocklet/meta/lib/util');
|
|
5
|
+
const { forEachBlockletSync, getBlockletAppIdList } = require('@blocklet/meta/lib/util');
|
|
6
6
|
const getBlockletInfo = require('@blocklet/meta/lib/info');
|
|
7
7
|
|
|
8
8
|
const { BLOCKLET_CONFIGURABLE_KEY } = require('@blocklet/constant');
|
|
@@ -90,8 +90,9 @@ module.exports = async ({ url, appSk, moveDir, context = {}, states, manager } =
|
|
|
90
90
|
|
|
91
91
|
// validate appDid
|
|
92
92
|
const nodeInfo = await states.node.read();
|
|
93
|
+
const appIdList = getBlockletAppIdList(state);
|
|
93
94
|
const { wallet } = getBlockletInfo({ meta: state.meta, configs: extra.configs, environments: [] }, nodeInfo.sk);
|
|
94
|
-
if (
|
|
95
|
+
if (appIdList.includes(wallet.address) === false) {
|
|
95
96
|
throw new Error('blocklet appDid is different from the previous one');
|
|
96
97
|
}
|
|
97
98
|
await checkDuplicateAppSk({ sk: wallet.secretKey, states });
|
|
@@ -19,8 +19,8 @@ class AuditLogBackup extends BaseBackup {
|
|
|
19
19
|
scope: this.blocklet.meta.did,
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
-
removeSync(join(this.
|
|
23
|
-
outputJsonSync(join(this.
|
|
22
|
+
removeSync(join(this.backupDir, this.filename));
|
|
23
|
+
outputJsonSync(join(this.backupDir, this.filename), auditLogs);
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
class BaseBackup {
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
3
|
* @type {import('./spaces').SpaceBackupInput}
|
|
5
4
|
* @memberof BaseBackup
|
|
6
5
|
*/
|
|
@@ -18,15 +17,15 @@ class BaseBackup {
|
|
|
18
17
|
* @type {string}
|
|
19
18
|
* @memberof BaseBackup
|
|
20
19
|
*/
|
|
21
|
-
|
|
20
|
+
backupDir;
|
|
22
21
|
|
|
23
22
|
/**
|
|
24
23
|
*
|
|
25
24
|
* @description spaces 的 endpoint
|
|
26
|
-
* @type {import('
|
|
25
|
+
* @type {import('./spaces').SecurityContext}
|
|
27
26
|
* @memberof BaseBackup
|
|
28
27
|
*/
|
|
29
|
-
|
|
28
|
+
securityContext;
|
|
30
29
|
|
|
31
30
|
/**
|
|
32
31
|
*
|
|
@@ -34,7 +33,7 @@ class BaseBackup {
|
|
|
34
33
|
* @type {string}
|
|
35
34
|
* @memberof BaseBackup
|
|
36
35
|
*/
|
|
37
|
-
|
|
36
|
+
serverDir;
|
|
38
37
|
|
|
39
38
|
constructor(input) {
|
|
40
39
|
this.input = input;
|
|
@@ -48,9 +47,9 @@ class BaseBackup {
|
|
|
48
47
|
*/
|
|
49
48
|
ensureParams(spacesBackup) {
|
|
50
49
|
this.blocklet = spacesBackup.blocklet;
|
|
51
|
-
this.
|
|
52
|
-
this.
|
|
53
|
-
this.
|
|
50
|
+
this.serverDir = spacesBackup.serverDir;
|
|
51
|
+
this.backupDir = spacesBackup.backupDir;
|
|
52
|
+
this.securityContext = spacesBackup.securityContext;
|
|
54
53
|
}
|
|
55
54
|
|
|
56
55
|
async export() {
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
const { removeSync,
|
|
1
|
+
const { removeSync, readFileSync, outputJsonSync } = require('fs-extra');
|
|
2
2
|
const { isEmpty, cloneDeep } = require('lodash');
|
|
3
3
|
const { join } = require('path');
|
|
4
|
-
const { Hasher } = require('@ocap/mcrypto');
|
|
5
|
-
const { toBuffer } = require('@ocap/util');
|
|
6
4
|
const security = require('@abtnode/util/lib/security');
|
|
5
|
+
|
|
7
6
|
const states = require('../../../states');
|
|
8
7
|
const { BaseBackup } = require('./base');
|
|
9
8
|
|
|
@@ -12,8 +11,8 @@ class BlockletExtrasBackup extends BaseBackup {
|
|
|
12
11
|
|
|
13
12
|
async export() {
|
|
14
13
|
const blockletExtra = await this.getBlockletExtra();
|
|
15
|
-
removeSync(join(this.
|
|
16
|
-
outputJsonSync(join(this.
|
|
14
|
+
removeSync(join(this.backupDir, this.filename));
|
|
15
|
+
outputJsonSync(join(this.backupDir, this.filename), blockletExtra);
|
|
17
16
|
}
|
|
18
17
|
|
|
19
18
|
/**
|
|
@@ -39,29 +38,29 @@ class BlockletExtrasBackup extends BaseBackup {
|
|
|
39
38
|
/**
|
|
40
39
|
*
|
|
41
40
|
* @description 清理数据并加密
|
|
42
|
-
* @param {import('@abtnode/client').BlockletState}
|
|
43
|
-
* @return {Promise<
|
|
41
|
+
* @param {import('@abtnode/client').BlockletState} raw
|
|
42
|
+
* @return {Promise<any>}
|
|
44
43
|
* @memberof BlockletExtrasBackup
|
|
45
44
|
*/
|
|
46
|
-
async cleanData(
|
|
47
|
-
const blockletExtra = cloneDeep(
|
|
45
|
+
async cleanData(raw) {
|
|
46
|
+
const blockletExtra = cloneDeep(raw);
|
|
48
47
|
|
|
49
48
|
const queue = [blockletExtra];
|
|
50
49
|
while (queue.length) {
|
|
51
|
-
const
|
|
50
|
+
const current = queue.pop();
|
|
52
51
|
|
|
53
52
|
// 删除父 blocklet 的某些数据
|
|
54
|
-
if (
|
|
55
|
-
delete
|
|
56
|
-
delete
|
|
57
|
-
delete
|
|
53
|
+
if (current._id) {
|
|
54
|
+
delete current._id;
|
|
55
|
+
delete current.createdAt;
|
|
56
|
+
delete current.updatedAt;
|
|
58
57
|
}
|
|
59
58
|
|
|
60
59
|
// 加解密
|
|
61
|
-
this.
|
|
60
|
+
this.encrypt(current.configs);
|
|
62
61
|
|
|
63
|
-
if (
|
|
64
|
-
queue.push(...
|
|
62
|
+
if (current?.children) {
|
|
63
|
+
queue.push(...current.children);
|
|
65
64
|
}
|
|
66
65
|
}
|
|
67
66
|
|
|
@@ -75,28 +74,16 @@ class BlockletExtrasBackup extends BaseBackup {
|
|
|
75
74
|
* @return {void}
|
|
76
75
|
* @memberof BlockletExtrasBackup
|
|
77
76
|
*/
|
|
78
|
-
|
|
77
|
+
encrypt(configs) {
|
|
79
78
|
if (isEmpty(configs)) {
|
|
80
79
|
return;
|
|
81
80
|
}
|
|
82
81
|
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
// 准备加解密所需的参数
|
|
86
|
-
// @see: https://github.com/ArcBlock/blocklet-server/blob/f561ba7290285f2e23dccb6d5323eb4d43c3cc3e/core/state/lib/index.js#L59
|
|
87
|
-
const dk = readFileSync(join(this.serverDataDir, '.sock'));
|
|
88
|
-
const ek = toBuffer(Hasher.SHA3.hash256(Buffer.concat([secretKey, address].map(toBuffer))));
|
|
89
|
-
|
|
82
|
+
const dk = readFileSync(join(this.serverDir, '.sock'));
|
|
90
83
|
for (const config of configs) {
|
|
91
84
|
if (config.secure) {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
// 先从 blocklet server 解密
|
|
95
|
-
// @see https://github.com/ArcBlock/blocklet-server/blob/f40338168a66893f325464cea79ae54c43f623b1/core/state/lib/blocklet/extras.js#L139
|
|
96
|
-
const decrypted = security.decrypt(encryptByServer, this.blocklet.meta.did, dk);
|
|
97
|
-
// 再用 blocklet 信息加密,然后才可以上传到 spaces
|
|
98
|
-
const encrypted = security.encrypt(decrypted, address, ek);
|
|
99
|
-
config.value = encrypted;
|
|
85
|
+
const decrypted = security.decrypt(config.value, this.blocklet.meta.did, dk);
|
|
86
|
+
config.value = this.securityContext.encrypt(decrypted);
|
|
100
87
|
}
|
|
101
88
|
}
|
|
102
89
|
}
|
|
@@ -8,45 +8,62 @@ class BlockletBackup extends BaseBackup {
|
|
|
8
8
|
|
|
9
9
|
async export() {
|
|
10
10
|
const blocklet = await this.cleanData();
|
|
11
|
-
removeSync(join(this.
|
|
12
|
-
outputJsonSync(join(this.
|
|
11
|
+
removeSync(join(this.backupDir, this.filename));
|
|
12
|
+
outputJsonSync(join(this.backupDir, this.filename), blocklet);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* @description 清理数据
|
|
17
17
|
* @see blocklet.db 中需要删除哪些字段呢? https://github.com/ArcBlock/blocklet-server/issues/6120#issuecomment-1383798348
|
|
18
|
-
* @return {Promise<
|
|
18
|
+
* @return {Promise<import('@abtnode/client').BlockletState>}
|
|
19
19
|
* @memberof BlockletBackup
|
|
20
20
|
*/
|
|
21
21
|
async cleanData() {
|
|
22
|
-
const
|
|
22
|
+
const clone = cloneDeep(this.blocklet);
|
|
23
23
|
|
|
24
24
|
/** @type {import('@abtnode/client').ComponentState[]} */
|
|
25
|
-
const queue = [
|
|
25
|
+
const queue = [clone];
|
|
26
26
|
|
|
27
27
|
// 广度优先遍历
|
|
28
28
|
while (queue.length) {
|
|
29
|
-
const
|
|
29
|
+
const current = queue.pop();
|
|
30
30
|
|
|
31
31
|
// 父组件才需要删除的属性
|
|
32
|
-
if (
|
|
33
|
-
delete
|
|
34
|
-
delete
|
|
35
|
-
delete
|
|
36
|
-
delete
|
|
32
|
+
if (current._id) {
|
|
33
|
+
delete current._id;
|
|
34
|
+
delete current.createdAt;
|
|
35
|
+
delete current.startedAt;
|
|
36
|
+
delete current.installedAt;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
// 子组件和父组件都需要删除的属性
|
|
40
|
-
delete
|
|
41
|
-
delete
|
|
42
|
-
delete
|
|
40
|
+
delete current.status;
|
|
41
|
+
delete current.ports;
|
|
42
|
+
delete current.environments;
|
|
43
43
|
|
|
44
|
-
if (
|
|
45
|
-
queue.push(...
|
|
44
|
+
if (current.children) {
|
|
45
|
+
queue.push(...current.children);
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
return
|
|
49
|
+
return this.encrypt(clone);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
*
|
|
54
|
+
* @description 清理数据并加密
|
|
55
|
+
* @param {import('@abtnode/client').BlockletState} info
|
|
56
|
+
* @memberof BlockletExtrasBackup
|
|
57
|
+
*/
|
|
58
|
+
encrypt(info) {
|
|
59
|
+
if (Array.isArray(info.migratedFrom)) {
|
|
60
|
+
info.migratedFrom = info.migratedFrom.map((x) => {
|
|
61
|
+
x.appSk = this.securityContext.encrypt(x.appSk);
|
|
62
|
+
return x;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return info;
|
|
50
67
|
}
|
|
51
68
|
}
|
|
52
69
|
|
|
@@ -12,7 +12,7 @@ class BlockletsBackup extends BaseBackup {
|
|
|
12
12
|
*/
|
|
13
13
|
async export() {
|
|
14
14
|
const blockletMetas = this.getBlockletMetas(this.blocklet);
|
|
15
|
-
const serverBlockletsDir = join(this.
|
|
15
|
+
const serverBlockletsDir = join(this.serverDir, 'blocklets');
|
|
16
16
|
|
|
17
17
|
const blockletMetasFromLocal = blockletMetas.filter(
|
|
18
18
|
(b) => !validUrl.isHttpUri(b.tarball) && !validUrl.isHttpsUri(b.tarball)
|
|
@@ -21,7 +21,7 @@ class BlockletsBackup extends BaseBackup {
|
|
|
21
21
|
const dirs = [];
|
|
22
22
|
for (const blockletMeta of blockletMetasFromLocal) {
|
|
23
23
|
const sourceDir = join(serverBlockletsDir, blockletMeta.name, blockletMeta.version);
|
|
24
|
-
const zipPath = join(this.
|
|
24
|
+
const zipPath = join(this.backupDir, 'blocklets', blockletMeta.name, `${blockletMeta.version}.zip`);
|
|
25
25
|
dirs.push({ sourceDir, zipPath });
|
|
26
26
|
}
|
|
27
27
|
|
|
@@ -9,8 +9,8 @@ class DataBackup extends BaseBackup {
|
|
|
9
9
|
* @memberof BlockletsBackup
|
|
10
10
|
*/
|
|
11
11
|
async export() {
|
|
12
|
-
const blockletDataDir = join(this.
|
|
13
|
-
const blockletBackupDataDir = join(this.
|
|
12
|
+
const blockletDataDir = join(this.serverDir, 'data', this.blocklet.meta.name);
|
|
13
|
+
const blockletBackupDataDir = join(this.backupDir, 'data');
|
|
14
14
|
|
|
15
15
|
await copy(blockletDataDir, blockletBackupDataDir, { overwrite: true });
|
|
16
16
|
}
|
|
@@ -5,10 +5,10 @@ const { BaseBackup } = require('./base');
|
|
|
5
5
|
// note: 可以不需要备份
|
|
6
6
|
class LogsBackup extends BaseBackup {
|
|
7
7
|
async export() {
|
|
8
|
-
const sourceLogsDir = join(this.
|
|
8
|
+
const sourceLogsDir = join(this.serverDir, 'logs', this.blocklet.meta.name);
|
|
9
9
|
ensureDirSync(sourceLogsDir);
|
|
10
10
|
|
|
11
|
-
const targetLogsDir = join(this.
|
|
11
|
+
const targetLogsDir = join(this.backupDir, 'logs');
|
|
12
12
|
|
|
13
13
|
await copy(sourceLogsDir, targetLogsDir, {
|
|
14
14
|
overwrite: true,
|
|
@@ -11,8 +11,8 @@ class RoutingRuleBackup extends BaseBackup {
|
|
|
11
11
|
domain: `${this.blocklet.meta.did}.blocklet-domain-group`,
|
|
12
12
|
});
|
|
13
13
|
|
|
14
|
-
removeSync(join(this.
|
|
15
|
-
outputJsonSync(join(this.
|
|
14
|
+
removeSync(join(this.backupDir, this.filename));
|
|
15
|
+
outputJsonSync(join(this.backupDir, this.filename), routingRule);
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
|
|
@@ -1,19 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @typedef {{
|
|
3
|
-
*
|
|
3
|
+
* appPid: string
|
|
4
4
|
* event: import('events').EventEmitter,
|
|
5
5
|
* userDid: string,
|
|
6
6
|
* referrer: string,
|
|
7
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
|
|
8
15
|
*/
|
|
9
16
|
|
|
10
17
|
const { isValid } = require('@arcblock/did');
|
|
11
18
|
const { BLOCKLET_CONFIGURABLE_KEY, BlockletEvents } = require('@blocklet/constant');
|
|
12
|
-
const { getBlockletInfo } = require('@blocklet/meta');
|
|
13
19
|
const { SpaceClient, BackupBlockletCommand } = require('@did-space/client');
|
|
14
20
|
const { ensureDirSync } = require('fs-extra');
|
|
15
21
|
const { isEmpty } = require('lodash');
|
|
16
22
|
const { join, basename } = require('path');
|
|
23
|
+
const { signV2 } = require('@arcblock/jwt');
|
|
24
|
+
const { Hasher } = require('@ocap/mcrypto');
|
|
25
|
+
const { toBuffer } = require('@ocap/util');
|
|
26
|
+
const { getAppName, getAppDescription } = require('@blocklet/meta/lib/util');
|
|
27
|
+
const getBlockletInfo = require('@blocklet/meta/lib/info');
|
|
28
|
+
const security = require('@abtnode/util/lib/security');
|
|
17
29
|
|
|
18
30
|
const logger = require('@abtnode/logger')('@abtnode/core:storage:backup');
|
|
19
31
|
|
|
@@ -45,7 +57,7 @@ class SpacesBackup {
|
|
|
45
57
|
* @type {string}
|
|
46
58
|
* @memberof SpacesBackup
|
|
47
59
|
*/
|
|
48
|
-
|
|
60
|
+
backupDir;
|
|
49
61
|
|
|
50
62
|
/**
|
|
51
63
|
*
|
|
@@ -53,7 +65,7 @@ class SpacesBackup {
|
|
|
53
65
|
* @type {string}
|
|
54
66
|
* @memberof SpacesBackup
|
|
55
67
|
*/
|
|
56
|
-
|
|
68
|
+
serverDir;
|
|
57
69
|
|
|
58
70
|
/**
|
|
59
71
|
*
|
|
@@ -61,15 +73,15 @@ class SpacesBackup {
|
|
|
61
73
|
* @type {string}
|
|
62
74
|
* @memberof SpacesBackup
|
|
63
75
|
*/
|
|
64
|
-
|
|
76
|
+
spaceEndpoint;
|
|
65
77
|
|
|
66
78
|
/**
|
|
67
79
|
*
|
|
68
|
-
* @description spaces
|
|
69
|
-
* @type
|
|
80
|
+
* @description spaces 安全相关的上下文
|
|
81
|
+
* @type SecurityContext
|
|
70
82
|
* @memberof SpacesBackup
|
|
71
83
|
*/
|
|
72
|
-
|
|
84
|
+
securityContext;
|
|
73
85
|
|
|
74
86
|
storages;
|
|
75
87
|
|
|
@@ -97,8 +109,8 @@ class SpacesBackup {
|
|
|
97
109
|
* @memberof SpacesBackup
|
|
98
110
|
*/
|
|
99
111
|
verify(input) {
|
|
100
|
-
if (isEmpty(input?.
|
|
101
|
-
throw new Error(`input.
|
|
112
|
+
if (isEmpty(input?.appPid) || !isValid(input?.appPid)) {
|
|
113
|
+
throw new Error(`input.appPid(${input?.appPid}) is not a valid did`);
|
|
102
114
|
}
|
|
103
115
|
}
|
|
104
116
|
|
|
@@ -114,33 +126,31 @@ class SpacesBackup {
|
|
|
114
126
|
}
|
|
115
127
|
|
|
116
128
|
async initialize() {
|
|
117
|
-
this.blocklet = await states.blocklet.
|
|
118
|
-
appDid: this.input.did,
|
|
119
|
-
});
|
|
129
|
+
this.blocklet = await states.blocklet.getBlocklet(this.input.appPid);
|
|
120
130
|
if (isEmpty(this.blocklet)) {
|
|
121
131
|
throw new Error('blocklet cannot be empty');
|
|
122
132
|
}
|
|
123
133
|
|
|
124
|
-
this.
|
|
134
|
+
this.serverDir = process.env.ABT_NODE_DATA_DIR;
|
|
135
|
+
this.backupDir = join(this.serverDir, 'tmp/backup', this.blocklet.appPid);
|
|
136
|
+
ensureDirSync(this.backupDir);
|
|
125
137
|
|
|
126
|
-
this.
|
|
127
|
-
ensureDirSync(this.blockletBackupDir);
|
|
128
|
-
|
|
129
|
-
this.spacesEndpoint = this.blocklet.environments.find(
|
|
138
|
+
this.spaceEndpoint = this.blocklet.environments.find(
|
|
130
139
|
(e) => e.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SPACE_ENDPOINT
|
|
131
140
|
)?.value;
|
|
132
|
-
if (isEmpty(this.
|
|
133
|
-
throw new Error('
|
|
141
|
+
if (isEmpty(this.spaceEndpoint)) {
|
|
142
|
+
throw new Error('spaceEndpoint cannot be empty');
|
|
134
143
|
}
|
|
135
144
|
|
|
136
|
-
this.
|
|
145
|
+
this.securityContext = await this.getSecurityContext();
|
|
137
146
|
}
|
|
138
147
|
|
|
139
148
|
async export() {
|
|
140
149
|
this.input.event.emit(BlockletEvents.backupProgress, {
|
|
141
|
-
|
|
150
|
+
appPid: this.input.appPid,
|
|
142
151
|
message: 'Preparing data for backup...',
|
|
143
152
|
progress: 15,
|
|
153
|
+
completed: false,
|
|
144
154
|
});
|
|
145
155
|
await Promise.all(
|
|
146
156
|
this.storages.map((storage) => {
|
|
@@ -149,23 +159,28 @@ class SpacesBackup {
|
|
|
149
159
|
})
|
|
150
160
|
);
|
|
151
161
|
this.input.event.emit(BlockletEvents.backupProgress, {
|
|
152
|
-
|
|
162
|
+
appPid: this.input.appPid,
|
|
153
163
|
message: 'Data ready, start backup...',
|
|
154
164
|
progress: 20,
|
|
165
|
+
completed: false,
|
|
155
166
|
});
|
|
156
167
|
}
|
|
157
168
|
|
|
158
169
|
async syncToSpaces() {
|
|
159
|
-
const wallet = await this.getBlockletWallet();
|
|
160
170
|
const spaceClient = new SpaceClient({
|
|
161
|
-
endpoint: this.
|
|
162
|
-
wallet,
|
|
171
|
+
endpoint: this.spaceEndpoint,
|
|
172
|
+
wallet: this.securityContext.signer,
|
|
173
|
+
delegation: this.securityContext.delegation,
|
|
163
174
|
});
|
|
164
175
|
|
|
165
176
|
const { errorCount, message } = await spaceClient.send(
|
|
166
177
|
new BackupBlockletCommand({
|
|
167
|
-
appDid: this.blocklet.
|
|
168
|
-
|
|
178
|
+
appDid: this.blocklet.appPid,
|
|
179
|
+
appName: getAppName(this.blocklet),
|
|
180
|
+
appDescription: getAppDescription(this.blocklet),
|
|
181
|
+
userDid: this.input.userDid,
|
|
182
|
+
referrer: this.input.referrer,
|
|
183
|
+
source: join(this.backupDir, '/'),
|
|
169
184
|
debug: true,
|
|
170
185
|
concurrency: 32,
|
|
171
186
|
retryCount: 10,
|
|
@@ -174,30 +189,53 @@ class SpacesBackup {
|
|
|
174
189
|
return object.name !== '.DS_Store';
|
|
175
190
|
},
|
|
176
191
|
onProgress: (data) => {
|
|
177
|
-
logger.info('backup progress', { appDid: this.input.
|
|
192
|
+
logger.info('backup progress', { appDid: this.input.appPid, data });
|
|
178
193
|
const percent = (data.completed * 100) / data.total;
|
|
179
194
|
this.input.event.emit(BlockletEvents.backupProgress, {
|
|
180
|
-
|
|
195
|
+
appPid: this.input.appPid,
|
|
181
196
|
message: `Uploaded file ${basename(data.key)} (${data.completed}/${data.total})`,
|
|
182
197
|
// 0.8 是因为上传文件到 spaces 占进度的 80%,+ 20 是因为需要累加之前的进度
|
|
183
198
|
progress: +Math.ceil(percent * 0.8).toFixed(2) + 20,
|
|
199
|
+
completed: false,
|
|
184
200
|
});
|
|
185
201
|
},
|
|
186
|
-
userDid: this.input.userDid,
|
|
187
|
-
referrer: this.input.referrer,
|
|
188
202
|
})
|
|
189
203
|
);
|
|
190
204
|
|
|
191
205
|
if (errorCount !== 0) {
|
|
192
|
-
throw new Error(`Sync to spaces encountered
|
|
206
|
+
throw new Error(`Sync to spaces encountered error: ${message}`);
|
|
193
207
|
}
|
|
194
208
|
}
|
|
195
209
|
|
|
196
|
-
async
|
|
197
|
-
const
|
|
210
|
+
async getSecurityContext() {
|
|
211
|
+
const blocklet = await states.blocklet.getBlocklet(this.input.appPid);
|
|
198
212
|
const nodeInfo = await states.node.read();
|
|
199
|
-
|
|
200
|
-
|
|
213
|
+
|
|
214
|
+
const { wallet, permanentWallet } = getBlockletInfo(blocklet, nodeInfo.sk);
|
|
215
|
+
|
|
216
|
+
// FIXME: @wangshijun migrated apps can not be restored, need to change core/webapp/api/routes/auth/verify-app-ownership.js
|
|
217
|
+
const { secretKey, address } = wallet; // we encrypt using latest wallet, not the permanent wallet
|
|
218
|
+
const password = toBuffer(Hasher.SHA3.hash256(Buffer.concat([secretKey, address].map(toBuffer))));
|
|
219
|
+
const encrypt = (v) => security.encrypt(v, address, password);
|
|
220
|
+
const decrypt = (v) => security.decrypt(v, address, password);
|
|
221
|
+
|
|
222
|
+
const delegation =
|
|
223
|
+
permanentWallet.address !== wallet.address
|
|
224
|
+
? signV2(permanentWallet.address, permanentWallet.secretKey, {
|
|
225
|
+
from: permanentWallet.address,
|
|
226
|
+
to: wallet.address,
|
|
227
|
+
userPk: permanentWallet.publicKey,
|
|
228
|
+
permissions: [{ role: 'DIDSpaceAgent', spaces: ['*'] }],
|
|
229
|
+
exp: Math.floor(new Date().getTime() / 1000) + 60 * 60,
|
|
230
|
+
})
|
|
231
|
+
: '';
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
signer: wallet,
|
|
235
|
+
delegation,
|
|
236
|
+
encrypt,
|
|
237
|
+
decrypt,
|
|
238
|
+
};
|
|
201
239
|
}
|
|
202
240
|
}
|
|
203
241
|
|
|
@@ -11,7 +11,7 @@ class BaseRestore {
|
|
|
11
11
|
* @type {string}
|
|
12
12
|
* @memberof BaseRestore
|
|
13
13
|
*/
|
|
14
|
-
|
|
14
|
+
restoreDir;
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
*
|
|
@@ -19,7 +19,7 @@ class BaseRestore {
|
|
|
19
19
|
* @type {string}
|
|
20
20
|
* @memberof BaseRestore
|
|
21
21
|
*/
|
|
22
|
-
|
|
22
|
+
serverDir;
|
|
23
23
|
|
|
24
24
|
constructor(input) {
|
|
25
25
|
this.input = input;
|
|
@@ -32,11 +32,12 @@ class BaseRestore {
|
|
|
32
32
|
* @memberof BaseRestore
|
|
33
33
|
*/
|
|
34
34
|
ensureParams(spaces) {
|
|
35
|
-
this.
|
|
36
|
-
this.
|
|
35
|
+
this.restoreDir = spaces.restoreDir;
|
|
36
|
+
this.serverDir = spaces.serverDir;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
// eslint-disable-next-line
|
|
40
|
+
async import(params) {
|
|
40
41
|
throw new Error('not implemented');
|
|
41
42
|
}
|
|
42
43
|
|
|
@@ -49,6 +50,16 @@ class BaseRestore {
|
|
|
49
50
|
getInstallParams() {
|
|
50
51
|
return {};
|
|
51
52
|
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Generate params for other restorers
|
|
56
|
+
*
|
|
57
|
+
* @return {object}
|
|
58
|
+
* @memberof BaseRestore
|
|
59
|
+
*/
|
|
60
|
+
getImportParams() {
|
|
61
|
+
return {};
|
|
62
|
+
}
|
|
52
63
|
}
|
|
53
64
|
|
|
54
65
|
module.exports = {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const { removeSync, outputJsonSync, readJSONSync } = require('fs-extra');
|
|
2
|
-
const { cloneDeep,
|
|
2
|
+
const { cloneDeep, isEmpty } = require('lodash');
|
|
3
3
|
const { join } = require('path');
|
|
4
4
|
const security = require('@abtnode/util/lib/security');
|
|
5
5
|
const { BLOCKLET_CONFIGURABLE_KEY } = require('@blocklet/constant');
|
|
@@ -8,77 +8,72 @@ const { BaseRestore } = require('./base');
|
|
|
8
8
|
class BlockletExtrasRestore extends BaseRestore {
|
|
9
9
|
filename = 'blocklet-extras.json';
|
|
10
10
|
|
|
11
|
-
async import() {
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
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);
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
*
|
|
19
20
|
* @description
|
|
20
|
-
* @return {
|
|
21
|
+
* @return {import('@abtnode/client').BlockletState}
|
|
21
22
|
* @memberof BlockletExtrasRestore
|
|
22
23
|
*/
|
|
23
|
-
|
|
24
|
+
getExtras() {
|
|
24
25
|
/**
|
|
25
26
|
* @type {import('@abtnode/client').BlockletState}
|
|
26
27
|
*/
|
|
27
|
-
|
|
28
|
-
return this.cleanData(blockletExtra);
|
|
28
|
+
return readJSONSync(join(this.restoreDir, this.filename));
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
32
|
*
|
|
33
33
|
* @description 清理数据并加密
|
|
34
|
-
* @param {import('@abtnode/client').BlockletState}
|
|
34
|
+
* @param {import('@abtnode/client').BlockletState} raw
|
|
35
35
|
* @memberof BlockletExtrasRestore
|
|
36
36
|
*/
|
|
37
|
-
async
|
|
38
|
-
const blockletExtra = cloneDeep(
|
|
37
|
+
async cleanExtras(raw, params) {
|
|
38
|
+
const blockletExtra = cloneDeep(raw);
|
|
39
39
|
|
|
40
40
|
const queue = [blockletExtra];
|
|
41
41
|
while (queue.length) {
|
|
42
|
-
const
|
|
42
|
+
const current = queue.pop();
|
|
43
43
|
|
|
44
44
|
// 加解密
|
|
45
|
-
this.
|
|
45
|
+
this.decryptConfigs(current.configs, params);
|
|
46
46
|
|
|
47
|
-
if (
|
|
48
|
-
queue.push(...
|
|
47
|
+
if (current?.children) {
|
|
48
|
+
queue.push(...current.children);
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
|
-
|
|
52
|
-
return blockletExtra;
|
|
53
51
|
}
|
|
54
52
|
|
|
55
53
|
/**
|
|
56
54
|
*
|
|
57
55
|
* @description 解密加密的数据
|
|
58
56
|
* @param {import('@abtnode/client').ConfigEntry[]} configs
|
|
57
|
+
* @param {object} params
|
|
59
58
|
* @return {void}
|
|
60
59
|
* @memberof BlockletExtrasRestore
|
|
61
60
|
*/
|
|
62
|
-
|
|
63
|
-
if (
|
|
61
|
+
decryptConfigs(configs, params) {
|
|
62
|
+
if (isEmpty(configs)) {
|
|
64
63
|
return;
|
|
65
64
|
}
|
|
66
65
|
|
|
67
|
-
const {
|
|
66
|
+
const { password } = this.input;
|
|
68
67
|
for (const config of configs) {
|
|
69
68
|
if (config.secure) {
|
|
70
|
-
|
|
71
|
-
const decrypted = security.decrypt(encrypted, appDid, Buffer.from(password));
|
|
72
|
-
config.value = decrypted;
|
|
69
|
+
config.value = security.decrypt(config.value, params.salt, password);
|
|
73
70
|
}
|
|
74
71
|
}
|
|
75
72
|
|
|
76
|
-
const
|
|
77
|
-
if (
|
|
78
|
-
|
|
73
|
+
const skConfig = configs.find((x) => x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SK);
|
|
74
|
+
if (skConfig) {
|
|
75
|
+
this.appSk = skConfig.value;
|
|
79
76
|
}
|
|
80
|
-
|
|
81
|
-
this.appSk = config.value;
|
|
82
77
|
}
|
|
83
78
|
|
|
84
79
|
getInstallParams() {
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const { removeSync, outputJsonSync, readJSONSync } = require('fs-extra');
|
|
2
|
+
const { join } = require('path');
|
|
3
|
+
const security = require('@abtnode/util/lib/security');
|
|
4
|
+
const { BaseRestore } = require('./base');
|
|
5
|
+
|
|
6
|
+
class BlockletRestore extends BaseRestore {
|
|
7
|
+
filename = 'blocklet.json';
|
|
8
|
+
|
|
9
|
+
async import(params) {
|
|
10
|
+
const blocklet = this.decrypt(this.getBlocklet(), params);
|
|
11
|
+
removeSync(join(this.restoreDir, this.filename));
|
|
12
|
+
outputJsonSync(join(this.restoreDir, this.filename), blocklet);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
*
|
|
17
|
+
* @description
|
|
18
|
+
* @return {import('@abtnode/client').BlockletState}
|
|
19
|
+
* @memberof BlockletRestore
|
|
20
|
+
*/
|
|
21
|
+
getBlocklet() {
|
|
22
|
+
return readJSONSync(join(this.restoreDir, this.filename));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
*
|
|
27
|
+
* @description 解密加密的数据
|
|
28
|
+
* @param {import('@abtnode/client').BlockletState} blocklet
|
|
29
|
+
* @return {import('@abtnode/client').BlockletState}
|
|
30
|
+
* @memberof BlockletRestore
|
|
31
|
+
*/
|
|
32
|
+
decrypt(blocklet, params) {
|
|
33
|
+
const { password } = this.input;
|
|
34
|
+
if (Array.isArray(blocklet.migratedFrom)) {
|
|
35
|
+
blocklet.migratedFrom = blocklet.migratedFrom.map((x) => {
|
|
36
|
+
x.appSk = security.decrypt(x.appSk, params.salt, password);
|
|
37
|
+
return x;
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return blocklet;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
getImportParams() {
|
|
45
|
+
return { salt: this.getBlocklet().appDid };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = { BlockletRestore };
|
|
@@ -8,7 +8,7 @@ class BlockletsRestore extends BaseRestore {
|
|
|
8
8
|
filename = 'blocklets';
|
|
9
9
|
|
|
10
10
|
async import() {
|
|
11
|
-
const blockletsDir = join(this.
|
|
11
|
+
const blockletsDir = join(this.restoreDir, this.filename);
|
|
12
12
|
|
|
13
13
|
// blockletsDir 可以不存在, 因为还原的 blocklet 可能所有的组件都是来自 store 的
|
|
14
14
|
if (!existsSync(blockletsDir)) {
|
|
@@ -7,13 +7,13 @@ class LogsRestore extends BaseRestore {
|
|
|
7
7
|
filename = 'logs.zip';
|
|
8
8
|
|
|
9
9
|
async import() {
|
|
10
|
-
const blockletZipPath = join(this.
|
|
10
|
+
const blockletZipPath = join(this.restoreDir, this.filename);
|
|
11
11
|
|
|
12
12
|
if (!existsSync(blockletZipPath)) {
|
|
13
13
|
throw new Error(`file not found: ${blockletZipPath}`);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
await zipToDir(blockletZipPath, join(this.
|
|
16
|
+
await zipToDir(blockletZipPath, join(this.restoreDir, 'logs'));
|
|
17
17
|
removeSync(blockletZipPath);
|
|
18
18
|
}
|
|
19
19
|
}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
const validUrl = require('valid-url');
|
|
15
|
+
const merge = require('lodash/merge');
|
|
15
16
|
const { BlockletEvents } = require('@blocklet/constant');
|
|
16
17
|
const { SpaceClient, RestoreBlockletCommand } = require('@did-space/client');
|
|
17
18
|
const { ensureDirSync, existsSync, rmdirSync } = require('fs-extra');
|
|
@@ -20,6 +21,7 @@ const { join, basename } = require('path');
|
|
|
20
21
|
const logger = require('@abtnode/logger')('@abtnode/core:storage:restore');
|
|
21
22
|
|
|
22
23
|
const { BlockletExtrasRestore } = require('./blocklet-extras');
|
|
24
|
+
const { BlockletRestore } = require('./blocklet');
|
|
23
25
|
const { BlockletsRestore } = require('./blocklets');
|
|
24
26
|
|
|
25
27
|
class SpacesRestore {
|
|
@@ -35,7 +37,7 @@ class SpacesRestore {
|
|
|
35
37
|
* @type {string}
|
|
36
38
|
* @memberof SpacesRestore
|
|
37
39
|
*/
|
|
38
|
-
|
|
40
|
+
restoreDir;
|
|
39
41
|
|
|
40
42
|
/**
|
|
41
43
|
*
|
|
@@ -43,7 +45,7 @@ class SpacesRestore {
|
|
|
43
45
|
* @type {string}
|
|
44
46
|
* @memberof SpacesRestore
|
|
45
47
|
*/
|
|
46
|
-
|
|
48
|
+
serverDir;
|
|
47
49
|
|
|
48
50
|
storages;
|
|
49
51
|
|
|
@@ -55,7 +57,11 @@ class SpacesRestore {
|
|
|
55
57
|
constructor(input) {
|
|
56
58
|
this.verify(input);
|
|
57
59
|
this.input = input;
|
|
58
|
-
this.storages = [
|
|
60
|
+
this.storages = [
|
|
61
|
+
new BlockletExtrasRestore(this.input),
|
|
62
|
+
new BlockletRestore(this.input),
|
|
63
|
+
new BlockletsRestore(this.input),
|
|
64
|
+
];
|
|
59
65
|
}
|
|
60
66
|
|
|
61
67
|
/**
|
|
@@ -68,26 +74,28 @@ class SpacesRestore {
|
|
|
68
74
|
if (!validUrl.isWebUri(input.endpoint)) {
|
|
69
75
|
throw new Error(`endpoint(${input.endpoint}) must be a WebUri`);
|
|
70
76
|
}
|
|
77
|
+
if (!input.endpoint.includes(input.appDid)) {
|
|
78
|
+
throw new Error(`endpoint and blocklet.appDid(${input.appDid}) do not match`);
|
|
79
|
+
}
|
|
71
80
|
}
|
|
72
81
|
|
|
73
82
|
async initialize() {
|
|
74
|
-
this.
|
|
75
|
-
|
|
76
|
-
if (
|
|
77
|
-
|
|
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
|
+
rmdirSync(this.restoreDir, { recursive: true });
|
|
78
87
|
}
|
|
88
|
+
ensureDirSync(this.restoreDir);
|
|
79
89
|
|
|
80
|
-
this.
|
|
81
|
-
if (existsSync(this.blockletRestoreDir)) {
|
|
82
|
-
rmdirSync(this.blockletRestoreDir, { recursive: true });
|
|
83
|
-
}
|
|
84
|
-
ensureDirSync(this.blockletRestoreDir);
|
|
90
|
+
this.storages.map((x) => x.ensureParams(this));
|
|
85
91
|
}
|
|
86
92
|
|
|
87
93
|
async restore() {
|
|
88
94
|
await this.initialize();
|
|
89
95
|
await this.syncFromSpaces();
|
|
90
|
-
|
|
96
|
+
|
|
97
|
+
const params = await Promise.all(this.storages.map((x) => x.getImportParams()));
|
|
98
|
+
await this.import(merge(...params));
|
|
91
99
|
|
|
92
100
|
return this.storages.map((x) => x.getInstallParams());
|
|
93
101
|
}
|
|
@@ -104,14 +112,14 @@ class SpacesRestore {
|
|
|
104
112
|
const { errorCount, message } = await spaceClient.send(
|
|
105
113
|
new RestoreBlockletCommand({
|
|
106
114
|
appDid: this.input.appDid,
|
|
107
|
-
target: join(this.
|
|
115
|
+
target: join(this.restoreDir, '/'),
|
|
108
116
|
debug: true,
|
|
109
117
|
concurrency: 32,
|
|
110
118
|
retryCount: 10,
|
|
111
119
|
onProgress: (data) => {
|
|
112
120
|
logger.info('restore progress', { appDid: this.input.appDid, data });
|
|
113
121
|
this.input.event.emit(BlockletEvents.restoreProgress, {
|
|
114
|
-
|
|
122
|
+
appDid: this.input.appDid,
|
|
115
123
|
message: `Downloaded file ${basename(data.key)} (${data.completed}/${data.total})`,
|
|
116
124
|
});
|
|
117
125
|
},
|
|
@@ -122,23 +130,22 @@ class SpacesRestore {
|
|
|
122
130
|
);
|
|
123
131
|
|
|
124
132
|
if (errorCount !== 0) {
|
|
125
|
-
throw new Error(`Sync from spaces encountered
|
|
133
|
+
throw new Error(`Sync from spaces encountered error: ${message}`);
|
|
126
134
|
}
|
|
127
135
|
}
|
|
128
136
|
|
|
129
|
-
async import() {
|
|
137
|
+
async import(params) {
|
|
130
138
|
this.input.event.emit(BlockletEvents.restoreProgress, {
|
|
131
|
-
|
|
139
|
+
appDid: this.input.appDid,
|
|
132
140
|
message: 'Preparing to import data...',
|
|
133
141
|
});
|
|
134
142
|
await Promise.all(
|
|
135
143
|
this.storages.map((storage) => {
|
|
136
|
-
storage.
|
|
137
|
-
return storage.import();
|
|
144
|
+
return storage.import(params);
|
|
138
145
|
})
|
|
139
146
|
);
|
|
140
147
|
this.input.event.emit(BlockletEvents.restoreProgress, {
|
|
141
|
-
|
|
148
|
+
appDid: this.input.appDid,
|
|
142
149
|
message: 'Importing data successfully...',
|
|
143
150
|
});
|
|
144
151
|
}
|
package/lib/event.js
CHANGED
|
@@ -242,6 +242,14 @@ module.exports = ({
|
|
|
242
242
|
);
|
|
243
243
|
|
|
244
244
|
logger.info('take snapshot after updated blocklet app', { event: eventName, did: blocklet.meta.did, hash });
|
|
245
|
+
} else if (BlockletEvents.spaceConnected === eventName) {
|
|
246
|
+
nodeState
|
|
247
|
+
.read()
|
|
248
|
+
.then((info) => handleRouting(info))
|
|
249
|
+
.catch((err) => {
|
|
250
|
+
logger.error('Reload gateway failed on blocklet.connectedSpace', { error: err });
|
|
251
|
+
});
|
|
252
|
+
logger.info('Reload gateway after blocklet connected to space', { event: eventName, did: blocklet.appDid });
|
|
245
253
|
}
|
|
246
254
|
|
|
247
255
|
if (
|
|
@@ -307,6 +315,8 @@ module.exports = ({
|
|
|
307
315
|
|
|
308
316
|
BlockletEvents.backupProgress,
|
|
309
317
|
BlockletEvents.restoreProgress,
|
|
318
|
+
|
|
319
|
+
BlockletEvents.spaceConnected,
|
|
310
320
|
].forEach((eventName) => {
|
|
311
321
|
listen(blockletManager, eventName, handleBlockletEvent);
|
|
312
322
|
});
|
package/lib/router/helper.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const fs = require('fs-extra');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const tar = require('tar');
|
|
6
|
+
const isUrl = require('is-url');
|
|
6
7
|
const get = require('lodash/get');
|
|
7
8
|
const cloneDeep = require('lodash/cloneDeep');
|
|
8
9
|
const isEqual = require('lodash/isEqual');
|
|
@@ -43,6 +44,7 @@ const {
|
|
|
43
44
|
BLOCKLET_INTERFACE_PUBLIC,
|
|
44
45
|
BLOCKLET_INTERFACE_WELLKNOWN,
|
|
45
46
|
BLOCKLET_INTERFACE_TYPE_WELLKNOWN,
|
|
47
|
+
BLOCKLET_CONFIGURABLE_KEY,
|
|
46
48
|
BlockletEvents,
|
|
47
49
|
BLOCKLET_MODES,
|
|
48
50
|
} = require('@blocklet/constant');
|
|
@@ -122,7 +124,7 @@ const addCorsToSite = (site, rawUrl) => {
|
|
|
122
124
|
site.corsAllowedOrigins.push(url.hostname);
|
|
123
125
|
}
|
|
124
126
|
} catch (err) {
|
|
125
|
-
|
|
127
|
+
console.error('addCorsToSite', err);
|
|
126
128
|
}
|
|
127
129
|
};
|
|
128
130
|
|
|
@@ -336,6 +338,22 @@ const ensureCorsForWebWallet = async (sites) => {
|
|
|
336
338
|
return sites;
|
|
337
339
|
};
|
|
338
340
|
|
|
341
|
+
const ensureCorsForDidSpace = async (sites = [], blocklets) => {
|
|
342
|
+
return sites.map((site) => {
|
|
343
|
+
const blocklet = blocklets.find((x) => x.meta.did === site.blockletDid);
|
|
344
|
+
if (blocklet) {
|
|
345
|
+
const endpoint = blocklet.environments.find(
|
|
346
|
+
(x) => x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SPACE_ENDPOINT
|
|
347
|
+
);
|
|
348
|
+
if (endpoint && isUrl(endpoint.value)) {
|
|
349
|
+
addCorsToSite(site, endpoint.value);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return site;
|
|
354
|
+
});
|
|
355
|
+
};
|
|
356
|
+
|
|
339
357
|
const filterSitesForRemovedBlocklets = async (sites = [], blocklets) => {
|
|
340
358
|
return sites.filter((site) => {
|
|
341
359
|
if (!site.domain.endsWith(BLOCKLET_SITE_GROUP_SUFFIX)) {
|
|
@@ -407,6 +425,7 @@ const ensureLatestInfo = async (sites = [], { withDefaultCors = true } = {}) =>
|
|
|
407
425
|
result = await ensureBlockletCache(result, blocklets);
|
|
408
426
|
result = await ensureWellknownRule(result);
|
|
409
427
|
result = await ensureCorsForWebWallet(result);
|
|
428
|
+
result = await ensureCorsForDidSpace(result, blocklets);
|
|
410
429
|
result = await ensureLatestInterfaceInfo(result);
|
|
411
430
|
|
|
412
431
|
return result;
|
package/lib/states/blocklet.js
CHANGED
|
@@ -49,7 +49,7 @@ const formatBlocklet = (blocklet, phase, dek) => {
|
|
|
49
49
|
return;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
(b.migratedFrom
|
|
52
|
+
(Array.isArray(b.migratedFrom) ? b.migratedFrom : []).forEach((x) => {
|
|
53
53
|
if (phase === 'onUpdate' && isHex(x.appSk) === true) {
|
|
54
54
|
x.appSk = security.encrypt(x.appSk, b.meta.did, dek);
|
|
55
55
|
}
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "1.8.
|
|
6
|
+
"version": "1.8.69-beta-e0666d0d",
|
|
7
7
|
"description": "",
|
|
8
8
|
"main": "lib/index.js",
|
|
9
9
|
"files": [
|
|
@@ -19,18 +19,18 @@
|
|
|
19
19
|
"author": "wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)",
|
|
20
20
|
"license": "MIT",
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@abtnode/auth": "1.8.
|
|
23
|
-
"@abtnode/certificate-manager": "1.8.
|
|
24
|
-
"@abtnode/constant": "1.8.
|
|
25
|
-
"@abtnode/cron": "1.8.
|
|
26
|
-
"@abtnode/db": "1.8.
|
|
27
|
-
"@abtnode/logger": "1.8.
|
|
28
|
-
"@abtnode/queue": "1.8.
|
|
29
|
-
"@abtnode/rbac": "1.8.
|
|
30
|
-
"@abtnode/router-provider": "1.8.
|
|
31
|
-
"@abtnode/static-server": "1.8.
|
|
32
|
-
"@abtnode/timemachine": "1.8.
|
|
33
|
-
"@abtnode/util": "1.8.
|
|
22
|
+
"@abtnode/auth": "1.8.69-beta-e0666d0d",
|
|
23
|
+
"@abtnode/certificate-manager": "1.8.69-beta-e0666d0d",
|
|
24
|
+
"@abtnode/constant": "1.8.69-beta-e0666d0d",
|
|
25
|
+
"@abtnode/cron": "1.8.69-beta-e0666d0d",
|
|
26
|
+
"@abtnode/db": "1.8.69-beta-e0666d0d",
|
|
27
|
+
"@abtnode/logger": "1.8.69-beta-e0666d0d",
|
|
28
|
+
"@abtnode/queue": "1.8.69-beta-e0666d0d",
|
|
29
|
+
"@abtnode/rbac": "1.8.69-beta-e0666d0d",
|
|
30
|
+
"@abtnode/router-provider": "1.8.69-beta-e0666d0d",
|
|
31
|
+
"@abtnode/static-server": "1.8.69-beta-e0666d0d",
|
|
32
|
+
"@abtnode/timemachine": "1.8.69-beta-e0666d0d",
|
|
33
|
+
"@abtnode/util": "1.8.69-beta-e0666d0d",
|
|
34
34
|
"@arcblock/did": "1.18.57",
|
|
35
35
|
"@arcblock/did-motif": "^1.1.10",
|
|
36
36
|
"@arcblock/did-util": "1.18.57",
|
|
@@ -38,10 +38,10 @@
|
|
|
38
38
|
"@arcblock/jwt": "^1.18.57",
|
|
39
39
|
"@arcblock/pm2-events": "^0.0.5",
|
|
40
40
|
"@arcblock/vc": "1.18.57",
|
|
41
|
-
"@blocklet/constant": "1.8.
|
|
42
|
-
"@blocklet/meta": "1.8.
|
|
43
|
-
"@blocklet/sdk": "1.8.
|
|
44
|
-
"@did-space/client": "0.
|
|
41
|
+
"@blocklet/constant": "1.8.69-beta-e0666d0d",
|
|
42
|
+
"@blocklet/meta": "1.8.69-beta-e0666d0d",
|
|
43
|
+
"@blocklet/sdk": "1.8.69-beta-e0666d0d",
|
|
44
|
+
"@did-space/client": "0.2.25",
|
|
45
45
|
"@fidm/x509": "^1.2.1",
|
|
46
46
|
"@ocap/mcrypto": "1.18.57",
|
|
47
47
|
"@ocap/util": "1.18.57",
|
|
@@ -89,5 +89,5 @@
|
|
|
89
89
|
"express": "^4.18.2",
|
|
90
90
|
"jest": "^27.5.1"
|
|
91
91
|
},
|
|
92
|
-
"gitHead": "
|
|
92
|
+
"gitHead": "acf0373591eaa3aff76483edc4e648afc543f1f7"
|
|
93
93
|
}
|