@abtnode/core 1.16.0-beta-8ee536d7 → 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/install-component-from-upload.js +1 -1
- package/lib/blocklet/manager/helper/migrate-application-to-struct-v2.js +32 -7
- package/lib/blocklet/manager/helper/upgrade-components.js +6 -2
- package/lib/blocklet/storage/backup/base.js +64 -10
- package/lib/blocklet/storage/backup/blocklet.js +64 -3
- package/lib/blocklet/storage/backup/disk.js +132 -0
- package/lib/blocklet/storage/backup/spaces.js +11 -33
- 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 +37 -0
- package/package.json +27 -27
|
@@ -6,7 +6,7 @@ const Client = require('@ocap/client');
|
|
|
6
6
|
|
|
7
7
|
const logger = require('@abtnode/logger')('@abtnode/core:migrate-application-to-struct-v2');
|
|
8
8
|
|
|
9
|
-
const { forEachBlockletSync, getSharedConfigObj, getChainInfo } = require('@blocklet/meta/lib/util');
|
|
9
|
+
const { forEachBlockletSync, forEachChildSync, getSharedConfigObj, getChainInfo } = require('@blocklet/meta/lib/util');
|
|
10
10
|
const { SLOT_FOR_IP_DNS_SITE } = require('@abtnode/constant');
|
|
11
11
|
|
|
12
12
|
const {
|
|
@@ -91,6 +91,31 @@ const fillBlockletData = (data, app, id) => {
|
|
|
91
91
|
|
|
92
92
|
const appSystemFiles = ['logo.svg', 'rbac.db', 'session.db', 'user.db', '.assets', BLOCKLET_UPLOADS_DIR];
|
|
93
93
|
|
|
94
|
+
const getChainHost = (blocklet) => {
|
|
95
|
+
if (!blocklet) {
|
|
96
|
+
return 'none';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const chainInfo = getChainInfo(blocklet.configObj);
|
|
100
|
+
if (chainInfo.host !== 'none') {
|
|
101
|
+
return chainInfo.host;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
let childChainHost;
|
|
105
|
+
forEachChildSync(blocklet, (b) => {
|
|
106
|
+
if (childChainHost) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const childChainInfo = getChainInfo(b.configObj);
|
|
111
|
+
if (childChainInfo.host !== 'none') {
|
|
112
|
+
childChainHost = childChainInfo.host;
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
return childChainHost || 'none';
|
|
117
|
+
};
|
|
118
|
+
|
|
94
119
|
const migrateAppOnChain = async (blocklet, oldSk, newSk) => {
|
|
95
120
|
logger.info('Preparing for on-chain migration', { did: blocklet.meta.did });
|
|
96
121
|
if (!oldSk) {
|
|
@@ -104,13 +129,13 @@ const migrateAppOnChain = async (blocklet, oldSk, newSk) => {
|
|
|
104
129
|
}
|
|
105
130
|
|
|
106
131
|
// ensure chain host
|
|
107
|
-
const
|
|
108
|
-
if (
|
|
132
|
+
const chainHost = getChainHost(blocklet);
|
|
133
|
+
if (!chainHost || chainHost === 'none') {
|
|
109
134
|
logger.info('on-chain migration aborted because CHAIN_HOST is empty', { did: blocklet.meta.did });
|
|
110
135
|
return;
|
|
111
136
|
}
|
|
112
137
|
|
|
113
|
-
logger.info('on-chain migration for chain ', { did: blocklet.meta.did, host:
|
|
138
|
+
logger.info('on-chain migration for chain ', { did: blocklet.meta.did, host: chainHost });
|
|
114
139
|
|
|
115
140
|
// ensure account changed
|
|
116
141
|
const type = blocklet.configObj.BLOCKLET_WALLET_TYPE;
|
|
@@ -122,7 +147,7 @@ const migrateAppOnChain = async (blocklet, oldSk, newSk) => {
|
|
|
122
147
|
}
|
|
123
148
|
|
|
124
149
|
// ensure old account exist on chain
|
|
125
|
-
const client = new Client(
|
|
150
|
+
const client = new Client(chainHost);
|
|
126
151
|
const oldResult = await client.getAccountState({ address: oldWallet.address });
|
|
127
152
|
if (!oldResult.state) {
|
|
128
153
|
logger.info('on-chain migration aborted because oldSk not declared on chain', { did: blocklet.meta.did });
|
|
@@ -250,7 +275,7 @@ const migrateApplicationToStructV2 = async ({ did, appSk: newAppSk, context = {}
|
|
|
250
275
|
// add root component to blockletData
|
|
251
276
|
const { source, deployedFrom } = component;
|
|
252
277
|
let bundleSource;
|
|
253
|
-
if (source === BlockletSource.
|
|
278
|
+
if (source === BlockletSource.registry && deployedFrom && component.meta.bundleName) {
|
|
254
279
|
bundleSource = {
|
|
255
280
|
store: component.deployedFrom,
|
|
256
281
|
name: component.meta.bundleName,
|
|
@@ -447,4 +472,4 @@ const migrateApplicationToStructV2 = async ({ did, appSk: newAppSk, context = {}
|
|
|
447
472
|
});
|
|
448
473
|
};
|
|
449
474
|
|
|
450
|
-
module.exports = { migrateApplicationToStructV2, sortMoveListBySrc };
|
|
475
|
+
module.exports = { migrateApplicationToStructV2, sortMoveListBySrc, getChainHost };
|
|
@@ -13,6 +13,7 @@ const {
|
|
|
13
13
|
checkStructVersion,
|
|
14
14
|
checkVersionCompatibility,
|
|
15
15
|
validateBlocklet,
|
|
16
|
+
getFixedBundleSource,
|
|
16
17
|
} = require('../../../util/blocklet');
|
|
17
18
|
|
|
18
19
|
const check = async ({ did, states }) => {
|
|
@@ -24,7 +25,10 @@ const check = async ({ did, states }) => {
|
|
|
24
25
|
const newChildren = [];
|
|
25
26
|
|
|
26
27
|
for (const child of newBlocklet.children || []) {
|
|
27
|
-
|
|
28
|
+
// There may be dirty data without bundleSource but with source and deployedFrom
|
|
29
|
+
const bundleSource = getFixedBundleSource(child);
|
|
30
|
+
|
|
31
|
+
if (bundleSource) {
|
|
28
32
|
const {
|
|
29
33
|
staticComponents: [newChild],
|
|
30
34
|
dynamicComponents,
|
|
@@ -32,7 +36,7 @@ const check = async ({ did, states }) => {
|
|
|
32
36
|
meta: {
|
|
33
37
|
staticComponents: [
|
|
34
38
|
{
|
|
35
|
-
source:
|
|
39
|
+
source: bundleSource,
|
|
36
40
|
name: child.meta.name,
|
|
37
41
|
title: child.meta.title,
|
|
38
42
|
mountPoint: child.mountPoint,
|
|
@@ -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 = {
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
const { removeSync, outputJsonSync } = require('fs-extra');
|
|
1
|
+
const { removeSync, outputJsonSync, createWriteStream, createReadStream } = require('fs-extra');
|
|
2
2
|
const { cloneDeep } = require('lodash');
|
|
3
|
-
const { join } = require('path');
|
|
3
|
+
const { join, basename } = require('path');
|
|
4
|
+
const { BLOCKLET_CONFIGURABLE_KEY } = require('@blocklet/constant');
|
|
5
|
+
const isEmpty = require('lodash/isEmpty');
|
|
6
|
+
const streamToPromise = require('stream-to-promise');
|
|
7
|
+
const axios = require('@abtnode/util/lib/axios');
|
|
8
|
+
const isUrl = require('is-url');
|
|
9
|
+
const { getLogoUrl } = require('@abtnode/util/lib/logo');
|
|
4
10
|
const { BaseBackup } = require('./base');
|
|
5
11
|
|
|
6
12
|
class BlockletBackup extends BaseBackup {
|
|
@@ -8,6 +14,10 @@ class BlockletBackup extends BaseBackup {
|
|
|
8
14
|
|
|
9
15
|
async export() {
|
|
10
16
|
const blocklet = await this.cleanData();
|
|
17
|
+
|
|
18
|
+
const targetLogoPath = await this.writeLogoFile();
|
|
19
|
+
blocklet.meta.appLogo = basename(targetLogoPath);
|
|
20
|
+
|
|
11
21
|
removeSync(join(this.backupDir, this.filename));
|
|
12
22
|
outputJsonSync(join(this.backupDir, this.filename), blocklet);
|
|
13
23
|
}
|
|
@@ -56,7 +66,7 @@ class BlockletBackup extends BaseBackup {
|
|
|
56
66
|
* @memberof BlockletExtrasBackup
|
|
57
67
|
*/
|
|
58
68
|
encrypt(info) {
|
|
59
|
-
if (Array.isArray(info
|
|
69
|
+
if (Array.isArray(info?.migratedFrom)) {
|
|
60
70
|
info.migratedFrom = info.migratedFrom.map((x) => {
|
|
61
71
|
x.appSk = this.securityContext.encrypt(x.appSk);
|
|
62
72
|
return x;
|
|
@@ -65,6 +75,57 @@ class BlockletBackup extends BaseBackup {
|
|
|
65
75
|
|
|
66
76
|
return info;
|
|
67
77
|
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
*
|
|
81
|
+
*
|
|
82
|
+
* @param {string} target
|
|
83
|
+
* @returns {Promise<string>}
|
|
84
|
+
* @memberof DataBackup
|
|
85
|
+
*/
|
|
86
|
+
async writeLogoFile() {
|
|
87
|
+
const customLogoSquareUrl = this.blocklet.environments.find(
|
|
88
|
+
(e) => e.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_LOGO_SQUARE
|
|
89
|
+
)?.value;
|
|
90
|
+
const appDir = this.blocklet.environments.find((e) => e.key === 'BLOCKLET_APP_DIR')?.value;
|
|
91
|
+
const logo = this.blocklet?.meta?.logo;
|
|
92
|
+
const defaultLogoPath = join(this.serverDir, 'data', this.blocklet.meta.name, 'logo.svg');
|
|
93
|
+
|
|
94
|
+
const logoUrl = await getLogoUrl({
|
|
95
|
+
customLogoSquareUrl,
|
|
96
|
+
appDir,
|
|
97
|
+
logo,
|
|
98
|
+
defaultLogoPath,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const logoStream = await this.getLogoStream(logoUrl);
|
|
102
|
+
const targetLogoPath = join(this.backupDir, 'data', basename(logoUrl));
|
|
103
|
+
await streamToPromise(logoStream.pipe(createWriteStream(targetLogoPath)));
|
|
104
|
+
|
|
105
|
+
return targetLogoPath;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
*
|
|
110
|
+
*
|
|
111
|
+
* @param {string} logoUrl
|
|
112
|
+
* @returns {Promise<NodeJS.ReadStream>}
|
|
113
|
+
* @memberof DataBackup
|
|
114
|
+
*/
|
|
115
|
+
async getLogoStream(logoUrl) {
|
|
116
|
+
if (isEmpty(logoUrl)) {
|
|
117
|
+
throw new Error(`logoUrl(${logoUrl}) cannot be empty`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (isUrl(logoUrl)) {
|
|
121
|
+
const res = await axios.get(logoUrl, {
|
|
122
|
+
responseType: 'stream',
|
|
123
|
+
});
|
|
124
|
+
return res.data;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return createReadStream(logoUrl);
|
|
128
|
+
}
|
|
68
129
|
}
|
|
69
130
|
|
|
70
131
|
module.exports = { BlockletBackup };
|
|
@@ -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 = [
|
|
@@ -98,8 +98,8 @@ class SpacesBackup {
|
|
|
98
98
|
new BlockletsBackup(this.input),
|
|
99
99
|
new BlockletExtrasBackup(this.input),
|
|
100
100
|
new RoutingRuleBackup(this.input),
|
|
101
|
-
new DataBackup(this.input),
|
|
102
101
|
];
|
|
102
|
+
this.dataBackup = new DataBackup(this.input);
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
/**
|
|
@@ -141,7 +141,7 @@ class SpacesBackup {
|
|
|
141
141
|
throw new Error('spaceEndpoint cannot be empty');
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
this.securityContext = await this.
|
|
144
|
+
this.securityContext = await this._getSecurityContext(states);
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
async export() {
|
|
@@ -151,12 +151,9 @@ class SpacesBackup {
|
|
|
151
151
|
progress: 15,
|
|
152
152
|
completed: false,
|
|
153
153
|
});
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
return storage.export();
|
|
158
|
-
})
|
|
159
|
-
);
|
|
154
|
+
|
|
155
|
+
await this._exportData(this.dataBackup, this.storages);
|
|
156
|
+
|
|
160
157
|
this.input.event.emit(BlockletEvents.backupProgress, {
|
|
161
158
|
appDid: this.input.appDid,
|
|
162
159
|
message: 'Data ready, start backup...',
|
|
@@ -201,7 +198,7 @@ class SpacesBackup {
|
|
|
201
198
|
const percent = (data.completed * 100) / data.total;
|
|
202
199
|
this.input.event.emit(BlockletEvents.backupProgress, {
|
|
203
200
|
appDid: this.input.appDid,
|
|
204
|
-
message: `
|
|
201
|
+
message: `Uploading file ${basename(data.key)} (${data.completed}/${data.total})`,
|
|
205
202
|
// 0.8 是因为上传文件到 spaces 占进度的 80%,+ 20 是因为需要累加之前的进度
|
|
206
203
|
progress: +Math.ceil(percent * 0.8).toFixed(2) + 20,
|
|
207
204
|
completed: false,
|
|
@@ -214,25 +211,6 @@ class SpacesBackup {
|
|
|
214
211
|
throw new Error(`Sync to spaces encountered error: ${message}`);
|
|
215
212
|
}
|
|
216
213
|
}
|
|
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
214
|
}
|
|
237
215
|
|
|
238
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;
|