@abtnode/core 1.15.17 → 1.16.0-beta-b16cb035
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/api/node.js +67 -69
- package/lib/api/team.js +386 -55
- package/lib/blocklet/downloader/blocklet-downloader.js +226 -0
- package/lib/blocklet/downloader/bundle-downloader.js +272 -0
- package/lib/blocklet/downloader/constants.js +3 -0
- package/lib/blocklet/downloader/resolve-download.js +199 -0
- package/lib/blocklet/extras.js +83 -26
- package/lib/blocklet/hooks.js +18 -65
- package/lib/blocklet/manager/base.js +10 -16
- package/lib/blocklet/manager/disk.js +1679 -1566
- package/lib/blocklet/manager/helper/install-application-from-backup.js +177 -0
- package/lib/blocklet/manager/helper/install-application-from-dev.js +94 -0
- package/lib/blocklet/manager/helper/install-application-from-general.js +188 -0
- package/lib/blocklet/manager/helper/install-component-from-dev.js +84 -0
- package/lib/blocklet/manager/helper/install-component-from-upload.js +181 -0
- package/lib/blocklet/manager/helper/install-component-from-url.js +173 -0
- package/lib/blocklet/manager/helper/migrate-application-to-struct-v2.js +450 -0
- package/lib/blocklet/manager/helper/rollback-cache.js +41 -0
- package/lib/blocklet/manager/helper/upgrade-components.js +152 -0
- package/lib/blocklet/migration.js +30 -52
- package/lib/blocklet/storage/backup/audit-log.js +27 -0
- package/lib/blocklet/storage/backup/base.js +62 -0
- package/lib/blocklet/storage/backup/blocklet-extras.js +92 -0
- package/lib/blocklet/storage/backup/blocklet.js +70 -0
- package/lib/blocklet/storage/backup/blocklets.js +74 -0
- package/lib/blocklet/storage/backup/data.js +19 -0
- package/lib/blocklet/storage/backup/logs.js +24 -0
- package/lib/blocklet/storage/backup/routing-rule.js +19 -0
- package/lib/blocklet/storage/backup/spaces.js +240 -0
- package/lib/blocklet/storage/restore/base.js +67 -0
- package/lib/blocklet/storage/restore/blocklet-extras.js +86 -0
- package/lib/blocklet/storage/restore/blocklet.js +56 -0
- package/lib/blocklet/storage/restore/blocklets.js +43 -0
- package/lib/blocklet/storage/restore/logs.js +21 -0
- package/lib/blocklet/storage/restore/spaces.js +156 -0
- package/lib/blocklet/storage/utils/hash.js +51 -0
- package/lib/blocklet/storage/utils/zip.js +43 -0
- package/lib/cert.js +206 -0
- package/lib/event.js +237 -64
- package/lib/index.js +191 -83
- package/lib/migrations/1.0.21-update-config.js +1 -1
- package/lib/migrations/1.0.22-max-memory.js +1 -1
- package/lib/migrations/1.0.25.js +1 -1
- package/lib/migrations/1.0.32-update-config.js +1 -1
- package/lib/migrations/1.0.33-blocklets.js +1 -1
- package/lib/migrations/1.5.20-registry.js +15 -0
- package/lib/migrations/1.6.17-blocklet-children.js +48 -0
- package/lib/migrations/1.6.21-rename-ip-echo-domain.js +35 -0
- package/lib/migrations/1.6.4-security.js +59 -0
- package/lib/migrations/1.6.5-security.js +60 -0
- package/lib/migrations/1.6.9-update-node-info-and-certificate.js +38 -0
- package/lib/migrations/1.7.1-blocklet-setup.js +18 -0
- package/lib/migrations/1.7.12-blocklet-meta.js +51 -0
- package/lib/migrations/1.7.15-blocklet-bundle-source.js +42 -0
- package/lib/migrations/1.7.20-blocklet-component.js +41 -0
- package/lib/migrations/1.8.33-blocklet-mem-limit.js +20 -0
- package/lib/migrations/README.md +1 -1
- package/lib/migrations/index.js +6 -2
- package/lib/monitor/blocklet-runtime-monitor.js +200 -0
- package/lib/monitor/get-history-list.js +37 -0
- package/lib/monitor/node-runtime-monitor.js +228 -0
- package/lib/router/helper.js +572 -497
- package/lib/router/index.js +85 -21
- package/lib/router/manager.js +146 -187
- package/lib/states/README.md +36 -1
- package/lib/states/access-key.js +39 -17
- package/lib/states/audit-log.js +462 -0
- package/lib/states/base.js +4 -213
- package/lib/states/blocklet-extras.js +194 -138
- package/lib/states/blocklet.js +361 -104
- package/lib/states/cache.js +8 -6
- package/lib/states/challenge.js +5 -5
- package/lib/states/index.js +19 -36
- package/lib/states/migration.js +4 -4
- package/lib/states/node.js +135 -46
- package/lib/states/notification.js +22 -35
- package/lib/states/session.js +17 -9
- package/lib/states/site.js +50 -25
- package/lib/states/user.js +74 -20
- package/lib/states/webhook.js +10 -6
- package/lib/team/manager.js +124 -7
- package/lib/util/blocklet.js +1223 -246
- package/lib/util/chain.js +1 -1
- package/lib/util/default-node-config.js +5 -23
- package/lib/util/disk-monitor.js +13 -10
- package/lib/util/domain-status.js +84 -15
- package/lib/util/get-accessible-external-node-ip.js +2 -2
- package/lib/util/get-domain-for-blocklet.js +13 -0
- package/lib/util/get-meta-from-url.js +33 -0
- package/lib/util/index.js +207 -272
- package/lib/util/ip.js +6 -0
- package/lib/util/maintain.js +233 -0
- package/lib/util/public-to-store.js +85 -0
- package/lib/util/ready.js +1 -1
- package/lib/util/requirement.js +28 -9
- package/lib/util/reset-node.js +22 -7
- package/lib/util/router.js +13 -0
- package/lib/util/rpc.js +16 -0
- package/lib/util/store.js +179 -0
- package/lib/util/sysinfo.js +44 -0
- package/lib/util/ua.js +54 -0
- package/lib/validators/blocklet-extra.js +24 -0
- package/lib/validators/node.js +25 -12
- package/lib/validators/permission.js +16 -1
- package/lib/validators/role.js +17 -3
- package/lib/validators/router.js +40 -20
- package/lib/validators/trusted-passport.js +1 -0
- package/lib/validators/util.js +22 -5
- package/lib/webhook/index.js +45 -35
- package/lib/webhook/sender/index.js +5 -0
- package/lib/webhook/sender/slack/index.js +1 -1
- package/lib/webhook/sender/wallet/index.js +48 -0
- package/package.json +54 -36
- package/lib/blocklet/registry.js +0 -205
- package/lib/states/https-cert.js +0 -67
- package/lib/util/get-ip-dns-domain-for-blocklet.js +0 -19
- package/lib/util/service.js +0 -66
- package/lib/util/upgrade.js +0 -178
- /package/lib/{queue.js → util/queue.js} +0 -0
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
const pick = require('lodash/pick');
|
|
4
|
+
const pRetry = require('p-retry');
|
|
5
|
+
const Client = require('@ocap/client');
|
|
6
|
+
|
|
7
|
+
const logger = require('@abtnode/logger')('@abtnode/core:migrate-application-to-struct-v2');
|
|
8
|
+
|
|
9
|
+
const { forEachBlockletSync, getSharedConfigObj, getChainInfo } = require('@blocklet/meta/lib/util');
|
|
10
|
+
const { SLOT_FOR_IP_DNS_SITE } = require('@abtnode/constant');
|
|
11
|
+
|
|
12
|
+
const {
|
|
13
|
+
BlockletStatus,
|
|
14
|
+
BlockletSource,
|
|
15
|
+
BlockletEvents,
|
|
16
|
+
BlockletGroup,
|
|
17
|
+
BLOCKLET_DEFAULT_PORT_NAME,
|
|
18
|
+
BLOCKLET_INTERFACE_TYPE_WEB,
|
|
19
|
+
BLOCKLET_INTERFACE_PUBLIC,
|
|
20
|
+
BLOCKLET_DYNAMIC_PATH_PREFIX,
|
|
21
|
+
BLOCKLET_INTERFACE_PROTOCOL_HTTP,
|
|
22
|
+
BLOCKLET_DEFAULT_PATH_REWRITE,
|
|
23
|
+
BLOCKLET_DEFAULT_VERSION,
|
|
24
|
+
BLOCKLET_LATEST_SPEC_VERSION,
|
|
25
|
+
BLOCKLET_CONFIGURABLE_KEY,
|
|
26
|
+
BLOCKLET_META_FILE,
|
|
27
|
+
BLOCKLET_UPLOADS_DIR,
|
|
28
|
+
} = require('@blocklet/constant');
|
|
29
|
+
const { update: updateMetaFile } = require('@blocklet/meta/lib/file');
|
|
30
|
+
const getBlockletWallet = require('@blocklet/meta/lib/wallet');
|
|
31
|
+
const { cloneDeep } = require('lodash');
|
|
32
|
+
|
|
33
|
+
const { isInProgress } = require('../../../util');
|
|
34
|
+
const { getBlockletDomainGroupName } = require('../../../util/router');
|
|
35
|
+
const { getIpDnsDomainForBlocklet } = require('../../../util/get-domain-for-blocklet');
|
|
36
|
+
const { getBundleDir } = require('../../../util/blocklet');
|
|
37
|
+
|
|
38
|
+
const sortMoveListBySrc = (list) => {
|
|
39
|
+
return list.sort((a, b) => (a.src.length > b.src.length ? -1 : 1));
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const validateDataMoveList = (list) => {
|
|
43
|
+
const srcList = list.map((x) => x.src);
|
|
44
|
+
const distList = list.map((x) => x.dist);
|
|
45
|
+
|
|
46
|
+
// find duplicate element in src
|
|
47
|
+
const duplicateInSrc = srcList.filter((x, i) => srcList.indexOf(x) !== i);
|
|
48
|
+
if (duplicateInSrc.length) {
|
|
49
|
+
throw new Error(`Duplicate element in src of dataMoveList: ${duplicateInSrc}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// find duplicate element in list
|
|
53
|
+
const duplicateInDist = distList.filter((x, i) => distList.indexOf(x) !== i);
|
|
54
|
+
if (duplicateInDist.length) {
|
|
55
|
+
throw new Error(`Duplicate element in dist of dataMoveList: ${duplicateInDist}`);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const fillBlockletData = (data, app, id) => {
|
|
60
|
+
Object.assign(data, {
|
|
61
|
+
meta: {
|
|
62
|
+
name: id,
|
|
63
|
+
did: id,
|
|
64
|
+
bundleDid: id,
|
|
65
|
+
bundleName: id,
|
|
66
|
+
title: app.meta.title || '',
|
|
67
|
+
description: app.meta.description || '',
|
|
68
|
+
version: BLOCKLET_DEFAULT_VERSION,
|
|
69
|
+
group: BlockletGroup.gateway,
|
|
70
|
+
interfaces: [
|
|
71
|
+
{
|
|
72
|
+
type: BLOCKLET_INTERFACE_TYPE_WEB,
|
|
73
|
+
name: BLOCKLET_INTERFACE_PUBLIC,
|
|
74
|
+
path: BLOCKLET_DEFAULT_PATH_REWRITE,
|
|
75
|
+
prefix: BLOCKLET_DYNAMIC_PATH_PREFIX,
|
|
76
|
+
port: BLOCKLET_DEFAULT_PORT_NAME,
|
|
77
|
+
protocol: BLOCKLET_INTERFACE_PROTOCOL_HTTP,
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
specVersion: BLOCKLET_LATEST_SPEC_VERSION,
|
|
81
|
+
environments: [],
|
|
82
|
+
},
|
|
83
|
+
source: BlockletSource.custom,
|
|
84
|
+
status: BlockletStatus.stopped,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
if (app.meta.logo) {
|
|
88
|
+
data.meta.logo = app.meta.logo;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const appSystemFiles = ['logo.svg', 'rbac.db', 'session.db', 'user.db', '.assets', BLOCKLET_UPLOADS_DIR];
|
|
93
|
+
|
|
94
|
+
const migrateAppOnChain = async (blocklet, oldSk, newSk) => {
|
|
95
|
+
logger.info('Preparing for on-chain migration', { did: blocklet.meta.did });
|
|
96
|
+
if (!oldSk) {
|
|
97
|
+
logger.info('on-chain migration aborted because oldSk is empty', { did: blocklet.meta.did });
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!newSk) {
|
|
102
|
+
logger.info('on-chain migration aborted because newSk is empty', { did: blocklet.meta.did });
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ensure chain host
|
|
107
|
+
const chainInfo = getChainInfo(blocklet.configObj);
|
|
108
|
+
if (chainInfo.host === 'none') {
|
|
109
|
+
logger.info('on-chain migration aborted because CHAIN_HOST is empty', { did: blocklet.meta.did });
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
logger.info('on-chain migration for chain ', { did: blocklet.meta.did, host: chainInfo.host });
|
|
114
|
+
|
|
115
|
+
// ensure account changed
|
|
116
|
+
const type = blocklet.configObj.BLOCKLET_WALLET_TYPE;
|
|
117
|
+
const oldWallet = getBlockletWallet(oldSk, undefined, type);
|
|
118
|
+
const newWallet = getBlockletWallet(newSk, undefined, type);
|
|
119
|
+
if (oldWallet.address === newWallet.address) {
|
|
120
|
+
logger.info('on-chain migration aborted because newSk same with oldSk', { did: blocklet.meta.did });
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ensure old account exist on chain
|
|
125
|
+
const client = new Client(chainInfo.host);
|
|
126
|
+
const oldResult = await client.getAccountState({ address: oldWallet.address });
|
|
127
|
+
if (!oldResult.state) {
|
|
128
|
+
logger.info('on-chain migration aborted because oldSk not declared on chain', { did: blocklet.meta.did });
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ensure new account not exist on chain
|
|
133
|
+
const newResult = await client.getAccountState({ address: newWallet.address });
|
|
134
|
+
if (newResult.state) {
|
|
135
|
+
logger.info('on-chain migration aborted because newSk declared on chain', { did: blocklet.meta.did });
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
logger.info('on-chain migration for wallets', { oldAddress: oldWallet.address, newAddress: newWallet.address });
|
|
140
|
+
|
|
141
|
+
// migrate old account to new account
|
|
142
|
+
const tx = await client.signAccountMigrateTx({
|
|
143
|
+
tx: {
|
|
144
|
+
itx: {
|
|
145
|
+
address: newWallet.address,
|
|
146
|
+
pk: newWallet.publicKey,
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
wallet: oldWallet,
|
|
150
|
+
});
|
|
151
|
+
const hash = await client.sendAccountMigrateTx({ tx, wallet: oldWallet });
|
|
152
|
+
logger.info('on-chain migration done', { did: blocklet.meta.did, hash });
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const migrateApplicationToStructV2 = async ({ did, appSk: newAppSk, context = {}, manager, states }) => {
|
|
156
|
+
logger.info('Preparing data for migration', { did });
|
|
157
|
+
|
|
158
|
+
if (!newAppSk) {
|
|
159
|
+
throw new Error('New key pair is required when migrate application');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const oldBlocklet = await manager.getBlocklet(did, { throwOnNotExist: true, ensureIntegrity: true });
|
|
163
|
+
if (oldBlocklet.structVersion) {
|
|
164
|
+
throw new Error('Blocklet already migrated', pick(oldBlocklet, ['structVersion', 'externalSk']));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (isInProgress(oldBlocklet.status) || oldBlocklet.status === BlockletStatus.running) {
|
|
168
|
+
throw new Error('Please stop blocklet before migration');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const extraData = await states.blockletExtras.findOne({ did: oldBlocklet.meta.did });
|
|
172
|
+
const siteData = await states.site.findOneByBlocklet(oldBlocklet.meta.did);
|
|
173
|
+
|
|
174
|
+
const backupBlocklet = await states.blocklet.findOne({ 'meta.did': oldBlocklet.meta.did });
|
|
175
|
+
const backupExtra = cloneDeep(extraData);
|
|
176
|
+
const backupSite = await states.site.findOne({ _id: siteData.id });
|
|
177
|
+
|
|
178
|
+
const { appPid } = oldBlocklet;
|
|
179
|
+
|
|
180
|
+
// change index of extraData
|
|
181
|
+
extraData.did = appPid;
|
|
182
|
+
if (extraData.meta) {
|
|
183
|
+
extraData.meta.did = appPid;
|
|
184
|
+
extraData.meta.name = appPid;
|
|
185
|
+
}
|
|
186
|
+
// fork root component's configs to container
|
|
187
|
+
// FIXME: @linchen the configs in container should be managed in dashboard ?
|
|
188
|
+
extraData.configs = (extraData.configs || []).filter((x) => x.key !== BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SK);
|
|
189
|
+
|
|
190
|
+
// If appSk not configured before, then set derived appSk as permanent appSk
|
|
191
|
+
extraData.configs.push({
|
|
192
|
+
key: BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SK,
|
|
193
|
+
value: oldBlocklet.environmentObj.BLOCKLET_APP_SK,
|
|
194
|
+
secure: true,
|
|
195
|
+
shared: false,
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// clean children configs
|
|
199
|
+
extraData.children = [];
|
|
200
|
+
// clean dirty data in settings
|
|
201
|
+
if (extraData.settings) {
|
|
202
|
+
extraData.settings.children = [];
|
|
203
|
+
delete extraData.settings.navigation;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// delete system generated rules in siteData
|
|
207
|
+
siteData.rules = siteData.rules.filter((rule) => !rule.isProtected && rule.to.did !== oldBlocklet.meta.did);
|
|
208
|
+
// change index of siteData
|
|
209
|
+
siteData.domain = getBlockletDomainGroupName(appPid);
|
|
210
|
+
|
|
211
|
+
const blockletData = {
|
|
212
|
+
appPid,
|
|
213
|
+
children: [],
|
|
214
|
+
migratedFrom: Array.isArray(oldBlocklet.migratedFrom) ? cloneDeep(oldBlocklet.migratedFrom) : [],
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const dataDirSrc = path.join(oldBlocklet.env.dataDir);
|
|
218
|
+
const dataDirDist = path.join(manager.dataDirs.data, appPid);
|
|
219
|
+
if (fs.existsSync(dataDirDist)) {
|
|
220
|
+
throw new Error(`blocklet data dir already exist: ${dataDirDist}`);
|
|
221
|
+
}
|
|
222
|
+
const dataMoveList = [];
|
|
223
|
+
let currentMoveIndex = -1; // for rollback
|
|
224
|
+
appSystemFiles.forEach((file) => {
|
|
225
|
+
const src = path.join(dataDirSrc, file);
|
|
226
|
+
const dist = path.join(dataDirDist, file);
|
|
227
|
+
if (fs.existsSync(src)) {
|
|
228
|
+
dataMoveList.push({ src, dist });
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
forEachBlockletSync(oldBlocklet, (component, { level, ancestors }) => {
|
|
233
|
+
// if have duplicate component, user should fix it manually
|
|
234
|
+
if (blockletData.children.some((x) => x.meta.bundleDid === component.meta.bundleDid)) {
|
|
235
|
+
throw new Error(
|
|
236
|
+
`Find duplicate component ${component.meta.title}, please delete useless components and migrate again`
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// If old root component is a container, just skip it
|
|
241
|
+
if (level === 0 && component.meta.group === BlockletGroup.gateway) {
|
|
242
|
+
fillBlockletData(blockletData, component, appPid);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// If old root component is a blocklet, make it to be level one component
|
|
247
|
+
if (level === 0 && component.meta.group !== BlockletGroup.gateway) {
|
|
248
|
+
fillBlockletData(blockletData, component, appPid);
|
|
249
|
+
|
|
250
|
+
// add root component to blockletData
|
|
251
|
+
const { source, deployedFrom } = component;
|
|
252
|
+
let bundleSource;
|
|
253
|
+
if (source === BlockletSource.store) {
|
|
254
|
+
bundleSource = {
|
|
255
|
+
store: component.deployedFrom,
|
|
256
|
+
name: component.meta.bundleName,
|
|
257
|
+
version: 'latest',
|
|
258
|
+
};
|
|
259
|
+
} else if (source === BlockletSource.url) {
|
|
260
|
+
bundleSource = {
|
|
261
|
+
url: component.deployedFrom,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
blockletData.children.push({
|
|
266
|
+
...pick(component, ['mode', 'status']),
|
|
267
|
+
mountPoint: component.mountPoint || '/',
|
|
268
|
+
meta: {
|
|
269
|
+
...component.meta,
|
|
270
|
+
// change index of component
|
|
271
|
+
did: component.meta.bundleDid,
|
|
272
|
+
name: component.meta.bundleName,
|
|
273
|
+
},
|
|
274
|
+
bundleSource,
|
|
275
|
+
source,
|
|
276
|
+
deployedFrom,
|
|
277
|
+
children: [],
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// add root component to extraData
|
|
281
|
+
extraData.children.push({
|
|
282
|
+
// change index of component
|
|
283
|
+
did: component.meta.bundleDid,
|
|
284
|
+
// filter application configs in root component
|
|
285
|
+
configs: (component.configs || []).filter((x) => !x.key.startsWith('BLOCKLET_')),
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// move root component data by file
|
|
289
|
+
const componentFolders = (component.children || []).map((x) => x.meta.name.split('/')[0]);
|
|
290
|
+
const files = fs.readdirSync(dataDirSrc);
|
|
291
|
+
for (const file of files) {
|
|
292
|
+
if (!componentFolders.includes(file) && !appSystemFiles.includes(file)) {
|
|
293
|
+
dataMoveList.push({
|
|
294
|
+
src: path.join(dataDirSrc, file),
|
|
295
|
+
dist: path.join(dataDirDist, component.meta.bundleName, file),
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// add component to blockletData
|
|
304
|
+
blockletData.children.push({
|
|
305
|
+
...pick(component, ['mode', 'status', 'bundleSource', 'source', 'deployedFrom']),
|
|
306
|
+
meta: {
|
|
307
|
+
...component.meta,
|
|
308
|
+
// change index of component
|
|
309
|
+
did: component.meta.bundleDid,
|
|
310
|
+
name: component.meta.bundleName,
|
|
311
|
+
},
|
|
312
|
+
// keep pathPrefix of the component remains the same as before
|
|
313
|
+
mountPoint: path.join('/', ...ancestors.slice(1).map((x) => x.mountPoint || ''), component.mountPoint || '/'),
|
|
314
|
+
children: [],
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
// add component to extraData
|
|
318
|
+
extraData.children.push({
|
|
319
|
+
did: component.meta.bundleDid,
|
|
320
|
+
configs: component.configs || [],
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
const sharedConfigObj = getSharedConfigObj(component, ancestors);
|
|
324
|
+
if (sharedConfigObj) {
|
|
325
|
+
Object.entries(sharedConfigObj).forEach(([key, value]) => {
|
|
326
|
+
if (!extraData.configs.some((x) => x.key === key)) {
|
|
327
|
+
logger.info('add shared config to container configs', { did, key, value });
|
|
328
|
+
extraData.configs.push({ key, value });
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// move component data dir
|
|
334
|
+
const src = component.env.dataDir;
|
|
335
|
+
const dist = path.join(dataDirSrc, component.meta.bundleName);
|
|
336
|
+
if (src !== dist) {
|
|
337
|
+
dataMoveList.push({ src: component.env.dataDir, dist: path.join(dataDirDist, component.meta.bundleName) });
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
// ensure the deepest component is moved first
|
|
342
|
+
const sortedDataMoveList = sortMoveListBySrc(dataMoveList);
|
|
343
|
+
validateDataMoveList(sortedDataMoveList);
|
|
344
|
+
|
|
345
|
+
// encrypt data in extras
|
|
346
|
+
states.blockletExtras.encryptSecurityData({ data: extraData });
|
|
347
|
+
|
|
348
|
+
// refresh ip dns domain
|
|
349
|
+
siteData.domainAliases = siteData.domainAliases.filter(
|
|
350
|
+
(x) => !x.value.includes(SLOT_FOR_IP_DNS_SITE) || !x.isProtected
|
|
351
|
+
);
|
|
352
|
+
siteData.domainAliases.push({ value: getIpDnsDomainForBlocklet(blockletData), isProtected: true });
|
|
353
|
+
|
|
354
|
+
// update state
|
|
355
|
+
let newBlocklet;
|
|
356
|
+
try {
|
|
357
|
+
logger.info('Start migrate application state', { did, appPid });
|
|
358
|
+
// delete old db in db proxy
|
|
359
|
+
await manager.teamManager.deleteTeam(oldBlocklet.meta.did);
|
|
360
|
+
|
|
361
|
+
// re create blocklet
|
|
362
|
+
await states.blocklet.remove({ appPid });
|
|
363
|
+
await states.blocklet.addBlocklet(blockletData);
|
|
364
|
+
await states.blocklet.updateStructV1Did(appPid, oldBlocklet.meta.did);
|
|
365
|
+
|
|
366
|
+
// fake install bundle
|
|
367
|
+
const bundleDir = getBundleDir(manager.installDir, blockletData.meta);
|
|
368
|
+
fs.mkdirSync(bundleDir, { recursive: true });
|
|
369
|
+
updateMetaFile(path.join(bundleDir, BLOCKLET_META_FILE), blockletData.meta);
|
|
370
|
+
|
|
371
|
+
if (oldBlocklet.meta.logo) {
|
|
372
|
+
const fileName = oldBlocklet.meta.logo;
|
|
373
|
+
const src = path.join(getBundleDir(manager.installDir, oldBlocklet.meta), fileName);
|
|
374
|
+
const dist = path.join(bundleDir, fileName);
|
|
375
|
+
|
|
376
|
+
await fs.copy(src, dist);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// update
|
|
380
|
+
await states.blockletExtras.update({ did: oldBlocklet.meta.did }, extraData);
|
|
381
|
+
|
|
382
|
+
// update environment, generate appDid and appPid
|
|
383
|
+
await manager._updateBlockletEnvironment(appPid);
|
|
384
|
+
|
|
385
|
+
// rotate to newAppSk
|
|
386
|
+
await manager.config({
|
|
387
|
+
did: appPid,
|
|
388
|
+
configs: [{ key: 'BLOCKLET_APP_SK', value: newAppSk, secure: true }],
|
|
389
|
+
skipHook: true,
|
|
390
|
+
skipDidDocument: true,
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
// update routing
|
|
394
|
+
await states.site.update(
|
|
395
|
+
{ _id: siteData.id },
|
|
396
|
+
{ $set: { domain: siteData.domain, domainAliases: siteData.domainAliases, rules: siteData.rules } }
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
newBlocklet = await manager.getBlocklet(appPid);
|
|
400
|
+
|
|
401
|
+
logger.info('Start migrate application data', { sortedDataMoveList });
|
|
402
|
+
|
|
403
|
+
// move data
|
|
404
|
+
fs.mkdirSync(dataDirDist, { recursive: true });
|
|
405
|
+
for (let i = 0; i < sortedDataMoveList.length; i++) {
|
|
406
|
+
const { src, dist } = sortedDataMoveList[i];
|
|
407
|
+
fs.moveSync(src, dist);
|
|
408
|
+
currentMoveIndex = i;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// migrate on-chain account
|
|
412
|
+
await pRetry(() => migrateAppOnChain(oldBlocklet, oldBlocklet.environmentObj.BLOCKLET_APP_SK, newAppSk), {
|
|
413
|
+
retries: 3,
|
|
414
|
+
onFailedAttempt: console.error,
|
|
415
|
+
});
|
|
416
|
+
} catch (error) {
|
|
417
|
+
logger.error('Migrate application state failed: ', { did, error });
|
|
418
|
+
|
|
419
|
+
await states.blocklet.remove({ _id: backupBlocklet._id });
|
|
420
|
+
await states.blocklet.remove({ 'meta.did': blockletData.meta.did });
|
|
421
|
+
await states.blocklet.insert(backupBlocklet);
|
|
422
|
+
await states.blockletExtras.remove({ _id: backupExtra._id });
|
|
423
|
+
await states.blockletExtras.insert(backupExtra);
|
|
424
|
+
await states.site.remove({ _id: backupSite._id });
|
|
425
|
+
await states.site.insert(backupSite);
|
|
426
|
+
|
|
427
|
+
logger.info('Rollback application state');
|
|
428
|
+
|
|
429
|
+
// rollback data
|
|
430
|
+
fs.ensureDirSync(dataDirSrc);
|
|
431
|
+
for (let i = currentMoveIndex; i >= 0; i--) {
|
|
432
|
+
const { src, dist } = sortedDataMoveList[i];
|
|
433
|
+
fs.moveSync(dist, src);
|
|
434
|
+
}
|
|
435
|
+
fs.removeSync(dataDirDist);
|
|
436
|
+
|
|
437
|
+
logger.info('Rollback application data');
|
|
438
|
+
|
|
439
|
+
throw error;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// 通过 event 触发 ensureBlockletRouting
|
|
443
|
+
manager.emit(BlockletEvents.installed, { blocklet: newBlocklet, context });
|
|
444
|
+
manager.emit(BlockletEvents.removed, {
|
|
445
|
+
blocklet: { meta: { did: oldBlocklet.meta.did } },
|
|
446
|
+
context: { skipAll: true },
|
|
447
|
+
});
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
module.exports = { migrateApplicationToStructV2, sortMoveListBySrc };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const DIR_NAME = 'rollback-cache';
|
|
5
|
+
const security = require('@abtnode/util/lib/security');
|
|
6
|
+
|
|
7
|
+
class RollbackCache {
|
|
8
|
+
constructor({ dir, dek }) {
|
|
9
|
+
this.dir = dir;
|
|
10
|
+
this.dek = dek;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async backup({ did, action, oldBlocklet }) {
|
|
14
|
+
const file = this._getFile(did);
|
|
15
|
+
const data = this.dek ? security.encrypt(JSON.stringify(oldBlocklet), did, this.dek) : oldBlocklet;
|
|
16
|
+
await fs.outputJSON(file, { action, oldBlocklet: data });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async remove({ did }) {
|
|
20
|
+
const file = this._getFile(did);
|
|
21
|
+
await fs.remove(file);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async restore({ did }) {
|
|
25
|
+
const file = this._getFile(did);
|
|
26
|
+
if (fs.existsSync(file)) {
|
|
27
|
+
const data = await fs.readJSON(file);
|
|
28
|
+
if (data?.oldBlocklet && this.dek) {
|
|
29
|
+
data.oldBlocklet = JSON.parse(security.decrypt(data.oldBlocklet, did, this.dek));
|
|
30
|
+
}
|
|
31
|
+
return data;
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
_getFile(did) {
|
|
37
|
+
return path.join(this.dir, DIR_NAME, `${did}.json`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
module.exports = RollbackCache;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/* eslint-disable no-await-in-loop */
|
|
2
|
+
const cloneDeep = require('lodash/cloneDeep');
|
|
3
|
+
const capitalize = require('lodash/capitalize');
|
|
4
|
+
|
|
5
|
+
const logger = require('@abtnode/logger')('@abtnode/core:upgrade-component');
|
|
6
|
+
|
|
7
|
+
const { BlockletStatus, BlockletEvents } = require('@blocklet/constant');
|
|
8
|
+
const {
|
|
9
|
+
getUpdateMetaList,
|
|
10
|
+
parseComponents,
|
|
11
|
+
checkDuplicateComponents,
|
|
12
|
+
filterDuplicateComponents,
|
|
13
|
+
checkStructVersion,
|
|
14
|
+
checkVersionCompatibility,
|
|
15
|
+
validateBlocklet,
|
|
16
|
+
} = require('../../../util/blocklet');
|
|
17
|
+
|
|
18
|
+
const check = async ({ did, states }) => {
|
|
19
|
+
const blocklet = await states.blocklet.getBlocklet(did);
|
|
20
|
+
checkStructVersion(blocklet);
|
|
21
|
+
|
|
22
|
+
const newBlocklet = cloneDeep(blocklet);
|
|
23
|
+
|
|
24
|
+
const newChildren = [];
|
|
25
|
+
|
|
26
|
+
for (const child of newBlocklet.children || []) {
|
|
27
|
+
if (child.bundleSource) {
|
|
28
|
+
const {
|
|
29
|
+
staticComponents: [newChild],
|
|
30
|
+
dynamicComponents,
|
|
31
|
+
} = await parseComponents({
|
|
32
|
+
meta: {
|
|
33
|
+
staticComponents: [
|
|
34
|
+
{
|
|
35
|
+
source: child.bundleSource,
|
|
36
|
+
name: child.meta.name,
|
|
37
|
+
title: child.meta.title,
|
|
38
|
+
mountPoint: child.mountPoint,
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
newChild._dynamicComponents = dynamicComponents;
|
|
44
|
+
newChildren.push(newChild);
|
|
45
|
+
} else {
|
|
46
|
+
const { dynamicComponents } = await parseComponents(child);
|
|
47
|
+
child._dynamicComponents = dynamicComponents;
|
|
48
|
+
newChildren.push(child);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
checkDuplicateComponents(newChildren);
|
|
53
|
+
|
|
54
|
+
const updateList = getUpdateMetaList(blocklet, { ...blocklet, children: newChildren });
|
|
55
|
+
|
|
56
|
+
if (!updateList.length) {
|
|
57
|
+
return {};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// start session
|
|
61
|
+
const { id: updateId } = await states.session.start({
|
|
62
|
+
did,
|
|
63
|
+
children: newChildren,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
updateId,
|
|
68
|
+
updateList,
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const upgrade = async ({ updateId, selectedComponentDids, context, states, manager }) => {
|
|
73
|
+
if (!selectedComponentDids?.length) {
|
|
74
|
+
throw new Error('At least one component needs to be selected');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const sessionData = await states.session.end(updateId);
|
|
78
|
+
const { did } = sessionData;
|
|
79
|
+
const oldBlocklet = await manager._getBlockletForInstallation(did);
|
|
80
|
+
checkStructVersion(oldBlocklet);
|
|
81
|
+
|
|
82
|
+
// parse children
|
|
83
|
+
let dynamicComponents = [];
|
|
84
|
+
const children = cloneDeep(oldBlocklet.children).map((oldComponent) => {
|
|
85
|
+
const newComponent = sessionData.children.find((x) => x.meta.did === oldComponent.meta.did);
|
|
86
|
+
if (newComponent && selectedComponentDids.includes(newComponent.meta.did)) {
|
|
87
|
+
dynamicComponents.push(...(newComponent._dynamicComponents || []));
|
|
88
|
+
delete newComponent._dynamicComponents;
|
|
89
|
+
return newComponent;
|
|
90
|
+
}
|
|
91
|
+
return oldComponent;
|
|
92
|
+
});
|
|
93
|
+
dynamicComponents = filterDuplicateComponents(dynamicComponents, children);
|
|
94
|
+
children.push(...dynamicComponents);
|
|
95
|
+
|
|
96
|
+
// selectedComponentDids
|
|
97
|
+
selectedComponentDids.push(...dynamicComponents.map((x) => x.meta.did));
|
|
98
|
+
|
|
99
|
+
checkVersionCompatibility(children);
|
|
100
|
+
|
|
101
|
+
logger.info('upgrade blocklet children', {
|
|
102
|
+
did,
|
|
103
|
+
children: children.map((x) => ({ name: x.meta.name, version: x.meta.version })),
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// new blocklet
|
|
107
|
+
const newBlocklet = await states.blocklet.setBlockletStatus(did, BlockletStatus.waiting);
|
|
108
|
+
|
|
109
|
+
newBlocklet.children = children;
|
|
110
|
+
await validateBlocklet(newBlocklet);
|
|
111
|
+
|
|
112
|
+
manager.emit(BlockletEvents.statusChange, newBlocklet);
|
|
113
|
+
|
|
114
|
+
const action = 'upgrade';
|
|
115
|
+
|
|
116
|
+
// backup rollback data
|
|
117
|
+
await manager._rollbackCache.backup({ did, action, oldBlocklet });
|
|
118
|
+
|
|
119
|
+
// add to queue
|
|
120
|
+
const ticket = manager.installQueue.push(
|
|
121
|
+
{
|
|
122
|
+
entity: 'blocklet',
|
|
123
|
+
action: 'download',
|
|
124
|
+
id: did,
|
|
125
|
+
oldBlocklet: { ...oldBlocklet },
|
|
126
|
+
blocklet: { ...newBlocklet },
|
|
127
|
+
selectedComponentDids: selectedComponentDids || [],
|
|
128
|
+
context,
|
|
129
|
+
postAction: action,
|
|
130
|
+
},
|
|
131
|
+
did
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
ticket.on('failed', async (err) => {
|
|
135
|
+
logger.error('queue failed', { entity: 'blocklet', action, did, error: err });
|
|
136
|
+
await manager._rollback(action, did, oldBlocklet);
|
|
137
|
+
manager.emit(`blocklet.${action}.failed`, { did, err });
|
|
138
|
+
manager._createNotification(did, {
|
|
139
|
+
title: `Blocklet ${capitalize(action)} Failed`,
|
|
140
|
+
description: `Blocklet ${did} ${action} failed with error: ${err.message || 'queue exception'}`,
|
|
141
|
+
entityType: 'blocklet',
|
|
142
|
+
entityId: did,
|
|
143
|
+
severity: 'error',
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
return newBlocklet;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
module.exports = {
|
|
150
|
+
check,
|
|
151
|
+
upgrade,
|
|
152
|
+
};
|