@abtnode/core 1.8.55 → 1.8.57

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.
@@ -13,7 +13,7 @@ const { Throttle } = require('stream-throttle');
13
13
  const LRU = require('lru-cache');
14
14
  const joi = require('joi');
15
15
  const isUrl = require('is-url');
16
- const { isNFTExpired } = require('@abtnode/util/lib/nft');
16
+ const { isNFTExpired, getNftExpirationDate } = require('@abtnode/util/lib/nft');
17
17
  const didDocument = require('@abtnode/util/lib/did-document');
18
18
  const { sign } = require('@arcblock/jwt');
19
19
  const { isValid: isValidDid } = require('@arcblock/did');
@@ -1525,10 +1525,10 @@ class BlockletManager extends BaseBlockletManager {
1525
1525
  * After the dev function finished, the caller should send a BlockletEvents.installed event to the daemon
1526
1526
  * @returns {Object} blocklet
1527
1527
  */
1528
- async dev(folder, { rootDid, mountPoint } = {}) {
1528
+ async dev(folder, { rootDid, mountPoint, defaultStoreUrl } = {}) {
1529
1529
  logger.info('dev component', { folder, rootDid, mountPoint });
1530
1530
 
1531
- const meta = getBlockletMeta(folder);
1531
+ const meta = getBlockletMeta(folder, { defaultStoreUrl });
1532
1532
  if (meta.group !== 'static' && (!meta.scripts || !meta.scripts.dev)) {
1533
1533
  throw new Error('Incorrect blocklet.yml: missing `scripts.dev` field');
1534
1534
  }
@@ -2659,6 +2659,11 @@ class BlockletManager extends BaseBlockletManager {
2659
2659
  time: '0 */30 * * * *', // 30min
2660
2660
  fn: () => this._deleteExpiredExternalBlocklet(),
2661
2661
  },
2662
+ {
2663
+ name: 'clean-expired-blocklet-data',
2664
+ time: '0 */20 0 * * *', // 每天凌晨 0 点的每 20 分钟
2665
+ fn: () => this._cleanExpiredBlockletData(),
2666
+ },
2662
2667
  {
2663
2668
  name: 'record-blocklet-runtime-history',
2664
2669
  time: `*/${MONITOR_RECORD_INTERVAL_SEC} * * * * *`, // 10s
@@ -2734,6 +2739,8 @@ class BlockletManager extends BaseBlockletManager {
2734
2739
 
2735
2740
  await validateBlocklet(blocklet);
2736
2741
 
2742
+ await states.blockletExtras.addMeta({ did, meta: { did, name }, controller });
2743
+
2737
2744
  await this._setConfigsFromMeta(did);
2738
2745
 
2739
2746
  logger.info('blocklet added to database', { did: meta.did });
@@ -3607,8 +3614,6 @@ class BlockletManager extends BaseBlockletManager {
3607
3614
  async _deleteBlocklet({ did, keepData, keepLogsDir, keepConfigs }, context) {
3608
3615
  const blocklet = await states.blocklet.getBlocklet(did);
3609
3616
  const { name } = blocklet.meta;
3610
- const dataDir = path.join(this.dataDirs.data, name);
3611
- const logsDir = path.join(this.dataDirs.logs, name);
3612
3617
  const cacheDir = path.join(this.dataDirs.cache, name);
3613
3618
 
3614
3619
  // Cleanup db
@@ -3616,19 +3621,7 @@ class BlockletManager extends BaseBlockletManager {
3616
3621
 
3617
3622
  // Cleanup disk storage
3618
3623
  fs.removeSync(cacheDir);
3619
- if (keepData === false) {
3620
- fs.removeSync(dataDir);
3621
- fs.removeSync(logsDir);
3622
- await states.blockletExtras.remove({ did: blocklet.meta.did });
3623
- } else {
3624
- if (keepLogsDir === false) {
3625
- fs.removeSync(logsDir);
3626
- }
3627
-
3628
- if (keepConfigs === false) {
3629
- await states.blockletExtras.remove({ did: blocklet.meta.did });
3630
- }
3631
- }
3624
+ await this._cleanBlockletData({ blocklet, keepData, keepLogsDir, keepConfigs });
3632
3625
 
3633
3626
  const nodeInfo = await states.node.read();
3634
3627
  const { wallet } = getBlockletInfo(blocklet, nodeInfo.sk);
@@ -3661,6 +3654,36 @@ class BlockletManager extends BaseBlockletManager {
3661
3654
  return blocklet;
3662
3655
  }
3663
3656
 
3657
+ async _cleanBlockletData({ blocklet, keepData, keepLogsDir, keepConfigs }) {
3658
+ const { name } = blocklet.meta;
3659
+
3660
+ const dataDir = path.join(this.dataDirs.data, name);
3661
+ const logsDir = path.join(this.dataDirs.logs, name);
3662
+
3663
+ logger.info(`clean blocklet ${blocklet.meta.did} data`, { keepData, keepLogsDir, keepConfigs });
3664
+
3665
+ if (keepData === false) {
3666
+ fs.removeSync(dataDir);
3667
+ logger.info(`removed blocklet ${blocklet.meta.did} data dir: ${dataDir}`);
3668
+
3669
+ fs.removeSync(logsDir);
3670
+ logger.info(`removed blocklet ${blocklet.meta.did} logs dir: ${logsDir}`);
3671
+
3672
+ await states.blockletExtras.remove({ did: blocklet.meta.did });
3673
+ logger.info(`removed blocklet ${blocklet.meta.did} extra data`);
3674
+ } else {
3675
+ if (keepLogsDir === false) {
3676
+ fs.removeSync(logsDir);
3677
+ logger.info(`removed blocklet ${blocklet.meta.did} logs dir: ${logsDir}`);
3678
+ }
3679
+
3680
+ if (keepConfigs === false) {
3681
+ await states.blockletExtras.remove({ did: blocklet.meta.did });
3682
+ logger.info(`removed blocklet ${blocklet.meta.did} extra data`);
3683
+ }
3684
+ }
3685
+ }
3686
+
3664
3687
  async _setConfigsFromMeta(did, childDid) {
3665
3688
  const blocklet = await getBlocklet({ states, dataDirs: this.dataDirs, did, validateEnv: false, ensureDirs: false });
3666
3689
 
@@ -3846,11 +3869,19 @@ class BlockletManager extends BaseBlockletManager {
3846
3869
  nftId: blocklet.controller.nftId,
3847
3870
  });
3848
3871
 
3849
- // FIXME: 后面需要考虑将数据保留一段时间
3850
- await this.delete({ did: blocklet.meta.did, keepData: false, keepConfigs: false, keepLogsDir: false });
3872
+ await this.delete({ did: blocklet.meta.did, keepData: true, keepConfigs: true, keepLogsDir: true });
3851
3873
  logger.info('the expired blocklet already deleted', {
3852
3874
  blockletId: blocklet._id,
3853
3875
  nftId: blocklet.controller.nftId,
3876
+ did: blocklet.meta.did,
3877
+ });
3878
+
3879
+ const expiredAt = getNftExpirationDate(assetState);
3880
+ await states.blockletExtras.updateExpireInfo({ did: blocklet.meta.did, expiredAt });
3881
+ logger.info('updated expired blocklet extra info', {
3882
+ blockletId: blocklet._id,
3883
+ nftId: blocklet.controller.nftId,
3884
+ did: blocklet.meta.did,
3854
3885
  });
3855
3886
  }
3856
3887
  } catch (error) {
@@ -3869,6 +3900,35 @@ class BlockletManager extends BaseBlockletManager {
3869
3900
  }
3870
3901
  }
3871
3902
 
3903
+ async _cleanExpiredBlockletData() {
3904
+ try {
3905
+ logger.info('start clean expired blocklet data');
3906
+ const blockletExtras = await states.blockletExtras.getExpiredList();
3907
+ if (blockletExtras.length === 0) {
3908
+ logger.info('no expired blocklet data');
3909
+ return;
3910
+ }
3911
+
3912
+ const tasks = blockletExtras.map(async ({ did }) => {
3913
+ const blocklet = await states.blocklet.getBlocklet(did);
3914
+ await this._cleanBlockletData({ blocklet, keepData: false, keepLogsDir: false, keepConfigs: false });
3915
+
3916
+ this.emit(BlockletEvents.dataCleaned, {
3917
+ blocklet,
3918
+ keepRouting: false,
3919
+ });
3920
+
3921
+ logger.info(`cleaned expired blocklet blocklet ${did} data`);
3922
+ });
3923
+
3924
+ await Promise.all(tasks);
3925
+
3926
+ logger.info('clean expired blocklet data done');
3927
+ } catch (error) {
3928
+ logger.error('clean expired blocklet data failed', { error });
3929
+ }
3930
+ }
3931
+
3872
3932
  async _updateDidDocument(blocklet) {
3873
3933
  const nodeInfo = await states.node.read();
3874
3934
 
package/lib/event.js CHANGED
@@ -203,7 +203,7 @@ module.exports = ({
203
203
  logger.error('Failed to createAuditLog for upgradeBlocklet', { error });
204
204
  }
205
205
  }
206
- } else if ([BlockletEvents.removed].includes(eventName)) {
206
+ } else if ([BlockletEvents.removed, BlockletEvents.dataCleaned].includes(eventName)) {
207
207
  await handleBlockletRemove(eventName, payload);
208
208
  } else if ([BlockletEvents.started].includes(eventName)) {
209
209
  const { publicToStore } = blocklet.settings || {};
@@ -306,7 +306,7 @@ module.exports = ({
306
306
  }
307
307
  });
308
308
 
309
- listen(nodeState, EVENTS.NODE_UPGRADE_PROGRESS, onEvent);
309
+ listen(nodeState, EVENTS.NODE_MAINTAIN_PROGRESS, onEvent);
310
310
  nodeState.on(EVENTS.RELOAD_GATEWAY, (nodeInfo) => {
311
311
  handleRouting(nodeInfo).catch((err) => {
312
312
  logger.error('Handle routing failed on node.updated', { error: err });
package/lib/index.js CHANGED
@@ -22,7 +22,7 @@ const Cert = require('./cert');
22
22
 
23
23
  const IP = require('./util/ip');
24
24
  const DomainStatus = require('./util/domain-status');
25
- const Upgrade = require('./util/upgrade');
25
+ const Maintain = require('./util/maintain');
26
26
  const resetNode = require('./util/reset-node');
27
27
  const DiskMonitor = require('./util/disk-monitor');
28
28
  const StoreUtil = require('./util/store');
@@ -30,6 +30,7 @@ const createQueue = require('./queue');
30
30
  const createEvents = require('./event');
31
31
  const pm2Events = require('./blocklet/manager/pm2-events');
32
32
  const { createStateReadyQueue, createStateReadyHandler } = require('./util/ready');
33
+ const { createDataArchive } = require('./util/blocklet');
33
34
  const { toStatus, fromStatus, ensureDataDirs, getQueueConcurrencyByMem, getStateCrons } = require('./util');
34
35
 
35
36
  /**
@@ -237,7 +238,7 @@ function ABTNode(options) {
237
238
  getNodeEnv: nodeAPI.getEnv.bind(nodeAPI),
238
239
  updateNodeInfo: nodeAPI.updateNodeInfo.bind(nodeAPI),
239
240
  getDelegationState: nodeAPI.getDelegationState.bind(nodeAPI),
240
- cleanupDirtyUpgradeState: states.node.cleanupDirtyUpgradeState.bind(states.node),
241
+ cleanupDirtyMaintainState: states.node.cleanupDirtyMaintainState.bind(states.node),
241
242
  updateNodeOwner: states.node.updateNodeOwner.bind(states.node),
242
243
  updateNftHolder: states.node.updateNftHolder.bind(states.node),
243
244
  updateNodeRouting,
@@ -369,10 +370,11 @@ function ABTNode(options) {
369
370
  markMigrationExecuted: states.migration.markExecuted.bind(states.migration),
370
371
 
371
372
  // Upgrading
372
- upgradeNodeVersion: Upgrade.startUpgrade,
373
- checkNodeVersion: Upgrade.checkNewVersion,
374
- isUpgrading: Upgrade.isUpgrading,
375
- doUpgrade: Upgrade.doUpgrade,
373
+ upgradeNodeVersion: () => Maintain.triggerMaintain({ action: 'upgrade', next: Maintain.resumeMaintain }),
374
+ checkNodeVersion: Maintain.checkNewVersion,
375
+ restartServer: () => Maintain.triggerMaintain({ action: 'restart', next: Maintain.resumeMaintain }),
376
+ isBeingMaintained: Maintain.isBeingMaintained,
377
+ resumeMaintain: Maintain.resumeMaintain,
376
378
 
377
379
  // Session
378
380
  getSession: (params, context) => states.session.read(params.id, context),
@@ -390,6 +392,9 @@ function ABTNode(options) {
390
392
  getNodeRuntimeInfo: nodeAPI.getRealtimeData.bind(nodeAPI),
391
393
 
392
394
  getRouterProvider,
395
+
396
+ // for exporting blocklet data dir
397
+ createBlockletDataArchive: createDataArchive,
393
398
  };
394
399
 
395
400
  const events = createEvents({
@@ -415,7 +420,7 @@ function ABTNode(options) {
415
420
  context: { states, events, webhook },
416
421
  jobs: [
417
422
  IP.cron,
418
- Upgrade.getCron(),
423
+ Maintain.getCron(),
419
424
  ...getRoutingCrons(),
420
425
  ...blockletManager.getCrons(),
421
426
  DiskMonitor.getCron(),
@@ -436,18 +441,15 @@ function ABTNode(options) {
436
441
 
437
442
  const createCLILog = (action) => {
438
443
  instance
439
- .createAuditLog(
440
- {
441
- action,
442
- args: {},
443
- context: formatContext({
444
- user: { fullName: 'CLI', role: 'admin', did: options.nodeDid },
445
- headers: { 'user-agent': 'CLI' },
446
- }),
447
- result: null,
448
- },
449
- instance
450
- )
444
+ .createAuditLog({
445
+ action,
446
+ args: {},
447
+ context: formatContext({
448
+ user: { fullName: 'CLI', role: 'admin', did: options.nodeDid },
449
+ headers: { 'user-agent': 'CLI' },
450
+ }),
451
+ result: null,
452
+ })
451
453
  .catch(console.error);
452
454
  };
453
455
 
@@ -33,6 +33,7 @@ const {
33
33
  WELLKNOWN_ACME_CHALLENGE_PREFIX,
34
34
  WELLKNOWN_DID_RESOLVER_PREFIX,
35
35
  WELLKNOWN_PING_PREFIX,
36
+ LOG_RETAIN_IN_DAYS,
36
37
  } = require('@abtnode/constant');
37
38
  const {
38
39
  BLOCKLET_DYNAMIC_PATH_PREFIX,
@@ -1024,7 +1025,7 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
1024
1025
  const providerName = get(info, 'routing.provider', null);
1025
1026
 
1026
1027
  if (providerName && providers[providerName] && typeof providers[providerName].rotateLogs === 'function') {
1027
- await providers[providerName].rotateLogs();
1028
+ await providers[providerName].rotateLogs({ retain: LOG_RETAIN_IN_DAYS });
1028
1029
  }
1029
1030
  };
1030
1031
 
@@ -1250,7 +1251,7 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
1250
1251
  },
1251
1252
  {
1252
1253
  name: 'rotate-log-files',
1253
- time: '5 0 0 * * *', // rotate at 05:00 every day
1254
+ time: '1 0 0 * * *', // rotate at 00:00:01 every day
1254
1255
  fn: rotateRouterLog,
1255
1256
  options: { runOnInit: false },
1256
1257
  },
@@ -208,6 +208,8 @@ const getLogContent = async (action, args, context, result, info, node) => {
208
208
  return `updated basic server settings: \n${Object.keys(args).map(x => `- ${x}: ${args[x]}`).join('\n')}`; // prettier-ignore
209
209
  case 'upgradeNodeVersion':
210
210
  return `upgrade server to ${info.nextVersion}`;
211
+ case 'restartServer':
212
+ return 'server was restarted';
211
213
  case 'startServer':
212
214
  return 'server was started';
213
215
  case 'stopServer':
@@ -2,12 +2,15 @@
2
2
  /* eslint-disable no-underscore-dangle */
3
3
  /* eslint-disable consistent-return */
4
4
  const logger = require('@abtnode/logger')('state-blocklet-extras');
5
+ const { EXPIRED_BLOCKLET_DATA_RETENTION_DAYS } = require('@abtnode/constant');
5
6
  const camelCase = require('lodash/camelCase');
6
7
  const get = require('lodash/get');
8
+ const dayjs = require('dayjs');
7
9
 
8
10
  const BaseState = require('./base');
9
11
 
10
12
  const { mergeConfigs, parseConfigs } = require('../blocklet/extras');
13
+ const { validateAddMeta, validateExpiredInfo } = require('../validators/blocklet-extra');
11
14
 
12
15
  const noop = (k) => (v) => v[k];
13
16
 
@@ -184,6 +187,33 @@ class BlockletExtrasState extends BaseState {
184
187
  return list;
185
188
  };
186
189
  }
190
+
191
+ async addMeta({ did, meta, controller } = {}) {
192
+ const entity = { did, meta };
193
+ if (controller) {
194
+ entity.controller = controller;
195
+ }
196
+
197
+ await validateAddMeta(entity);
198
+
199
+ return super.update({ did }, { $set: entity }, { upsert: true });
200
+ }
201
+
202
+ async updateExpireInfo({ did, expiredAt } = {}) {
203
+ const entity = { did, expiredAt };
204
+ await validateExpiredInfo(entity);
205
+
206
+ return super.update({ did }, { $set: { expiredAt } });
207
+ }
208
+
209
+ async getExpiredList() {
210
+ const now = dayjs();
211
+
212
+ return super.find({
213
+ 'controller.nftId': { $exists: true },
214
+ expiredAt: { $exists: true, $lte: now.subtract(EXPIRED_BLOCKLET_DATA_RETENTION_DAYS, 'days').toISOString() },
215
+ });
216
+ }
187
217
  }
188
218
 
189
219
  module.exports = BlockletExtrasState;
@@ -179,7 +179,7 @@ class NodeState extends BaseState {
179
179
  return nodeInfo;
180
180
  }
181
181
 
182
- cleanupDirtyUpgradeState() {
182
+ cleanupDirtyMaintainState() {
183
183
  return this.read().then((doc) => {
184
184
  if (doc.nextVersion && semver.lte(doc.nextVersion, doc.version)) {
185
185
  const updates = { nextVersion: '', upgradeSessionId: '' };
@@ -11,6 +11,7 @@ const streamToPromise = require('stream-to-promise');
11
11
  const { Throttle } = require('stream-throttle');
12
12
  const ssri = require('ssri');
13
13
  const diff = require('deep-diff');
14
+ const createArchive = require('archiver');
14
15
  const axios = require('@abtnode/util/lib/axios');
15
16
  const { stableStringify } = require('@arcblock/vc');
16
17
 
@@ -1492,6 +1493,26 @@ const consumeServerlessNFT = async ({ nftId, nodeInfo, blocklet }) => {
1492
1493
  }
1493
1494
  };
1494
1495
 
1496
+ const createDataArchive = (dataDir, fileName) => {
1497
+ const zipPath = path.join(os.tmpdir(), fileName);
1498
+ if (fs.existsSync(zipPath)) {
1499
+ fs.removeSync(zipPath);
1500
+ }
1501
+
1502
+ const archive = createArchive('zip', { zlib: { level: 9 } });
1503
+ const stream = fs.createWriteStream(zipPath);
1504
+
1505
+ return new Promise((resolve, reject) => {
1506
+ archive
1507
+ .directory(dataDir, false)
1508
+ .on('error', (err) => reject(err))
1509
+ .pipe(stream);
1510
+
1511
+ stream.on('close', () => resolve(zipPath));
1512
+ archive.finalize();
1513
+ });
1514
+ };
1515
+
1495
1516
  module.exports = {
1496
1517
  consumeServerlessNFT,
1497
1518
  forEachBlocklet,
@@ -1534,4 +1555,5 @@ module.exports = {
1534
1555
  getBlocklet,
1535
1556
  ensureEnvDefault,
1536
1557
  getConfigFromPreferences,
1558
+ createDataArchive,
1537
1559
  };
@@ -0,0 +1,233 @@
1
+ /* eslint-disable no-param-reassign */
2
+ const semver = require('semver');
3
+ const sleep = require('@abtnode/util/lib/sleep');
4
+ const SysInfo = require('systeminformation');
5
+ const { NODE_MODES, NODE_MAINTAIN_PROGRESS, EVENTS } = require('@abtnode/constant');
6
+ const listNpmPackageVersion = require('@abtnode/util/lib/list-npm-package-version');
7
+ const Lock = require('@abtnode/util/lib/lock');
8
+ const logger = require('@abtnode/logger')('@abtnode/core:maintain');
9
+
10
+ const states = require('../states');
11
+ const doRpc = require('./rpc');
12
+
13
+ const lock = new Lock('node-upgrade-lock');
14
+
15
+ // eslint-disable-next-line no-unused-vars
16
+ const checkNewVersion = async (params, context) => {
17
+ try {
18
+ const info = await states.node.read();
19
+
20
+ if (!process.env.ABT_NODE_PACKAGE_NAME) {
21
+ logger.error('ABT_NODE_PACKAGE_NAME name was not found in environment');
22
+ return '';
23
+ }
24
+
25
+ const versions = await listNpmPackageVersion(process.env.ABT_NODE_PACKAGE_NAME);
26
+ if (Array.isArray(versions) && versions.length) {
27
+ const latestVersion = versions[0].version;
28
+ if (semver.gt(latestVersion, info.version)) {
29
+ // if (semver.gte(latestVersion, info.version)) {
30
+ logger.info('New version found for Blocklet Server', {
31
+ latestVersion,
32
+ currentVersion: info.version,
33
+ nextVersion: info.nextVersion,
34
+ });
35
+ await states.node.updateNodeInfo({ nextVersion: latestVersion });
36
+ await states.notification.create({
37
+ title: 'Blocklet Server upgrade available',
38
+ description: 'A new and improved version of blocklet server is now available',
39
+ entityType: 'node',
40
+ severity: 'info',
41
+ sticky: true,
42
+ action: '/settings/about',
43
+ });
44
+
45
+ return latestVersion;
46
+ }
47
+ }
48
+
49
+ return '';
50
+ } catch (err) {
51
+ logger.error('Failed to check new version for Blocklet Server', { error: err });
52
+ }
53
+
54
+ return '';
55
+ };
56
+
57
+ const triggerMaintain = async ({ action, next }) => {
58
+ if (['upgrade', 'restart'].includes(action) === false) {
59
+ throw new Error(`Unrecognized server maintain action: ${action}`);
60
+ }
61
+
62
+ const info = await states.node.read();
63
+
64
+ if (action === 'upgrade') {
65
+ if (!info.nextVersion) {
66
+ throw new Error('Do nothing since there is no next version to upgrade');
67
+ }
68
+
69
+ // ensure we have enough memory for upgrading since the upgrading will consume a lot of memory
70
+ const { mem } = await SysInfo.get({ mem: '*' });
71
+ const freeMemory = mem.free / 1024 / 1024;
72
+ const availableMemory = mem.available / 1024 / 1024;
73
+ if (freeMemory < 200 && availableMemory < 300) {
74
+ throw new Error('Upgrade aborted because free memory not enough, please stop some blocklets and try again');
75
+ }
76
+ }
77
+
78
+ const from = info.version;
79
+ const to = action === 'upgrade' ? info.nextVersion : info.version;
80
+
81
+ // ensure we have an maintain session, we need a session to recover from crash
82
+ let sessionId = info.upgradeSessionId;
83
+ if (!sessionId) {
84
+ const result = await states.session.start({
85
+ action,
86
+ from,
87
+ to,
88
+ stage: NODE_MAINTAIN_PROGRESS.SETUP,
89
+ history: [],
90
+ startedAt: Date.now(),
91
+ });
92
+ sessionId = result.id;
93
+ logger.info(`generate new session for ${action}`, { from, to, sessionId });
94
+ await states.node.updateNodeInfo({ upgradeSessionId: sessionId });
95
+ }
96
+ const session = await states.session.read(sessionId);
97
+ if (!session) {
98
+ throw new Error(`${action} aborted due to invalid session: ${sessionId}`);
99
+ }
100
+
101
+ process.nextTick(async () => {
102
+ await next(session);
103
+ });
104
+
105
+ return sessionId;
106
+ };
107
+
108
+ const resumeMaintain = async (session) => {
109
+ await lock.acquire();
110
+
111
+ const sessionId = session.id;
112
+ const { action, from, to } = session;
113
+
114
+ const goNextState = async (stage, previousSucceed) => {
115
+ session = await states.session.update(sessionId, {
116
+ stage,
117
+ history: [...session.history, { stage: session.stage, succeed: previousSucceed }],
118
+ });
119
+ // Emit events so client will keep up
120
+ states.node.emit(EVENTS.NODE_MAINTAIN_PROGRESS, session);
121
+ };
122
+
123
+ // 1. enter maintenance mode
124
+ if (session.stage === NODE_MAINTAIN_PROGRESS.SETUP) {
125
+ logger.info('enter maintenance mode', { from, to, sessionId });
126
+ await states.node.enterMode(NODE_MODES.MAINTENANCE);
127
+ if (action === 'upgrade') {
128
+ await goNextState(NODE_MAINTAIN_PROGRESS.INSTALLING, true);
129
+ }
130
+ if (action === 'restart') {
131
+ await goNextState(NODE_MAINTAIN_PROGRESS.VERIFYING, true);
132
+ }
133
+ }
134
+
135
+ // 2. install new version
136
+ if (session.stage === NODE_MAINTAIN_PROGRESS.INSTALLING) {
137
+ const result = await doRpc({ command: 'install', version: to });
138
+ logger.info('new version installed', { from, to, sessionId });
139
+ if (result.code !== 0) {
140
+ await sleep(3000);
141
+ }
142
+ await goNextState(NODE_MAINTAIN_PROGRESS.VERIFYING, result.code === 0);
143
+ }
144
+
145
+ // 2. verify new version
146
+ if (session.stage === NODE_MAINTAIN_PROGRESS.VERIFYING) {
147
+ const result = await doRpc({ command: 'verify', version: to });
148
+ await sleep(3000);
149
+ if (result.code === 0) {
150
+ logger.info('new version verified', { from, to, sessionId });
151
+ await goNextState(NODE_MAINTAIN_PROGRESS.RESTARTING, true);
152
+ } else {
153
+ logger.info('new version verify failed', { from, to, sessionId });
154
+ await goNextState(NODE_MAINTAIN_PROGRESS.ROLLBACK, false);
155
+ }
156
+ }
157
+
158
+ // 4. reset node.nextVersion/upgradeSessionId and exit maintenance mode
159
+ if (session.stage === NODE_MAINTAIN_PROGRESS.ROLLBACK) {
160
+ logger.info('rollback', { from, to, sessionId });
161
+ await sleep(3000);
162
+ await goNextState(NODE_MAINTAIN_PROGRESS.COMPLETE, true);
163
+ await sleep(5000);
164
+ await states.node.updateNodeInfo({ nextVersion: '', version: from, upgradeSessionId: '' });
165
+ try {
166
+ await states.node.exitMode(NODE_MODES.MAINTENANCE);
167
+ } catch (error) {
168
+ logger.error('Failed to rollback', { error });
169
+ }
170
+ await lock.release();
171
+ return;
172
+ }
173
+
174
+ // 3. restarting update version in node config and state
175
+ if (session.stage === NODE_MAINTAIN_PROGRESS.RESTARTING) {
176
+ logger.info('restart server', { from, to, sessionId });
177
+ await sleep(3000);
178
+ await goNextState(NODE_MAINTAIN_PROGRESS.CLEANUP, true);
179
+ await sleep(3000);
180
+ await doRpc({ command: 'restart', dataDir: process.env.ABT_NODE_DATA_DIR });
181
+ await lock.release();
182
+ return; // we should abort here and resume after restart
183
+ }
184
+
185
+ // 4. reset node.nextVersion/upgradeSessionId and exit maintenance mode
186
+ if (session.stage === NODE_MAINTAIN_PROGRESS.CLEANUP) {
187
+ logger.info('cleanup', { from, to, sessionId });
188
+ await goNextState(NODE_MAINTAIN_PROGRESS.COMPLETE, true);
189
+ await sleep(8000);
190
+ await states.node.updateNodeInfo({ nextVersion: '', version: to, upgradeSessionId: '' });
191
+ try {
192
+ await states.node.exitMode(NODE_MODES.MAINTENANCE);
193
+ } catch (error) {
194
+ logger.error('Failed to exit maintenance mode', { error });
195
+ }
196
+ }
197
+ await lock.release();
198
+ };
199
+
200
+ const isBeingMaintained = async () => {
201
+ const info = await states.node.read();
202
+ if (!info.upgradeSessionId) {
203
+ return false;
204
+ }
205
+
206
+ const session = await states.session.read(info.upgradeSessionId);
207
+ if (!session) {
208
+ return false;
209
+ }
210
+
211
+ if (session.action === 'upgrade' && !info.nextVersion) {
212
+ return false;
213
+ }
214
+
215
+ return session;
216
+ };
217
+
218
+ const getCron = () => ({
219
+ name: 'check-update',
220
+ time: '0 0 8 * * *', // check every day
221
+ // time: '0 */5 * * * *', // check every 5 minutes
222
+ fn: async () => {
223
+ const info = await states.node.read();
224
+ if (!info.autoUpgrade) {
225
+ return;
226
+ }
227
+
228
+ checkNewVersion();
229
+ },
230
+ options: { runOnInit: false },
231
+ });
232
+
233
+ module.exports = { getCron, checkNewVersion, resumeMaintain, triggerMaintain, isBeingMaintained };
@@ -0,0 +1,24 @@
1
+ const JOI = require('joi');
2
+ const { didExtension } = require('@blocklet/meta/lib/extension');
3
+ const { blockletController, createValidator } = require('./util');
4
+
5
+ const Joi = JOI.extend(didExtension);
6
+
7
+ const addMeta = Joi.object({
8
+ did: Joi.ref('meta.did'),
9
+ meta: Joi.object({
10
+ did: Joi.DID().required(),
11
+ name: Joi.string().required(),
12
+ }).required(),
13
+ controller: blockletController.optional(),
14
+ }).required();
15
+
16
+ const updateExpiredInfo = Joi.object({
17
+ did: Joi.DID().required(),
18
+ expiredAt: Joi.string().required(),
19
+ });
20
+
21
+ module.exports = {
22
+ validateAddMeta: createValidator(addMeta),
23
+ validateExpiredInfo: createValidator(updateExpiredInfo),
24
+ };
@@ -1,13 +1,4 @@
1
- const JOI = require('joi');
2
- const { didExtension } = require('@blocklet/meta/lib/extension');
3
-
4
- const Joi = JOI.extend(didExtension);
5
-
6
- const blockletController = Joi.object({
7
- nftId: Joi.DID().required(),
8
- nftOwner: Joi.DID().required(),
9
- appMaxCount: Joi.number().required().min(1),
10
- }).options({ stripUnknown: true });
1
+ const { blockletController } = require('./util');
11
2
 
12
3
  module.exports = {
13
4
  validateBlockletController: (entity) => blockletController.validateAsync(entity),
@@ -1,9 +1,25 @@
1
1
  const get = require('lodash/get');
2
+ const JOI = require('joi');
3
+ const { didExtension } = require('@blocklet/meta/lib/extension');
4
+
5
+ const Joi = JOI.extend(didExtension);
6
+
7
+ const getMultipleLangParams = (context) => ({
8
+ errors: {
9
+ language: get(context, 'query.locale', 'en'),
10
+ },
11
+ });
12
+
13
+ const blockletController = Joi.object({
14
+ nftId: Joi.DID().required(),
15
+ nftOwner: Joi.DID().required(),
16
+ appMaxCount: Joi.number().required().min(1),
17
+ }).options({ stripUnknown: true });
18
+
19
+ const createValidator = (schema) => (entity) => schema.validateAsync(entity);
2
20
 
3
21
  module.exports = {
4
- getMultipleLangParams: (context) => ({
5
- errors: {
6
- language: get(context, 'query.locale', 'en'),
7
- },
8
- }),
22
+ createValidator,
23
+ getMultipleLangParams,
24
+ blockletController,
9
25
  };
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.8.55",
6
+ "version": "1.8.57",
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.55",
23
- "@abtnode/certificate-manager": "1.8.55",
24
- "@abtnode/constant": "1.8.55",
25
- "@abtnode/cron": "1.8.55",
26
- "@abtnode/db": "1.8.55",
27
- "@abtnode/logger": "1.8.55",
28
- "@abtnode/queue": "1.8.55",
29
- "@abtnode/rbac": "1.8.55",
30
- "@abtnode/router-provider": "1.8.55",
31
- "@abtnode/static-server": "1.8.55",
32
- "@abtnode/timemachine": "1.8.55",
33
- "@abtnode/util": "1.8.55",
22
+ "@abtnode/auth": "1.8.57",
23
+ "@abtnode/certificate-manager": "1.8.57",
24
+ "@abtnode/constant": "1.8.57",
25
+ "@abtnode/cron": "1.8.57",
26
+ "@abtnode/db": "1.8.57",
27
+ "@abtnode/logger": "1.8.57",
28
+ "@abtnode/queue": "1.8.57",
29
+ "@abtnode/rbac": "1.8.57",
30
+ "@abtnode/router-provider": "1.8.57",
31
+ "@abtnode/static-server": "1.8.57",
32
+ "@abtnode/timemachine": "1.8.57",
33
+ "@abtnode/util": "1.8.57",
34
34
  "@arcblock/did": "1.18.34",
35
35
  "@arcblock/did-motif": "^1.1.10",
36
36
  "@arcblock/did-util": "1.18.34",
@@ -38,17 +38,19 @@
38
38
  "@arcblock/jwt": "^1.18.34",
39
39
  "@arcblock/pm2-events": "^0.0.5",
40
40
  "@arcblock/vc": "1.18.34",
41
- "@blocklet/constant": "1.8.55",
42
- "@blocklet/meta": "1.8.55",
43
- "@blocklet/sdk": "1.8.55",
41
+ "@blocklet/constant": "1.8.57",
42
+ "@blocklet/meta": "1.8.57",
43
+ "@blocklet/sdk": "1.8.57",
44
44
  "@fidm/x509": "^1.2.1",
45
45
  "@ocap/mcrypto": "1.18.34",
46
46
  "@ocap/util": "1.18.34",
47
47
  "@ocap/wallet": "1.18.34",
48
48
  "@slack/webhook": "^5.0.4",
49
+ "archiver": "^5.3.1",
49
50
  "axios": "^0.27.2",
50
51
  "axon": "^2.0.3",
51
52
  "chalk": "^4.1.2",
53
+ "dayjs": "^1.11.7",
52
54
  "deep-diff": "^1.0.2",
53
55
  "detect-port": "^1.5.1",
54
56
  "escape-string-regexp": "^4.0.0",
@@ -83,5 +85,5 @@
83
85
  "express": "^4.18.2",
84
86
  "jest": "^27.5.1"
85
87
  },
86
- "gitHead": "dce39bef7062b76648998b1f1b9a3c0a9b708dff"
88
+ "gitHead": "2573eb9370c79853cba88cba418a964fb1cc8949"
87
89
  }
@@ -1,175 +0,0 @@
1
- /* eslint-disable no-param-reassign */
2
- const semver = require('semver');
3
- const sleep = require('@abtnode/util/lib/sleep');
4
- const { NODE_MODES, NODE_UPGRADE_PROGRESS, EVENTS } = require('@abtnode/constant');
5
- const listNpmPackageVersion = require('@abtnode/util/lib/list-npm-package-version');
6
- const Lock = require('@abtnode/util/lib/lock');
7
- const logger = require('@abtnode/logger')('@abtnode/core:upgrade');
8
-
9
- const states = require('../states');
10
- const doRpc = require('./rpc');
11
-
12
- const lock = new Lock('node-upgrade-lock');
13
-
14
- // eslint-disable-next-line no-unused-vars
15
- const checkNewVersion = async (params, context) => {
16
- try {
17
- const info = await states.node.read();
18
-
19
- if (!process.env.ABT_NODE_PACKAGE_NAME) {
20
- logger.error('ABT_NODE_PACKAGE_NAME name was not found in environment');
21
- return '';
22
- }
23
-
24
- const versions = await listNpmPackageVersion(process.env.ABT_NODE_PACKAGE_NAME);
25
- if (Array.isArray(versions) && versions.length) {
26
- const latestVersion = versions[0].version;
27
- if (semver.gt(latestVersion, info.version)) {
28
- // if (semver.gte(latestVersion, info.version)) {
29
- logger.info('New version found for Blocklet Server', {
30
- latestVersion,
31
- currentVersion: info.version,
32
- nextVersion: info.nextVersion,
33
- });
34
- await states.node.updateNodeInfo({ nextVersion: latestVersion });
35
- await states.notification.create({
36
- title: 'Blocklet Server upgrade available',
37
- description: 'A new and improved version of blocklet server is now available',
38
- entityType: 'node',
39
- severity: 'info',
40
- sticky: true,
41
- action: '/settings/about',
42
- });
43
-
44
- return latestVersion;
45
- }
46
- }
47
-
48
- return '';
49
- } catch (err) {
50
- logger.error('Failed to check new version for Blocklet Server', { error: err });
51
- }
52
-
53
- return '';
54
- };
55
-
56
- // eslint-disable-next-line no-unused-vars
57
- const startUpgrade = async (params, context) => {
58
- const info = await states.node.read();
59
-
60
- if (!info.nextVersion) {
61
- throw new Error('Do nothing since there is no next version to upgrade');
62
- }
63
-
64
- const from = info.version;
65
- const to = info.nextVersion;
66
-
67
- // 0. ensure we have an upgrading session, we need a session to recover from crash
68
- let sessionId = info.upgradeSessionId;
69
- if (!sessionId) {
70
- const result = await states.session.start({ from, to, stage: NODE_UPGRADE_PROGRESS.SETUP, startedAt: Date.now() });
71
- sessionId = result.id;
72
- logger.info('generate new session for upgrade', { from, to, sessionId });
73
- await states.node.updateNodeInfo({ upgradeSessionId: sessionId });
74
- }
75
- const session = await states.session.read(sessionId);
76
- if (!session) {
77
- throw new Error(`Upgrade aborted due to invalid session: ${sessionId}`);
78
- }
79
-
80
- process.nextTick(async () => {
81
- await doUpgrade(session);
82
- });
83
-
84
- return sessionId;
85
- };
86
-
87
- // TODO: error handling and rollback support
88
- const doUpgrade = async (session) => {
89
- await lock.acquire();
90
-
91
- const sessionId = session.id;
92
- const info = await states.node.read();
93
- const from = info.version;
94
- const to = info.nextVersion;
95
-
96
- const goNextState = async (stage) => {
97
- session = await states.session.update(sessionId, { stage });
98
- // Emit events so client will keep up
99
- states.node.emit(EVENTS.NODE_UPGRADE_PROGRESS, session);
100
- };
101
-
102
- // 1. enter maintenance mode
103
- if (session.stage === NODE_UPGRADE_PROGRESS.SETUP) {
104
- logger.info('enter maintenance mode for upgrading', { from, to, sessionId });
105
- await states.node.enterMode(NODE_MODES.MAINTENANCE);
106
- await goNextState(NODE_UPGRADE_PROGRESS.INSTALLING);
107
- }
108
-
109
- // 2. install specified version
110
- if (session.stage === NODE_UPGRADE_PROGRESS.INSTALLING) {
111
- await doRpc({ command: 'install', version: to });
112
- logger.info('new version installed for upgrading', { from, to, sessionId });
113
- await goNextState(NODE_UPGRADE_PROGRESS.RESTARTING);
114
- }
115
-
116
- // 3. restarting update version in node config and state
117
- if (session.stage === NODE_UPGRADE_PROGRESS.RESTARTING) {
118
- logger.info('daemon restarted for upgrading', { from, to, sessionId });
119
- await sleep(3000);
120
- await goNextState(NODE_UPGRADE_PROGRESS.CLEANUP);
121
- await sleep(3000);
122
- await doRpc({ command: 'restart', dataDir: process.env.ABT_NODE_DATA_DIR });
123
- await lock.release();
124
- return; // we should abort here and resume after restart
125
- }
126
-
127
- // 4. reset node.nextVersion/upgradeSessionId and exit maintenance mode
128
- if (session.stage === NODE_UPGRADE_PROGRESS.CLEANUP) {
129
- logger.info('cleanup for upgrading', { from, to, sessionId });
130
- await goNextState(NODE_UPGRADE_PROGRESS.COMPLETE);
131
- await sleep(8000);
132
- await states.node.updateNodeInfo({ nextVersion: '', version: to, upgradeSessionId: '' });
133
- try {
134
- await states.node.exitMode(NODE_MODES.MAINTENANCE);
135
- } catch (error) {
136
- logger.error('Failed to exit maintenance mode', { error });
137
- }
138
- }
139
- await lock.release();
140
- };
141
-
142
- const isUpgrading = async () => {
143
- const info = await states.node.read();
144
- if (!info.nextVersion) {
145
- return false;
146
- }
147
-
148
- if (!info.upgradeSessionId) {
149
- return false;
150
- }
151
-
152
- const session = await states.session.read(info.upgradeSessionId);
153
- if (!session) {
154
- return false;
155
- }
156
-
157
- return session;
158
- };
159
-
160
- const getCron = () => ({
161
- name: 'check-update',
162
- time: '0 0 8 * * *', // check every day
163
- // time: '0 */5 * * * *', // check every 5 minutes
164
- fn: async () => {
165
- const info = await states.node.read();
166
- if (!info.autoUpgrade) {
167
- return;
168
- }
169
-
170
- checkNewVersion();
171
- },
172
- options: { runOnInit: false },
173
- });
174
-
175
- module.exports = { getCron, checkNewVersion, doUpgrade, startUpgrade, isUpgrading };