@abtnode/core 1.16.0-beta-1d6c582e → 1.16.0-beta-62b42401
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/team.js +51 -0
- package/lib/blocklet/manager/disk.js +129 -28
- package/lib/blocklet/manager/helper/install-application-from-backup.js +101 -16
- package/lib/blocklet/manager/helper/install-application-from-dev.js +7 -1
- package/lib/blocklet/manager/helper/install-application-from-general.js +23 -17
- package/lib/blocklet/manager/helper/migrate-application-to-struct-v2.js +31 -6
- package/lib/blocklet/storage/backup/base.js +64 -10
- package/lib/blocklet/storage/backup/blocklet.js +1 -1
- package/lib/blocklet/storage/backup/disk.js +132 -0
- package/lib/blocklet/storage/backup/spaces.js +8 -36
- package/lib/blocklet/storage/restore/base.js +13 -6
- package/lib/blocklet/storage/restore/blocklet-extras.js +5 -3
- package/lib/blocklet/storage/restore/blocklet.js +1 -1
- package/lib/blocklet/storage/restore/disk.js +104 -0
- package/lib/blocklet/storage/restore/spaces.js +10 -6
- package/lib/blocklet/storage/utils/disk.js +61 -0
- package/lib/event.js +7 -1
- package/lib/index.js +4 -2
- package/lib/states/audit-log.js +3 -0
- package/lib/states/user.js +88 -1
- package/lib/util/blocklet.js +4 -0
- package/package.json +26 -26
|
@@ -1,6 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {{
|
|
3
|
+
* appDid: string
|
|
4
|
+
* event: import('events').EventEmitter,
|
|
5
|
+
* }} BaseBackupInput
|
|
6
|
+
*
|
|
7
|
+
* @typedef {{
|
|
8
|
+
* encrypt: (v: string) => string,
|
|
9
|
+
* decrypt: (v: string) => string,
|
|
10
|
+
* }} BaseSecurityContext
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const { Hasher } = require('@ocap/mcrypto');
|
|
14
|
+
const { toBuffer } = require('@ocap/util');
|
|
15
|
+
const getBlockletInfo = require('@blocklet/meta/lib/info');
|
|
16
|
+
const security = require('@abtnode/util/lib/security');
|
|
17
|
+
|
|
1
18
|
class BaseBackup {
|
|
2
19
|
/**
|
|
3
|
-
* @type {
|
|
20
|
+
* @type {BaseBackupInput}
|
|
4
21
|
* @memberof BaseBackup
|
|
5
22
|
*/
|
|
6
23
|
input;
|
|
@@ -21,8 +38,8 @@ class BaseBackup {
|
|
|
21
38
|
|
|
22
39
|
/**
|
|
23
40
|
*
|
|
24
|
-
* @description
|
|
25
|
-
* @type {
|
|
41
|
+
* @description 安全相关的上下文
|
|
42
|
+
* @type {BaseSecurityContext}
|
|
26
43
|
* @memberof BaseBackup
|
|
27
44
|
*/
|
|
28
45
|
securityContext;
|
|
@@ -41,20 +58,57 @@ class BaseBackup {
|
|
|
41
58
|
|
|
42
59
|
/**
|
|
43
60
|
*
|
|
44
|
-
*
|
|
45
|
-
* @param {import('./spaces').SpacesBackup} spacesBackup
|
|
61
|
+
* @param {BaseBackup} backup
|
|
46
62
|
* @memberof BaseBackup
|
|
47
63
|
*/
|
|
48
|
-
ensureParams(
|
|
49
|
-
this.blocklet =
|
|
50
|
-
this.serverDir =
|
|
51
|
-
this.backupDir =
|
|
52
|
-
this.securityContext =
|
|
64
|
+
ensureParams(backup) {
|
|
65
|
+
this.blocklet = backup.blocklet;
|
|
66
|
+
this.serverDir = backup.serverDir;
|
|
67
|
+
this.backupDir = backup.backupDir;
|
|
68
|
+
this.securityContext = backup.securityContext;
|
|
53
69
|
}
|
|
54
70
|
|
|
55
71
|
async export() {
|
|
56
72
|
throw new Error('not implemented');
|
|
57
73
|
}
|
|
74
|
+
|
|
75
|
+
async _getSecurityContext(states) {
|
|
76
|
+
const blocklet = await states.blocklet.getBlocklet(this.input.appDid);
|
|
77
|
+
const nodeInfo = await states.node.read();
|
|
78
|
+
|
|
79
|
+
const { wallet } = getBlockletInfo(blocklet, nodeInfo.sk);
|
|
80
|
+
|
|
81
|
+
const { secretKey, address } = wallet; // we encrypt using latest wallet, not the permanent wallet
|
|
82
|
+
const password = toBuffer(Hasher.SHA3.hash256(Buffer.concat([secretKey, address].map(toBuffer))));
|
|
83
|
+
const encrypt = (v) => security.encrypt(v, address, password);
|
|
84
|
+
const decrypt = (v) => security.decrypt(v, address, password);
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
signer: wallet,
|
|
88
|
+
delegation: '',
|
|
89
|
+
encrypt,
|
|
90
|
+
decrypt,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
*
|
|
96
|
+
* @param {BaseBackup} dataBackup
|
|
97
|
+
* @param {Array<BaseBackup>} storages
|
|
98
|
+
* @memberof BaseBackup
|
|
99
|
+
*/
|
|
100
|
+
async _exportData(dataBackup, storages) {
|
|
101
|
+
// @note: dataBackup 需要先于 blockletBackup 执行,并且 blockletBackup 与其他 backup的执行可以是无序的
|
|
102
|
+
dataBackup.ensureParams(this);
|
|
103
|
+
await dataBackup.export();
|
|
104
|
+
|
|
105
|
+
await Promise.all(
|
|
106
|
+
storages.map((storage) => {
|
|
107
|
+
storage.ensureParams(this);
|
|
108
|
+
return storage.export();
|
|
109
|
+
})
|
|
110
|
+
);
|
|
111
|
+
}
|
|
58
112
|
}
|
|
59
113
|
|
|
60
114
|
module.exports = {
|
|
@@ -66,7 +66,7 @@ class BlockletBackup extends BaseBackup {
|
|
|
66
66
|
* @memberof BlockletExtrasBackup
|
|
67
67
|
*/
|
|
68
68
|
encrypt(info) {
|
|
69
|
-
if (Array.isArray(info
|
|
69
|
+
if (Array.isArray(info?.migratedFrom)) {
|
|
70
70
|
info.migratedFrom = info.migratedFrom.map((x) => {
|
|
71
71
|
x.appSk = this.securityContext.encrypt(x.appSk);
|
|
72
72
|
return x;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const { isValid } = require('@arcblock/did');
|
|
3
|
+
const { ensureDirSync } = require('fs-extra');
|
|
4
|
+
const { isEmpty } = require('lodash');
|
|
5
|
+
const { join } = require('path');
|
|
6
|
+
const { getAppName } = require('@blocklet/meta/lib/util');
|
|
7
|
+
|
|
8
|
+
const states = require('../../../states');
|
|
9
|
+
const { getBackupDirs } = require('../utils/disk');
|
|
10
|
+
const { BaseBackup } = require('./base');
|
|
11
|
+
const { AuditLogBackup } = require('./audit-log');
|
|
12
|
+
const { BlockletBackup } = require('./blocklet');
|
|
13
|
+
const { BlockletExtrasBackup } = require('./blocklet-extras');
|
|
14
|
+
const { BlockletsBackup } = require('./blocklets');
|
|
15
|
+
const { DataBackup } = require('./data');
|
|
16
|
+
const { RoutingRuleBackup } = require('./routing-rule');
|
|
17
|
+
|
|
18
|
+
class DiskBackup extends BaseBackup {
|
|
19
|
+
/**
|
|
20
|
+
*
|
|
21
|
+
* @type {import('./base').BaseBackupInput}
|
|
22
|
+
* @memberof DiskBackup
|
|
23
|
+
*/
|
|
24
|
+
input;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @description blocklet state 对象
|
|
28
|
+
* @type {import('@abtnode/client').BlockletState}
|
|
29
|
+
* @memberof DiskBackup
|
|
30
|
+
*/
|
|
31
|
+
blocklet;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @type {string}
|
|
35
|
+
* @memberof DiskBackup
|
|
36
|
+
*/
|
|
37
|
+
backupDir;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
*
|
|
41
|
+
* @description server 的数据目录
|
|
42
|
+
* @type {string}
|
|
43
|
+
* @memberof DiskBackup
|
|
44
|
+
*/
|
|
45
|
+
serverDir;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
*
|
|
49
|
+
* @type {import('./base').BaseSecurityContext}
|
|
50
|
+
* @memberof DiskBackup
|
|
51
|
+
*/
|
|
52
|
+
securityContext;
|
|
53
|
+
|
|
54
|
+
storages;
|
|
55
|
+
|
|
56
|
+
dataBackup;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
*
|
|
60
|
+
* @param {import('./base').BaseBackupInput} input
|
|
61
|
+
* @memberof DiskBackup
|
|
62
|
+
*/
|
|
63
|
+
constructor(input) {
|
|
64
|
+
super(input);
|
|
65
|
+
this.verify(input);
|
|
66
|
+
this.input = input;
|
|
67
|
+
this.storages = [
|
|
68
|
+
new AuditLogBackup(this.input),
|
|
69
|
+
new BlockletBackup(this.input),
|
|
70
|
+
new BlockletsBackup(this.input),
|
|
71
|
+
new BlockletExtrasBackup(this.input),
|
|
72
|
+
new RoutingRuleBackup(this.input),
|
|
73
|
+
];
|
|
74
|
+
this.dataBackup = new DataBackup(this.input);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* @param {import('./base').BaseBackupInput} input
|
|
79
|
+
* @returns {void}
|
|
80
|
+
* @memberof DiskBackup
|
|
81
|
+
*/
|
|
82
|
+
verify(input) {
|
|
83
|
+
if (isEmpty(input?.appDid) || !isValid(input?.appDid)) {
|
|
84
|
+
throw new Error(`input.appDid(${input?.appDid}) is not a valid did`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
*
|
|
90
|
+
* @returns {Promise<void>}
|
|
91
|
+
* @memberof DiskBackup
|
|
92
|
+
*/
|
|
93
|
+
async backup() {
|
|
94
|
+
await this.initialize();
|
|
95
|
+
await this.addMeta();
|
|
96
|
+
await this.export();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async initialize() {
|
|
100
|
+
this.blocklet = await states.blocklet.getBlocklet(this.input.appDid);
|
|
101
|
+
if (isEmpty(this.blocklet)) {
|
|
102
|
+
throw new Error('blocklet cannot be empty');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
this.serverDir = process.env.ABT_NODE_DATA_DIR;
|
|
106
|
+
const { baseBackupDir, backupDir } = getBackupDirs(this.serverDir, this.blocklet.appDid);
|
|
107
|
+
this.baseBackupDir = baseBackupDir;
|
|
108
|
+
this.backupDir = backupDir;
|
|
109
|
+
ensureDirSync(this.backupDir);
|
|
110
|
+
|
|
111
|
+
this.securityContext = await this._getSecurityContext(states);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async export() {
|
|
115
|
+
return this._exportData(this.dataBackup, this.storages);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async addMeta() {
|
|
119
|
+
const meta = {
|
|
120
|
+
appDid: this.blocklet.appDid,
|
|
121
|
+
appPid: this.blocklet.appPid,
|
|
122
|
+
name: getAppName(this.blocklet),
|
|
123
|
+
createdAt: Date.now(),
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
await fs.writeJSON(join(this.baseBackupDir, 'meta.json'), meta);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
module.exports = {
|
|
131
|
+
DiskBackup,
|
|
132
|
+
};
|
|
@@ -20,15 +20,12 @@ const { SpaceClient, BackupBlockletCommand } = require('@did-space/client');
|
|
|
20
20
|
const { ensureDirSync } = require('fs-extra');
|
|
21
21
|
const { isEmpty } = require('lodash');
|
|
22
22
|
const { join, basename } = require('path');
|
|
23
|
-
const { Hasher } = require('@ocap/mcrypto');
|
|
24
|
-
const { toBuffer } = require('@ocap/util');
|
|
25
23
|
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
24
|
|
|
29
25
|
const logger = require('@abtnode/logger')('@abtnode/core:storage:backup');
|
|
30
26
|
|
|
31
27
|
const states = require('../../../states');
|
|
28
|
+
const { BaseBackup } = require('./base');
|
|
32
29
|
const { AuditLogBackup } = require('./audit-log');
|
|
33
30
|
const { BlockletBackup } = require('./blocklet');
|
|
34
31
|
const { BlockletExtrasBackup } = require('./blocklet-extras');
|
|
@@ -36,7 +33,7 @@ const { BlockletsBackup } = require('./blocklets');
|
|
|
36
33
|
const { DataBackup } = require('./data');
|
|
37
34
|
const { RoutingRuleBackup } = require('./routing-rule');
|
|
38
35
|
|
|
39
|
-
class SpacesBackup {
|
|
36
|
+
class SpacesBackup extends BaseBackup {
|
|
40
37
|
/**
|
|
41
38
|
*
|
|
42
39
|
* @type {SpaceBackupInput}
|
|
@@ -84,12 +81,15 @@ class SpacesBackup {
|
|
|
84
81
|
|
|
85
82
|
storages;
|
|
86
83
|
|
|
84
|
+
dataBackup;
|
|
85
|
+
|
|
87
86
|
/**
|
|
88
87
|
*
|
|
89
88
|
* @param {SpaceBackupInput} input
|
|
90
89
|
* @memberof SpacesBackup
|
|
91
90
|
*/
|
|
92
91
|
constructor(input) {
|
|
92
|
+
super(input);
|
|
93
93
|
this.verify(input);
|
|
94
94
|
this.input = input;
|
|
95
95
|
this.storages = [
|
|
@@ -99,6 +99,7 @@ class SpacesBackup {
|
|
|
99
99
|
new BlockletExtrasBackup(this.input),
|
|
100
100
|
new RoutingRuleBackup(this.input),
|
|
101
101
|
];
|
|
102
|
+
this.dataBackup = new DataBackup(this.input);
|
|
102
103
|
}
|
|
103
104
|
|
|
104
105
|
/**
|
|
@@ -140,7 +141,7 @@ class SpacesBackup {
|
|
|
140
141
|
throw new Error('spaceEndpoint cannot be empty');
|
|
141
142
|
}
|
|
142
143
|
|
|
143
|
-
this.securityContext = await this.
|
|
144
|
+
this.securityContext = await this._getSecurityContext(states);
|
|
144
145
|
}
|
|
145
146
|
|
|
146
147
|
async export() {
|
|
@@ -151,17 +152,7 @@ class SpacesBackup {
|
|
|
151
152
|
completed: false,
|
|
152
153
|
});
|
|
153
154
|
|
|
154
|
-
|
|
155
|
-
const dataBackup = new DataBackup(this.input);
|
|
156
|
-
dataBackup.ensureParams(this);
|
|
157
|
-
await dataBackup.export();
|
|
158
|
-
|
|
159
|
-
await Promise.all(
|
|
160
|
-
this.storages.map((storage) => {
|
|
161
|
-
storage.ensureParams(this);
|
|
162
|
-
return storage.export();
|
|
163
|
-
})
|
|
164
|
-
);
|
|
155
|
+
await this._exportData(this.dataBackup, this.storages);
|
|
165
156
|
|
|
166
157
|
this.input.event.emit(BlockletEvents.backupProgress, {
|
|
167
158
|
appDid: this.input.appDid,
|
|
@@ -220,25 +211,6 @@ class SpacesBackup {
|
|
|
220
211
|
throw new Error(`Sync to spaces encountered error: ${message}`);
|
|
221
212
|
}
|
|
222
213
|
}
|
|
223
|
-
|
|
224
|
-
async getSecurityContext() {
|
|
225
|
-
const blocklet = await states.blocklet.getBlocklet(this.input.appDid);
|
|
226
|
-
const nodeInfo = await states.node.read();
|
|
227
|
-
|
|
228
|
-
const { wallet } = getBlockletInfo(blocklet, nodeInfo.sk);
|
|
229
|
-
|
|
230
|
-
const { secretKey, address } = wallet; // we encrypt using latest wallet, not the permanent wallet
|
|
231
|
-
const password = toBuffer(Hasher.SHA3.hash256(Buffer.concat([secretKey, address].map(toBuffer))));
|
|
232
|
-
const encrypt = (v) => security.encrypt(v, address, password);
|
|
233
|
-
const decrypt = (v) => security.decrypt(v, address, password);
|
|
234
|
-
|
|
235
|
-
return {
|
|
236
|
-
signer: wallet,
|
|
237
|
-
delegation: '',
|
|
238
|
-
encrypt,
|
|
239
|
-
decrypt,
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
214
|
}
|
|
243
215
|
|
|
244
216
|
module.exports = {
|
|
@@ -1,13 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {{
|
|
3
|
+
* appDid: string; // --> appDid
|
|
4
|
+
* password: Buffer; // derived from (appSk, appDid)
|
|
5
|
+
* event: import('events').EventEmitter,
|
|
6
|
+
* }} BaseRestoreInput
|
|
7
|
+
*/
|
|
8
|
+
|
|
1
9
|
class BaseRestore {
|
|
2
10
|
/**
|
|
3
11
|
*
|
|
4
|
-
* @type {
|
|
12
|
+
* @type {BaseRestoreInput}
|
|
5
13
|
* @memberof BaseRestore
|
|
6
14
|
*/
|
|
7
15
|
input;
|
|
8
16
|
|
|
9
17
|
/**
|
|
10
|
-
* @description 当前 blocklet 的数据目录
|
|
11
18
|
* @type {string}
|
|
12
19
|
* @memberof BaseRestore
|
|
13
20
|
*/
|
|
@@ -28,12 +35,12 @@ class BaseRestore {
|
|
|
28
35
|
/**
|
|
29
36
|
*
|
|
30
37
|
*
|
|
31
|
-
* @param {
|
|
38
|
+
* @param {BaseRestore} restore
|
|
32
39
|
* @memberof BaseRestore
|
|
33
40
|
*/
|
|
34
|
-
ensureParams(
|
|
35
|
-
this.restoreDir =
|
|
36
|
-
this.serverDir =
|
|
41
|
+
ensureParams(restore) {
|
|
42
|
+
this.restoreDir = restore.restoreDir;
|
|
43
|
+
this.serverDir = restore.serverDir;
|
|
37
44
|
}
|
|
38
45
|
|
|
39
46
|
// eslint-disable-next-line
|
|
@@ -9,8 +9,7 @@ class BlockletExtrasRestore extends BaseRestore {
|
|
|
9
9
|
filename = 'blocklet-extras.json';
|
|
10
10
|
|
|
11
11
|
async import(params) {
|
|
12
|
-
const extras = this.getExtras();
|
|
13
|
-
this.cleanExtras(extras, params);
|
|
12
|
+
const extras = this.cleanExtras(this.getExtras(), params);
|
|
14
13
|
removeSync(join(this.restoreDir, this.filename));
|
|
15
14
|
outputJsonSync(join(this.restoreDir, this.filename), extras);
|
|
16
15
|
}
|
|
@@ -32,9 +31,10 @@ class BlockletExtrasRestore extends BaseRestore {
|
|
|
32
31
|
*
|
|
33
32
|
* @description 清理数据并加密
|
|
34
33
|
* @param {import('@abtnode/client').BlockletState} raw
|
|
34
|
+
* @returns {import('@abtnode/client').BlockletState}
|
|
35
35
|
* @memberof BlockletExtrasRestore
|
|
36
36
|
*/
|
|
37
|
-
|
|
37
|
+
cleanExtras(raw, params) {
|
|
38
38
|
const blockletExtra = cloneDeep(raw);
|
|
39
39
|
|
|
40
40
|
const queue = [blockletExtra];
|
|
@@ -48,6 +48,8 @@ class BlockletExtrasRestore extends BaseRestore {
|
|
|
48
48
|
queue.push(...current.children);
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
|
+
|
|
52
|
+
return blockletExtra;
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
/**
|
|
@@ -33,7 +33,7 @@ class BlockletRestore extends BaseRestore {
|
|
|
33
33
|
*/
|
|
34
34
|
decrypt(blocklet, params) {
|
|
35
35
|
const { password } = this.input;
|
|
36
|
-
if (Array.isArray(blocklet
|
|
36
|
+
if (Array.isArray(blocklet?.migratedFrom)) {
|
|
37
37
|
blocklet.migratedFrom = blocklet.migratedFrom.map((x) => {
|
|
38
38
|
x.appSk = security.decrypt(x.appSk, params.salt, password);
|
|
39
39
|
return x;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
const { isValid } = require('@arcblock/did');
|
|
2
|
+
const { isEmpty } = require('lodash');
|
|
3
|
+
const merge = require('lodash/merge');
|
|
4
|
+
const { ensureDirSync, existsSync, rmSync, copy } = require('fs-extra');
|
|
5
|
+
|
|
6
|
+
const { getBackupDirs } = require('../utils/disk');
|
|
7
|
+
const { BaseRestore } = require('./base');
|
|
8
|
+
const { BlockletExtrasRestore } = require('./blocklet-extras');
|
|
9
|
+
const { BlockletRestore } = require('./blocklet');
|
|
10
|
+
const { BlockletsRestore } = require('./blocklets');
|
|
11
|
+
|
|
12
|
+
class DiskRestore extends BaseRestore {
|
|
13
|
+
/**
|
|
14
|
+
*
|
|
15
|
+
* @type {import('./base').BaseRestoreInput}
|
|
16
|
+
* @memberof DiskRestore
|
|
17
|
+
*/
|
|
18
|
+
input;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @type {string}
|
|
22
|
+
* @memberof DiskRestore
|
|
23
|
+
*/
|
|
24
|
+
restoreDir;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
*
|
|
28
|
+
* @description server 的数据目录
|
|
29
|
+
* @type {string}
|
|
30
|
+
* @memberof DiskRestore
|
|
31
|
+
*/
|
|
32
|
+
serverDir;
|
|
33
|
+
|
|
34
|
+
storages;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
*
|
|
38
|
+
* @param {import('./base').BaseRestoreInput} input
|
|
39
|
+
* @memberof DiskRestore
|
|
40
|
+
*/
|
|
41
|
+
constructor(input) {
|
|
42
|
+
super(input);
|
|
43
|
+
this.verify(input);
|
|
44
|
+
this.input = input;
|
|
45
|
+
this.storages = [
|
|
46
|
+
new BlockletExtrasRestore(this.input),
|
|
47
|
+
new BlockletRestore(this.input),
|
|
48
|
+
new BlockletsRestore(this.input),
|
|
49
|
+
];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
*
|
|
54
|
+
* @param {import('./base').BaseRestoreInput} input
|
|
55
|
+
* @returns {void}
|
|
56
|
+
* @memberof DiskRestore
|
|
57
|
+
*/
|
|
58
|
+
verify(input) {
|
|
59
|
+
if (isEmpty(input?.appDid) || !isValid(input?.appDid)) {
|
|
60
|
+
throw new Error(`input.appDid(${input?.appDid}) is not a valid did`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async initialize() {
|
|
65
|
+
this.serverDir = process.env.ABT_NODE_DATA_DIR;
|
|
66
|
+
this.restoreDir = getBackupDirs(this.serverDir, this.input.appDid).restoreDir;
|
|
67
|
+
if (existsSync(this.restoreDir)) {
|
|
68
|
+
rmSync(this.restoreDir, { recursive: true });
|
|
69
|
+
}
|
|
70
|
+
ensureDirSync(this.restoreDir);
|
|
71
|
+
|
|
72
|
+
this.storages.map((x) => x.ensureParams(this));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async restore() {
|
|
76
|
+
await this.initialize();
|
|
77
|
+
await this.syncFromBackupDir();
|
|
78
|
+
|
|
79
|
+
const params = await Promise.all(this.storages.map((x) => x.getImportParams()));
|
|
80
|
+
await this.import(merge(...params));
|
|
81
|
+
|
|
82
|
+
return this.storages.map((x) => x.getInstallParams());
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async syncFromBackupDir() {
|
|
86
|
+
const { appDid } = this.input;
|
|
87
|
+
|
|
88
|
+
const { backupDir } = getBackupDirs(this.serverDir, appDid);
|
|
89
|
+
|
|
90
|
+
await copy(backupDir, this.restoreDir);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async import(params) {
|
|
94
|
+
await Promise.all(
|
|
95
|
+
this.storages.map((storage) => {
|
|
96
|
+
return storage.import(params);
|
|
97
|
+
})
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
module.exports = {
|
|
103
|
+
DiskRestore,
|
|
104
|
+
};
|
|
@@ -13,18 +13,19 @@
|
|
|
13
13
|
|
|
14
14
|
const validUrl = require('valid-url');
|
|
15
15
|
const merge = require('lodash/merge');
|
|
16
|
-
const { BlockletEvents } = require('@blocklet/constant');
|
|
16
|
+
const { BlockletEvents, RESTORE_PROGRESS_STATUS } = require('@blocklet/constant');
|
|
17
17
|
const { SpaceClient, RestoreBlockletCommand } = require('@did-space/client');
|
|
18
18
|
const { ensureDirSync, existsSync, rmSync } = require('fs-extra');
|
|
19
|
-
const { join
|
|
19
|
+
const { join } = require('path');
|
|
20
20
|
|
|
21
21
|
const logger = require('@abtnode/logger')('@abtnode/core:storage:restore');
|
|
22
22
|
|
|
23
|
+
const { BaseRestore } = require('./base');
|
|
23
24
|
const { BlockletExtrasRestore } = require('./blocklet-extras');
|
|
24
25
|
const { BlockletRestore } = require('./blocklet');
|
|
25
26
|
const { BlockletsRestore } = require('./blocklets');
|
|
26
27
|
|
|
27
|
-
class SpacesRestore {
|
|
28
|
+
class SpacesRestore extends BaseRestore {
|
|
28
29
|
/**
|
|
29
30
|
*
|
|
30
31
|
* @type {SpaceRestoreInput}
|
|
@@ -55,6 +56,7 @@ class SpacesRestore {
|
|
|
55
56
|
* @memberof SpacesRestore
|
|
56
57
|
*/
|
|
57
58
|
constructor(input) {
|
|
59
|
+
super(input);
|
|
58
60
|
this.verify(input);
|
|
59
61
|
this.input = input;
|
|
60
62
|
this.storages = [
|
|
@@ -120,7 +122,8 @@ class SpacesRestore {
|
|
|
120
122
|
logger.info('restore progress', { appDid: this.input.appDid, data });
|
|
121
123
|
this.input.event.emit(BlockletEvents.restoreProgress, {
|
|
122
124
|
appDid: this.input.appDid,
|
|
123
|
-
|
|
125
|
+
status: RESTORE_PROGRESS_STATUS.downloading,
|
|
126
|
+
data,
|
|
124
127
|
});
|
|
125
128
|
},
|
|
126
129
|
|
|
@@ -137,8 +140,9 @@ class SpacesRestore {
|
|
|
137
140
|
async import(params) {
|
|
138
141
|
this.input.event.emit(BlockletEvents.restoreProgress, {
|
|
139
142
|
appDid: this.input.appDid,
|
|
140
|
-
|
|
143
|
+
status: RESTORE_PROGRESS_STATUS.importData,
|
|
141
144
|
});
|
|
145
|
+
|
|
142
146
|
await Promise.all(
|
|
143
147
|
this.storages.map((storage) => {
|
|
144
148
|
return storage.import(params);
|
|
@@ -146,7 +150,7 @@ class SpacesRestore {
|
|
|
146
150
|
);
|
|
147
151
|
this.input.event.emit(BlockletEvents.restoreProgress, {
|
|
148
152
|
appDid: this.input.appDid,
|
|
149
|
-
|
|
153
|
+
status: RESTORE_PROGRESS_STATUS.importDataSuccess,
|
|
150
154
|
});
|
|
151
155
|
}
|
|
152
156
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
|
|
4
|
+
const logger = require('@abtnode/logger')('@abtnode/core:storage:utils:disk');
|
|
5
|
+
|
|
6
|
+
const backupDirName = '_abtnode/backup';
|
|
7
|
+
const restoreDirName = 'tmp/restore-disk';
|
|
8
|
+
|
|
9
|
+
const getBackupList = (dataDir) => {
|
|
10
|
+
const baseBackupDir = path.join(dataDir, backupDirName);
|
|
11
|
+
const backupList = [];
|
|
12
|
+
const appDidList = fs.readdirSync(baseBackupDir);
|
|
13
|
+
|
|
14
|
+
appDidList.forEach((appDid) => {
|
|
15
|
+
const metaFile = path.join(baseBackupDir, appDid, 'meta.json');
|
|
16
|
+
if (fs.existsSync(metaFile)) {
|
|
17
|
+
try {
|
|
18
|
+
const meta = fs.readJsonSync(metaFile);
|
|
19
|
+
if (meta.appDid === appDid) {
|
|
20
|
+
backupList.push(meta);
|
|
21
|
+
}
|
|
22
|
+
} catch (error) {
|
|
23
|
+
logger.error('read meta.json error', error);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
return backupList;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const removeBackup = async (dataDir, appDid) => {
|
|
32
|
+
try {
|
|
33
|
+
const baseBackupDir = path.join(dataDir, backupDirName);
|
|
34
|
+
const backupDir = path.join(baseBackupDir, appDid);
|
|
35
|
+
if (fs.existsSync(backupDir)) {
|
|
36
|
+
await fs.remove(backupDir);
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
return false;
|
|
40
|
+
} catch (error) {
|
|
41
|
+
logger.error('remove backup error', error);
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const getBackupDirs = (serverDir, appDid) => {
|
|
47
|
+
const baseBackupDir = path.join(serverDir, 'data', backupDirName, appDid);
|
|
48
|
+
const backupDir = path.join(baseBackupDir, appDid);
|
|
49
|
+
const restoreDir = path.join(serverDir, restoreDirName, appDid);
|
|
50
|
+
return {
|
|
51
|
+
baseBackupDir,
|
|
52
|
+
backupDir,
|
|
53
|
+
restoreDir,
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
module.exports = {
|
|
58
|
+
getBackupList,
|
|
59
|
+
removeBackup,
|
|
60
|
+
getBackupDirs,
|
|
61
|
+
};
|
package/lib/event.js
CHANGED
|
@@ -257,7 +257,13 @@ module.exports = ({
|
|
|
257
257
|
}
|
|
258
258
|
|
|
259
259
|
if (
|
|
260
|
-
|
|
260
|
+
[
|
|
261
|
+
BlockletEvents.started,
|
|
262
|
+
BlockletEvents.startFailed,
|
|
263
|
+
BlockletEvents.stopped,
|
|
264
|
+
BlockletEvents.reloaded,
|
|
265
|
+
BlockletEvents.statusChange,
|
|
266
|
+
].includes(eventName) &&
|
|
261
267
|
blocklet.status &&
|
|
262
268
|
!isBeforeInstalled(blocklet.status)
|
|
263
269
|
) {
|
package/lib/index.js
CHANGED
|
@@ -230,11 +230,12 @@ function ABTNode(options) {
|
|
|
230
230
|
deleteBlockletProcess: blockletManager.deleteProcess.bind(blockletManager),
|
|
231
231
|
configPublicToStore: blockletManager.configPublicToStore.bind(blockletManager),
|
|
232
232
|
configNavigations: blockletManager.configNavigations.bind(blockletManager),
|
|
233
|
+
configOAuth: blockletManager.configOAuth.bind(blockletManager),
|
|
233
234
|
updateWhoCanAccess: blockletManager.updateWhoCanAccess.bind(blockletManager),
|
|
234
235
|
updateComponentTitle: blockletManager.updateComponentTitle.bind(blockletManager),
|
|
235
236
|
updateComponentMountPoint: blockletManager.updateComponentMountPoint.bind(blockletManager),
|
|
236
|
-
|
|
237
|
-
|
|
237
|
+
backupBlocklet: blockletManager.backup.bind(blockletManager),
|
|
238
|
+
restoreBlocklet: blockletManager.restore.bind(blockletManager),
|
|
238
239
|
migrateApplicationToStructV2: blockletManager.migrateApplicationToStructV2.bind(blockletManager),
|
|
239
240
|
|
|
240
241
|
// For diagnose purpose
|
|
@@ -243,6 +244,7 @@ function ABTNode(options) {
|
|
|
243
244
|
updateBlockletStatus: states.blocklet.setBlockletStatus.bind(states.blocklet),
|
|
244
245
|
|
|
245
246
|
getBlocklets: blockletManager.list.bind(blockletManager),
|
|
247
|
+
getBlockletsFromBackup: blockletManager.listBackups.bind(blockletManager),
|
|
246
248
|
getBlocklet: blockletManager.detail.bind(blockletManager),
|
|
247
249
|
getBlockletDiff: blockletManager.diff.bind(blockletManager),
|
|
248
250
|
hasBlocklet: blockletManager.hasBlocklet.bind(blockletManager),
|