@abtnode/core 1.16.6-beta-8be2fe37 → 1.16.6-beta-4ea1eb90
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/blocklet/manager/disk.js +52 -15
- package/lib/blocklet/manager/helper/install-application-from-dev.js +5 -3
- package/lib/blocklet/manager/helper/install-component-from-url.js +3 -0
- package/lib/blocklet/storage/backup/spaces.js +5 -1
- package/lib/blocklet/storage/restore/spaces.js +5 -1
- package/lib/crons/check-new-version.js +68 -0
- package/lib/{util/disk-monitor.js → crons/monitor-disk-usage.js} +2 -2
- package/lib/crons/rotate-pm2-logs/index.js +41 -0
- package/lib/crons/rotate-pm2-logs/script.js +56 -0
- package/lib/index.js +7 -4
- package/lib/processes/updater.js +160 -0
- package/lib/states/node.js +1 -0
- package/lib/util/blocklet.js +39 -0
- package/lib/util/ip.js +0 -6
- package/lib/util/maintain.js +9 -64
- package/lib/util/rotator.js +194 -0
- package/lib/util/rpc.js +69 -8
- package/lib/util/sysinfo.js +9 -7
- package/package.json +32 -28
|
@@ -4,6 +4,7 @@ const fs = require('fs-extra');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const flat = require('flat');
|
|
6
6
|
const get = require('lodash/get');
|
|
7
|
+
const omit = require('lodash/omit');
|
|
7
8
|
const uniq = require('lodash/uniq');
|
|
8
9
|
const merge = require('lodash/merge');
|
|
9
10
|
const pick = require('lodash/pick');
|
|
@@ -61,6 +62,7 @@ const {
|
|
|
61
62
|
BLOCKLET_META_FILE,
|
|
62
63
|
BLOCKLET_CONFIGURABLE_KEY,
|
|
63
64
|
RESTORE_PROGRESS_STATUS,
|
|
65
|
+
CHAIN_PROP_MAP_REVERSE,
|
|
64
66
|
} = require('@blocklet/constant');
|
|
65
67
|
const util = require('../../util');
|
|
66
68
|
const {
|
|
@@ -79,6 +81,7 @@ const {
|
|
|
79
81
|
getBlockletStatusFromProcess,
|
|
80
82
|
checkBlockletProcessHealthy,
|
|
81
83
|
validateBlocklet,
|
|
84
|
+
validateBlockletChainInfo,
|
|
82
85
|
statusMap,
|
|
83
86
|
pruneBlockletBundle,
|
|
84
87
|
getDiskInfo,
|
|
@@ -99,6 +102,7 @@ const {
|
|
|
99
102
|
checkVersionCompatibility,
|
|
100
103
|
getBlockletKnownAs,
|
|
101
104
|
updateBlockletFallbackLogo,
|
|
105
|
+
ensureAppLogo,
|
|
102
106
|
} = require('../../util/blocklet');
|
|
103
107
|
const states = require('../../states');
|
|
104
108
|
const BaseBlockletManager = require('./base');
|
|
@@ -433,6 +437,7 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
433
437
|
|
|
434
438
|
// validate requirement and engine
|
|
435
439
|
await validateBlocklet(blocklet);
|
|
440
|
+
await validateBlockletChainInfo(blocklet);
|
|
436
441
|
|
|
437
442
|
if (!hasRunnableComponent(blocklet)) {
|
|
438
443
|
throw new Error('No runnable component found');
|
|
@@ -613,7 +618,7 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
613
618
|
* @memberof BlockletManager
|
|
614
619
|
*/
|
|
615
620
|
// eslint-disable-next-line no-unused-vars
|
|
616
|
-
async
|
|
621
|
+
async restoreBlocklet(input, context) {
|
|
617
622
|
const { from, ...param } = input;
|
|
618
623
|
if (from === 'spaces') {
|
|
619
624
|
return this._restoreFromSpaces(param, context);
|
|
@@ -999,7 +1004,6 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
999
1004
|
return getBackupList(this.dataDirs.data);
|
|
1000
1005
|
}
|
|
1001
1006
|
|
|
1002
|
-
// CAUTION: this method currently only support config by blocklet.meta.did
|
|
1003
1007
|
// eslint-disable-next-line no-unused-vars
|
|
1004
1008
|
async config({ did, configs: newConfigs, skipHook, skipDidDocument }, context) {
|
|
1005
1009
|
// todo: skipDidDocument will be deleted
|
|
@@ -1113,6 +1117,9 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
1113
1117
|
}
|
|
1114
1118
|
}
|
|
1115
1119
|
|
|
1120
|
+
// chain config
|
|
1121
|
+
await this._ensureAppChainConfig(rootMetaDid, newConfigs);
|
|
1122
|
+
|
|
1116
1123
|
await this._updateBlockletEnvironment(rootDid);
|
|
1117
1124
|
|
|
1118
1125
|
// response
|
|
@@ -1962,13 +1969,7 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
1962
1969
|
logger.info('blocklet installed', { source, did: meta.did });
|
|
1963
1970
|
|
|
1964
1971
|
// logo
|
|
1965
|
-
|
|
1966
|
-
const fileName = blocklet.children[0].meta.logo;
|
|
1967
|
-
const src = path.join(getBundleDir(this.installDir, blocklet.children[0].meta), fileName);
|
|
1968
|
-
const dist = path.join(getBundleDir(this.installDir, blocklet.meta), fileName);
|
|
1969
|
-
await fs.copy(src, dist);
|
|
1970
|
-
}
|
|
1971
|
-
|
|
1972
|
+
await ensureAppLogo(blocklet, this.installDir);
|
|
1972
1973
|
await updateBlockletFallbackLogo(blocklet);
|
|
1973
1974
|
|
|
1974
1975
|
// Init db
|
|
@@ -2346,6 +2347,9 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
2346
2347
|
|
|
2347
2348
|
// write configs to db
|
|
2348
2349
|
await states.blockletExtras.setConfigs([...ancestors.map((x) => x.meta.did), b.meta.did], environments);
|
|
2350
|
+
|
|
2351
|
+
// chain config
|
|
2352
|
+
await this._ensureAppChainConfig(blocklet.meta.did, environments, 'name');
|
|
2349
2353
|
});
|
|
2350
2354
|
} else {
|
|
2351
2355
|
const child = blocklet.children.find((x) => x.meta.did === childDid);
|
|
@@ -2354,6 +2358,9 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
2354
2358
|
[blocklet.meta.did, ...ancestors.map((x) => x.meta.did), b.meta.did],
|
|
2355
2359
|
[...get(b.meta, 'environments', []), ...getConfigFromPreferences(child)]
|
|
2356
2360
|
);
|
|
2361
|
+
|
|
2362
|
+
// chain config
|
|
2363
|
+
await this._ensureAppChainConfig(blocklet.meta.did, get(b.meta, 'environments', []), 'name');
|
|
2357
2364
|
});
|
|
2358
2365
|
}
|
|
2359
2366
|
}
|
|
@@ -2603,12 +2610,21 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
2603
2610
|
async _backupToSpaces({ blocklet }, context) {
|
|
2604
2611
|
const userDid = context.user.did;
|
|
2605
2612
|
const { referrer } = context;
|
|
2606
|
-
const {
|
|
2613
|
+
const {
|
|
2614
|
+
appDid,
|
|
2615
|
+
meta: { did: appPid },
|
|
2616
|
+
} = blocklet;
|
|
2607
2617
|
|
|
2608
|
-
const spacesBackup = new SpacesBackup({ appDid, event: this, userDid, referrer });
|
|
2609
|
-
this.emit(BlockletEvents.backupProgress, {
|
|
2618
|
+
const spacesBackup = new SpacesBackup({ appDid, appPid, event: this, userDid, referrer });
|
|
2619
|
+
this.emit(BlockletEvents.backupProgress, {
|
|
2620
|
+
appDid,
|
|
2621
|
+
meta: { did: appPid },
|
|
2622
|
+
message: 'Start backup...',
|
|
2623
|
+
progress: 10,
|
|
2624
|
+
completed: false,
|
|
2625
|
+
});
|
|
2610
2626
|
await spacesBackup.backup();
|
|
2611
|
-
this.emit(BlockletEvents.backupProgress, { appDid, completed: true, progress: 100 });
|
|
2627
|
+
this.emit(BlockletEvents.backupProgress, { appDid, meta: { did: appPid }, completed: true, progress: 100 });
|
|
2612
2628
|
}
|
|
2613
2629
|
|
|
2614
2630
|
/**
|
|
@@ -2634,11 +2650,17 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
2634
2650
|
await sleep(input.delay);
|
|
2635
2651
|
}
|
|
2636
2652
|
|
|
2637
|
-
|
|
2653
|
+
const appPid = input.appDid;
|
|
2654
|
+
|
|
2655
|
+
this.emit(BlockletEvents.restoreProgress, {
|
|
2656
|
+
appDid: input.appDid,
|
|
2657
|
+
meta: { did: appPid },
|
|
2658
|
+
status: RESTORE_PROGRESS_STATUS.start,
|
|
2659
|
+
});
|
|
2638
2660
|
|
|
2639
2661
|
const userDid = context.user.did;
|
|
2640
2662
|
|
|
2641
|
-
const spacesRestore = new SpacesRestore({ ...input, event: this, userDid, referrer: context.referrer });
|
|
2663
|
+
const spacesRestore = new SpacesRestore({ ...input, appPid, event: this, userDid, referrer: context.referrer });
|
|
2642
2664
|
const params = await spacesRestore.restore();
|
|
2643
2665
|
|
|
2644
2666
|
const removeRestoreDir = () => {
|
|
@@ -2702,6 +2724,21 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
2702
2724
|
throw error;
|
|
2703
2725
|
}
|
|
2704
2726
|
}
|
|
2727
|
+
|
|
2728
|
+
async _ensureAppChainConfig(metaDid, configs, key = 'key') {
|
|
2729
|
+
const chainConfigs = configs.filter((x) => ['CHAIN_HOST', 'CHAIN_ID', 'CHAIN_TYPE'].includes(x[key]));
|
|
2730
|
+
if (chainConfigs.length) {
|
|
2731
|
+
const items = chainConfigs.map((x) => ({
|
|
2732
|
+
...omit(x, ['description', 'validation']),
|
|
2733
|
+
[key]: CHAIN_PROP_MAP_REVERSE[x[key]],
|
|
2734
|
+
shared: true,
|
|
2735
|
+
secure: false,
|
|
2736
|
+
required: false,
|
|
2737
|
+
custom: false,
|
|
2738
|
+
}));
|
|
2739
|
+
await states.blockletExtras.setConfigs(metaDid, items);
|
|
2740
|
+
}
|
|
2741
|
+
}
|
|
2705
2742
|
}
|
|
2706
2743
|
|
|
2707
2744
|
module.exports = BlockletManager;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const { BlockletStatus, BLOCKLET_MODES, fromBlockletStatus, BlockletSource } = require('@blocklet/constant');
|
|
2
2
|
|
|
3
3
|
const logger = require('@abtnode/logger')('@abtnode/core:install-app-dev');
|
|
4
|
-
const { ensureMeta, updateBlockletFallbackLogo } = require('../../../util/blocklet');
|
|
4
|
+
const { ensureMeta, updateBlockletFallbackLogo, ensureAppLogo } = require('../../../util/blocklet');
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
*
|
|
@@ -79,10 +79,12 @@ const installApplicationFromDev = async ({ folder, meta, states, manager } = {})
|
|
|
79
79
|
|
|
80
80
|
await states.blocklet.setBlockletStatus(did, BlockletStatus.installed);
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
// logo
|
|
83
|
+
await ensureAppLogo(blocklet, manager.installDir);
|
|
84
84
|
await updateBlockletFallbackLogo(blocklet);
|
|
85
85
|
|
|
86
|
+
blocklet = await manager.getBlocklet(did);
|
|
87
|
+
|
|
86
88
|
return blocklet;
|
|
87
89
|
};
|
|
88
90
|
|
|
@@ -117,6 +117,9 @@ const installComponentFromUrl = async ({
|
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
await states.blockletExtras.setConfigs([blocklet.meta.did, newChild.meta.did], configs);
|
|
120
|
+
|
|
121
|
+
// chain config
|
|
122
|
+
await manager._ensureAppChainConfig(blocklet.meta.did, configs);
|
|
120
123
|
}
|
|
121
124
|
} catch (err) {
|
|
122
125
|
logger.error('Add component failed', err);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @typedef {{
|
|
3
3
|
* appDid: string
|
|
4
|
+
* appPid: string,
|
|
4
5
|
* event: import('events').EventEmitter,
|
|
5
6
|
* userDid: string,
|
|
6
7
|
* referrer: string,
|
|
@@ -147,6 +148,7 @@ class SpacesBackup extends BaseBackup {
|
|
|
147
148
|
async export() {
|
|
148
149
|
this.input.event.emit(BlockletEvents.backupProgress, {
|
|
149
150
|
appDid: this.input.appDid,
|
|
151
|
+
meta: { did: this.input.appPid },
|
|
150
152
|
message: 'Preparing data for backup...',
|
|
151
153
|
progress: 15,
|
|
152
154
|
completed: false,
|
|
@@ -156,6 +158,7 @@ class SpacesBackup extends BaseBackup {
|
|
|
156
158
|
|
|
157
159
|
this.input.event.emit(BlockletEvents.backupProgress, {
|
|
158
160
|
appDid: this.input.appDid,
|
|
161
|
+
meta: { did: this.input.appPid },
|
|
159
162
|
message: 'Data ready, start backup...',
|
|
160
163
|
progress: 20,
|
|
161
164
|
completed: false,
|
|
@@ -193,11 +196,12 @@ class SpacesBackup extends BaseBackup {
|
|
|
193
196
|
// FIXME: @yejianchao 这里需要更完整的黑名单
|
|
194
197
|
return object.name !== '.DS_Store';
|
|
195
198
|
},
|
|
196
|
-
|
|
199
|
+
onAfterUpload: (data) => {
|
|
197
200
|
logger.info('backup progress', { appDid: this.input.appDid, data });
|
|
198
201
|
const percent = (data.completed * 100) / data.total;
|
|
199
202
|
this.input.event.emit(BlockletEvents.backupProgress, {
|
|
200
203
|
appDid: this.input.appDid,
|
|
204
|
+
meta: { did: this.input.appDid },
|
|
201
205
|
message: `Uploading file ${basename(data.key)} (${data.completed}/${data.total})`,
|
|
202
206
|
// 0.8 是因为上传文件到 spaces 占进度的 80%,+ 20 是因为需要累加之前的进度
|
|
203
207
|
progress: +Math.ceil(percent * 0.8).toFixed(2) + 20,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @typedef {{
|
|
3
3
|
* appDid: string; // --> appDid
|
|
4
|
+
* appPid: string;
|
|
4
5
|
* endpoint: string;
|
|
5
6
|
* password: Buffer; // derived from (appSk, appDid)
|
|
6
7
|
* delegation: string; // from appDid --> serverDid for downloading
|
|
@@ -118,10 +119,11 @@ class SpacesRestore extends BaseRestore {
|
|
|
118
119
|
debug: true,
|
|
119
120
|
concurrency: 32,
|
|
120
121
|
retryCount: 10,
|
|
121
|
-
|
|
122
|
+
onAfterUpload: (data) => {
|
|
122
123
|
logger.info('restore progress', { appDid: this.input.appDid, data });
|
|
123
124
|
this.input.event.emit(BlockletEvents.restoreProgress, {
|
|
124
125
|
appDid: this.input.appDid,
|
|
126
|
+
meta: { did: this.input.appPid },
|
|
125
127
|
status: RESTORE_PROGRESS_STATUS.downloading,
|
|
126
128
|
data,
|
|
127
129
|
});
|
|
@@ -140,6 +142,7 @@ class SpacesRestore extends BaseRestore {
|
|
|
140
142
|
async import(params) {
|
|
141
143
|
this.input.event.emit(BlockletEvents.restoreProgress, {
|
|
142
144
|
appDid: this.input.appDid,
|
|
145
|
+
meta: { did: this.input.appPid },
|
|
143
146
|
status: RESTORE_PROGRESS_STATUS.importData,
|
|
144
147
|
});
|
|
145
148
|
|
|
@@ -150,6 +153,7 @@ class SpacesRestore extends BaseRestore {
|
|
|
150
153
|
);
|
|
151
154
|
this.input.event.emit(BlockletEvents.restoreProgress, {
|
|
152
155
|
appDid: this.input.appDid,
|
|
156
|
+
meta: { did: this.input.appPid },
|
|
153
157
|
status: RESTORE_PROGRESS_STATUS.importDataSuccess,
|
|
154
158
|
});
|
|
155
159
|
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/* eslint-disable no-param-reassign */
|
|
2
|
+
const semver = require('semver');
|
|
3
|
+
const listNpmPackageVersion = require('@abtnode/util/lib/list-npm-package-version');
|
|
4
|
+
const logger = require('@abtnode/logger')('@abtnode/core:maintain');
|
|
5
|
+
|
|
6
|
+
const states = require('../states');
|
|
7
|
+
|
|
8
|
+
// eslint-disable-next-line no-unused-vars
|
|
9
|
+
const checkNewVersion = async (params, context) => {
|
|
10
|
+
try {
|
|
11
|
+
const info = await states.node.read();
|
|
12
|
+
|
|
13
|
+
if (!process.env.ABT_NODE_PACKAGE_NAME) {
|
|
14
|
+
logger.error('ABT_NODE_PACKAGE_NAME name was not found in environment');
|
|
15
|
+
return '';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const versions = await listNpmPackageVersion(process.env.ABT_NODE_PACKAGE_NAME, {
|
|
19
|
+
includePrereleases: !!info.enableBetaRelease,
|
|
20
|
+
});
|
|
21
|
+
if (Array.isArray(versions) && versions.length) {
|
|
22
|
+
const latestVersionStr = versions[0].version;
|
|
23
|
+
const latestVersion = semver.coerce(latestVersionStr);
|
|
24
|
+
const currentVersion = semver.coerce(info.version);
|
|
25
|
+
if (latestVersionStr !== info.version && semver.gte(latestVersion, currentVersion)) {
|
|
26
|
+
logger.info('New version found for Blocklet Server', {
|
|
27
|
+
latestVersion: latestVersionStr,
|
|
28
|
+
currentVersion: info.version,
|
|
29
|
+
nextVersion: info.nextVersion,
|
|
30
|
+
});
|
|
31
|
+
await states.node.updateNodeInfo({ nextVersion: latestVersionStr });
|
|
32
|
+
await states.notification.create({
|
|
33
|
+
title: 'Blocklet Server upgrade available',
|
|
34
|
+
description: 'A new and improved version of blocklet server is now available',
|
|
35
|
+
entityType: 'node',
|
|
36
|
+
severity: 'info',
|
|
37
|
+
sticky: true,
|
|
38
|
+
action: '/settings/about',
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
return latestVersionStr;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return '';
|
|
46
|
+
} catch (err) {
|
|
47
|
+
logger.error('Failed to check new version for Blocklet Server', { error: err });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return '';
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const getCron = () => ({
|
|
54
|
+
name: 'check-new-version',
|
|
55
|
+
time: '0 0 8 * * *', // check every day
|
|
56
|
+
// time: '0 */5 * * * *', // check every 5 minutes
|
|
57
|
+
fn: async () => {
|
|
58
|
+
const info = await states.node.read();
|
|
59
|
+
if (!info.autoUpgrade) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
checkNewVersion();
|
|
64
|
+
},
|
|
65
|
+
options: { runOnInit: false },
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
module.exports = { getCron, checkNewVersion };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const logger = require('@abtnode/logger')('@abtnode/disk-monitor');
|
|
2
|
-
const info = require('
|
|
2
|
+
const info = require('../util/sysinfo');
|
|
3
3
|
|
|
4
4
|
const states = require('../states');
|
|
5
5
|
|
|
@@ -29,7 +29,7 @@ const check = async (threshold) => {
|
|
|
29
29
|
};
|
|
30
30
|
|
|
31
31
|
const getCron = () => ({
|
|
32
|
-
name: 'disk-
|
|
32
|
+
name: 'monitor-disk-usage',
|
|
33
33
|
time: '0 5 * * * *', // check every hour
|
|
34
34
|
fn: async () => {
|
|
35
35
|
const nodeInfo = await states.node.read();
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const spawn = require('cross-spawn');
|
|
4
|
+
const { LOG_RETAIN_IN_DAYS } = require('@abtnode/constant');
|
|
5
|
+
|
|
6
|
+
const getCron = () => ({
|
|
7
|
+
name: 'rotate-pm2-logs',
|
|
8
|
+
time: '0 0 * * *', // default : every day at midnight
|
|
9
|
+
options: { runOnInit: false },
|
|
10
|
+
fn: async () => {
|
|
11
|
+
console.info(`Log rotator started on ${new Date().toISOString()}`);
|
|
12
|
+
const child = spawn('node', [path.join(__dirname, './script.js')], {
|
|
13
|
+
detached: true,
|
|
14
|
+
windowsHide: true, // required for Windows
|
|
15
|
+
cwd: process.cwd(),
|
|
16
|
+
timeout: 30 * 60 * 1000, // 30 minutes
|
|
17
|
+
shell: process.env.SHELL || false,
|
|
18
|
+
stdio: ['ignore', process.stdout, process.stderr],
|
|
19
|
+
env: {
|
|
20
|
+
PATH: process.env.PATH,
|
|
21
|
+
PM2_HOME: process.env.PM2_HOME,
|
|
22
|
+
COMPRESS: true,
|
|
23
|
+
RETAIN: LOG_RETAIN_IN_DAYS,
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
child.on('error', (err) => {
|
|
28
|
+
console.error('Log rotator errored', err);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
child.on('close', (code) => {
|
|
32
|
+
console.info(`Log rotator exited with code ${code} on ${new Date().toISOString()}`);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
child.unref();
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
module.exports = {
|
|
40
|
+
getCron,
|
|
41
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
const pm2 = require('@abtnode/util/lib/async-pm2');
|
|
3
|
+
|
|
4
|
+
const LogRotator = require('../../util/rotator');
|
|
5
|
+
|
|
6
|
+
const RETAIN = parseInt(process.env.RETAIN || '', 10);
|
|
7
|
+
const COMPRESS = JSON.parse(process.env.COMPRESS || 'false');
|
|
8
|
+
const DATE_FORMAT = process.env.DATE_FORMAT || 'YYYY-MM-DD';
|
|
9
|
+
const { TZ } = process.env;
|
|
10
|
+
|
|
11
|
+
const logRotate = new LogRotator({ compress: COMPRESS, dateFormat: DATE_FORMAT, tz: TZ, retain: RETAIN });
|
|
12
|
+
|
|
13
|
+
// register the cron to force rotate file
|
|
14
|
+
console.info('Start rotating pm2 logs...');
|
|
15
|
+
|
|
16
|
+
// get list of process managed by pm2
|
|
17
|
+
pm2.connect((err) => {
|
|
18
|
+
if (err) {
|
|
19
|
+
console.error('Can not connect to pm2 daemon', err);
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// eslint-disable-next-line no-shadow
|
|
24
|
+
pm2.list(async (err, apps) => {
|
|
25
|
+
if (err) {
|
|
26
|
+
console.error('Can not list apps from pm2', err);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const appMap = {};
|
|
31
|
+
|
|
32
|
+
// force rotate for each app
|
|
33
|
+
await Promise.allSettled(
|
|
34
|
+
apps.map((app) => {
|
|
35
|
+
// if apps instances are multi and one of the instances has rotated, ignore
|
|
36
|
+
if (app.pm2_env.instances > 1 && appMap[app.name]) {
|
|
37
|
+
return Promise.resolve(true);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
appMap[app.name] = app;
|
|
41
|
+
|
|
42
|
+
return logRotate
|
|
43
|
+
.proceedPm2App(app)
|
|
44
|
+
.then(() => {
|
|
45
|
+
console.info(`Rotate ${app.name} log files succeed`);
|
|
46
|
+
})
|
|
47
|
+
.catch((error) => {
|
|
48
|
+
console.error(`Rotate ${app.name} log files failed`, { error });
|
|
49
|
+
});
|
|
50
|
+
})
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
console.info('Done rotating pm2 logs...');
|
|
54
|
+
process.exit(0);
|
|
55
|
+
});
|
|
56
|
+
});
|
package/lib/index.js
CHANGED
|
@@ -24,7 +24,9 @@ const IP = require('./util/ip');
|
|
|
24
24
|
const DomainStatus = require('./util/domain-status');
|
|
25
25
|
const Maintain = require('./util/maintain');
|
|
26
26
|
const resetNode = require('./util/reset-node');
|
|
27
|
-
const DiskMonitor = require('./
|
|
27
|
+
const DiskMonitor = require('./crons/monitor-disk-usage');
|
|
28
|
+
const LogRotator = require('./crons/rotate-pm2-logs');
|
|
29
|
+
const VersionChecker = require('./crons/check-new-version');
|
|
28
30
|
const StoreUtil = require('./util/store');
|
|
29
31
|
const createQueue = require('./util/queue');
|
|
30
32
|
const createEvents = require('./event');
|
|
@@ -235,7 +237,7 @@ function ABTNode(options) {
|
|
|
235
237
|
updateComponentTitle: blockletManager.updateComponentTitle.bind(blockletManager),
|
|
236
238
|
updateComponentMountPoint: blockletManager.updateComponentMountPoint.bind(blockletManager),
|
|
237
239
|
backupBlocklet: blockletManager.backup.bind(blockletManager),
|
|
238
|
-
restoreBlocklet: blockletManager.
|
|
240
|
+
restoreBlocklet: blockletManager.restoreBlocklet.bind(blockletManager),
|
|
239
241
|
migrateApplicationToStructV2: blockletManager.migrateApplicationToStructV2.bind(blockletManager),
|
|
240
242
|
|
|
241
243
|
// For diagnose purpose
|
|
@@ -402,8 +404,8 @@ function ABTNode(options) {
|
|
|
402
404
|
markMigrationExecuted: states.migration.markExecuted.bind(states.migration),
|
|
403
405
|
|
|
404
406
|
// Upgrading
|
|
407
|
+
checkNodeVersion: VersionChecker.checkNewVersion,
|
|
405
408
|
upgradeNodeVersion: () => Maintain.triggerMaintain({ action: 'upgrade', next: Maintain.resumeMaintain }),
|
|
406
|
-
checkNodeVersion: Maintain.checkNewVersion,
|
|
407
409
|
restartServer: () => Maintain.triggerMaintain({ action: 'restart', next: Maintain.resumeMaintain }),
|
|
408
410
|
isBeingMaintained: Maintain.isBeingMaintained,
|
|
409
411
|
resumeMaintain: Maintain.resumeMaintain,
|
|
@@ -452,10 +454,11 @@ function ABTNode(options) {
|
|
|
452
454
|
context: { states, events, webhook },
|
|
453
455
|
jobs: [
|
|
454
456
|
IP.cron,
|
|
455
|
-
|
|
457
|
+
VersionChecker.getCron(),
|
|
456
458
|
...getRoutingCrons(),
|
|
457
459
|
...blockletManager.getCrons(),
|
|
458
460
|
DiskMonitor.getCron(),
|
|
461
|
+
LogRotator.getCron(),
|
|
459
462
|
...getStateCrons(states),
|
|
460
463
|
...nodeAPI.getCrons(),
|
|
461
464
|
],
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
const axon = require('axon');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const shell = require('shelljs');
|
|
6
|
+
const semver = require('semver');
|
|
7
|
+
|
|
8
|
+
const VERBOSE = '';
|
|
9
|
+
// const VERBOSE = '--verbose';
|
|
10
|
+
|
|
11
|
+
const BINARY_NAME = process.env.ABT_NODE_BINARY_NAME;
|
|
12
|
+
const COMMAND_NAME = process.env.ABT_NODE_COMMAND_NAME;
|
|
13
|
+
const PACKAGE_NAME = process.env.ABT_NODE_PACKAGE_NAME;
|
|
14
|
+
const VALID_BINARY_NAMES = ['blocklet', 'abtnode'];
|
|
15
|
+
|
|
16
|
+
console.info(`binary name: ${BINARY_NAME}`);
|
|
17
|
+
console.info(`command name: ${COMMAND_NAME}`);
|
|
18
|
+
console.info(`package name: ${PACKAGE_NAME}`);
|
|
19
|
+
|
|
20
|
+
// async and promise style of shelljs.exec
|
|
21
|
+
const runAsync = (command, options) =>
|
|
22
|
+
new Promise((resolve) => {
|
|
23
|
+
console.info(`Run command: ${command}`);
|
|
24
|
+
shell.exec(command, { async: true, ...options }, (code, stdout, stderr) => {
|
|
25
|
+
resolve({ code, stdout, stderr });
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const getInstaller = () => {
|
|
30
|
+
const { stdout: pnpmPath } = shell.which('pnpm') || {};
|
|
31
|
+
if (pnpmPath) {
|
|
32
|
+
const { stdout: expectedDir } = shell.exec('pnpm bin -g', { silent: true });
|
|
33
|
+
const { stdout: binPath } = shell.which(BINARY_NAME);
|
|
34
|
+
if (expectedDir && binPath && expectedDir.trim() === path.dirname(binPath)) {
|
|
35
|
+
return 'pnpm';
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const { stdout: yarnPath } = shell.which('yarn') || {};
|
|
40
|
+
if (yarnPath) {
|
|
41
|
+
const { stdout: binaryDir } = shell.exec(`yarn global bin ${BINARY_NAME}`, { silent: true });
|
|
42
|
+
const binaryPath = path.join(binaryDir, BINARY_NAME);
|
|
43
|
+
if (fs.existsSync(binaryPath)) {
|
|
44
|
+
return 'yarn';
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return 'npm';
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const getBinaryDir = () => {
|
|
52
|
+
const installer = getInstaller();
|
|
53
|
+
if (installer === 'pnpm') {
|
|
54
|
+
const { stdout: binaryDir } = shell.exec('pnpm bin -g', { silent: true });
|
|
55
|
+
return binaryDir.trim();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (installer === 'yarn') {
|
|
59
|
+
const { stdout: binaryDir } = shell.exec(`yarn global bin ${BINARY_NAME}`, { silent: true });
|
|
60
|
+
return binaryDir.trim();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const { stdout: binaryDir } = shell.exec(`npm bin -g ${BINARY_NAME}`, { silent: true });
|
|
64
|
+
return binaryDir.trim();
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const installBlockletServer = async (version) => {
|
|
68
|
+
if (VALID_BINARY_NAMES.includes(BINARY_NAME) === false) {
|
|
69
|
+
return { code: 1, stderr: 'Abort because you are not using a standard @blocklet/cli setup' };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const installer = getInstaller();
|
|
73
|
+
const commands = {
|
|
74
|
+
pnpm: `pnpm add -g ${PACKAGE_NAME}@${version} ${VERBOSE}`,
|
|
75
|
+
npm: `npm install -g ${PACKAGE_NAME}@${version} ${VERBOSE}`,
|
|
76
|
+
yarn: `yarn global add ${PACKAGE_NAME}@${version} ${VERBOSE}`,
|
|
77
|
+
};
|
|
78
|
+
const command = commands[installer];
|
|
79
|
+
console.info('Installing blocklet server', { version, installer, command });
|
|
80
|
+
const result = await runAsync(command);
|
|
81
|
+
console.info('Installing blocklet server done', { version });
|
|
82
|
+
return result;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const verifyBlockletServer = async (version) => {
|
|
86
|
+
console.info('Verifying blocklet server', { version });
|
|
87
|
+
const result = await runAsync(`${BINARY_NAME} --version`);
|
|
88
|
+
console.info('Verifying blocklet server done', { version });
|
|
89
|
+
|
|
90
|
+
if (result.code === 0) {
|
|
91
|
+
const actual = semver.coerce(result.stdout);
|
|
92
|
+
if (actual && actual.version === version) {
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return { code: 1, stderr: `Blocklet server version is not expected ${version}` };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return result;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// Restart Blocklet Server
|
|
103
|
+
const restartBlockletServer = async (dataDir) => {
|
|
104
|
+
// We put this in the event loop because it will terminate the current node process
|
|
105
|
+
// But we need to wait for the session state to transition from restarting to cleanup
|
|
106
|
+
process.nextTick(async () => {
|
|
107
|
+
const binaryDir = getBinaryDir();
|
|
108
|
+
console.info('Restarting blocklet server', { dataDir, binaryDir });
|
|
109
|
+
|
|
110
|
+
if (!binaryDir) {
|
|
111
|
+
console.error('Can not restart blocklet server because no binaryDir found');
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// restart the server
|
|
116
|
+
if (VALID_BINARY_NAMES.includes(BINARY_NAME)) {
|
|
117
|
+
await runAsync(`${binaryDir}/${COMMAND_NAME} start`, { cwd: path.dirname(dataDir) });
|
|
118
|
+
} else {
|
|
119
|
+
await runAsync(`${COMMAND_NAME} start`, { cwd: path.dirname(dataDir) });
|
|
120
|
+
}
|
|
121
|
+
console.info('Restarting blocklet server done');
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
return dataDir;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
// Start the server
|
|
128
|
+
const sock = axon.socket('rep');
|
|
129
|
+
const port = Number(process.env.ABT_NODE_UPDATER_PORT);
|
|
130
|
+
sock.bind(port, '127.0.0.1');
|
|
131
|
+
sock.on('message', async (raw, reply) => {
|
|
132
|
+
console.info('receive', { raw });
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
const message = JSON.parse(raw);
|
|
136
|
+
if (message.command === 'install') {
|
|
137
|
+
const result = await installBlockletServer(message.version);
|
|
138
|
+
reply(result);
|
|
139
|
+
} else if (message.command === 'verify') {
|
|
140
|
+
const result = await verifyBlockletServer(message.version);
|
|
141
|
+
reply(result);
|
|
142
|
+
} else if (message.command === 'restart') {
|
|
143
|
+
const result = await restartBlockletServer(message.dataDir);
|
|
144
|
+
reply(result);
|
|
145
|
+
} else if (message.command === 'shutdown') {
|
|
146
|
+
reply('Terminate myself after this message');
|
|
147
|
+
setTimeout(() => {
|
|
148
|
+
process.exit(0);
|
|
149
|
+
}, 500);
|
|
150
|
+
} else if (message.command === 'ping') {
|
|
151
|
+
reply('pong');
|
|
152
|
+
} else {
|
|
153
|
+
reply({ error: 'Unknown command' });
|
|
154
|
+
}
|
|
155
|
+
} catch (err) {
|
|
156
|
+
console.error('failed to process command', { error: err });
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
console.info(`Blocklet Server Updater process is ready on port ${port}`);
|
package/lib/states/node.js
CHANGED
package/lib/util/blocklet.js
CHANGED
|
@@ -18,6 +18,7 @@ const isUrl = require('is-url');
|
|
|
18
18
|
const semver = require('semver');
|
|
19
19
|
const axios = require('@abtnode/util/lib/axios');
|
|
20
20
|
const { stableStringify } = require('@arcblock/vc');
|
|
21
|
+
const { chainInfo: chainInfoSchema } = require('@arcblock/did-auth/lib/schema');
|
|
21
22
|
|
|
22
23
|
const { fromSecretKey } = require('@ocap/wallet');
|
|
23
24
|
const { toHex, toBase58, isHex, toDid } = require('@ocap/util');
|
|
@@ -78,6 +79,7 @@ const {
|
|
|
78
79
|
isEnvShareable,
|
|
79
80
|
getBlockletAppIdList,
|
|
80
81
|
isBeforeInstalled,
|
|
82
|
+
getChainInfo,
|
|
81
83
|
} = require('@blocklet/meta/lib/util');
|
|
82
84
|
const toBlockletDid = require('@blocklet/meta/lib/did');
|
|
83
85
|
const { titleSchema, descriptionSchema, logoSchema } = require('@blocklet/meta/lib/schema');
|
|
@@ -884,6 +886,21 @@ const validateBlocklet = (blocklet) =>
|
|
|
884
886
|
validateEngine(getBlockletEngineNameByPlatform(b.meta));
|
|
885
887
|
});
|
|
886
888
|
|
|
889
|
+
const validateBlockletChainInfo = (blocklet) => {
|
|
890
|
+
const chainInfo = getChainInfo({
|
|
891
|
+
CHAIN_TYPE: blocklet.configObj[BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_CHAIN_TYPE],
|
|
892
|
+
CHAIN_ID: blocklet.configObj[BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_CHAIN_ID],
|
|
893
|
+
CHAIN_HOST: blocklet.configObj[BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_CHAIN_HOST],
|
|
894
|
+
});
|
|
895
|
+
|
|
896
|
+
const { error } = chainInfoSchema.validate(chainInfo);
|
|
897
|
+
if (error) {
|
|
898
|
+
throw error;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
return chainInfo;
|
|
902
|
+
};
|
|
903
|
+
|
|
887
904
|
const checkBlockletProcessHealthy = async (blocklet, { minConsecutiveTime, timeout } = {}) => {
|
|
888
905
|
await forEachBlocklet(
|
|
889
906
|
blocklet,
|
|
@@ -1885,6 +1902,26 @@ const updateBlockletFallbackLogo = async (blocklet) => {
|
|
|
1885
1902
|
}
|
|
1886
1903
|
};
|
|
1887
1904
|
|
|
1905
|
+
const ensureAppLogo = async (blocklet, blockletsDir) => {
|
|
1906
|
+
if (!blocklet) {
|
|
1907
|
+
return;
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
if (
|
|
1911
|
+
blocklet.source === BlockletSource.custom &&
|
|
1912
|
+
(blocklet.children || [])[0]?.meta?.logo &&
|
|
1913
|
+
blocklet.children[0].env.appDir
|
|
1914
|
+
) {
|
|
1915
|
+
const fileName = blocklet.children[0].meta.logo;
|
|
1916
|
+
const src = path.join(blocklet.children[0].env.appDir, fileName);
|
|
1917
|
+
const dist = path.join(getBundleDir(blockletsDir, blocklet.meta), fileName);
|
|
1918
|
+
|
|
1919
|
+
if (fs.existsSync(src)) {
|
|
1920
|
+
await fs.copy(src, dist);
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
};
|
|
1924
|
+
|
|
1888
1925
|
module.exports = {
|
|
1889
1926
|
updateBlockletFallbackLogo,
|
|
1890
1927
|
consumeServerlessNFT,
|
|
@@ -1897,6 +1934,7 @@ module.exports = {
|
|
|
1897
1934
|
getComponentSystemEnvironments,
|
|
1898
1935
|
getRuntimeEnvironments,
|
|
1899
1936
|
validateBlocklet,
|
|
1937
|
+
validateBlockletChainInfo,
|
|
1900
1938
|
fillBlockletConfigs,
|
|
1901
1939
|
ensureBlockletExpanded,
|
|
1902
1940
|
startBlockletProcess,
|
|
@@ -1941,4 +1979,5 @@ module.exports = {
|
|
|
1941
1979
|
validateBlockletMeta,
|
|
1942
1980
|
getBlockletKnownAs,
|
|
1943
1981
|
getFixedBundleSource,
|
|
1982
|
+
ensureAppLogo,
|
|
1944
1983
|
};
|
package/lib/util/ip.js
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
const getIp = require('@abtnode/util/lib/get-ip');
|
|
2
2
|
const logger = require('@abtnode/logger')('@abtnode/core:ip');
|
|
3
3
|
|
|
4
|
-
const doRpc = require('./rpc');
|
|
5
|
-
const { memoizeAsync } = require('./index');
|
|
6
|
-
|
|
7
4
|
let cache = null;
|
|
8
5
|
const fetch = async () => {
|
|
9
6
|
try {
|
|
@@ -25,9 +22,6 @@ const cron = {
|
|
|
25
22
|
fn: fetch,
|
|
26
23
|
};
|
|
27
24
|
|
|
28
|
-
const lookup = memoizeAsync((ip) => doRpc({ command: 'lookup', ip }));
|
|
29
|
-
|
|
30
25
|
module.exports.fetch = fetch;
|
|
31
26
|
module.exports.get = get;
|
|
32
27
|
module.exports.cron = cron;
|
|
33
|
-
module.exports.lookup = lookup;
|
package/lib/util/maintain.js
CHANGED
|
@@ -1,59 +1,15 @@
|
|
|
1
1
|
/* eslint-disable no-param-reassign */
|
|
2
|
-
const semver = require('semver');
|
|
3
2
|
const sleep = require('@abtnode/util/lib/sleep');
|
|
4
3
|
const SysInfo = require('systeminformation');
|
|
5
4
|
const { NODE_MODES, NODE_MAINTAIN_PROGRESS, EVENTS } = require('@abtnode/constant');
|
|
6
|
-
const listNpmPackageVersion = require('@abtnode/util/lib/list-npm-package-version');
|
|
7
5
|
const Lock = require('@abtnode/util/lib/lock');
|
|
8
6
|
const logger = require('@abtnode/logger')('@abtnode/core:maintain');
|
|
9
7
|
|
|
10
8
|
const states = require('../states');
|
|
11
|
-
const
|
|
9
|
+
const { doRpcCall, startRpcServer } = require('./rpc');
|
|
12
10
|
|
|
13
11
|
const lock = new Lock('node-upgrade-lock');
|
|
14
12
|
|
|
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
13
|
const triggerMaintain = async ({ action, next }) => {
|
|
58
14
|
if (['upgrade', 'restart'].includes(action) === false) {
|
|
59
15
|
throw new Error(`Unrecognized server maintain action: ${action}`);
|
|
@@ -98,6 +54,8 @@ const triggerMaintain = async ({ action, next }) => {
|
|
|
98
54
|
throw new Error(`${action} aborted due to invalid session: ${sessionId}`);
|
|
99
55
|
}
|
|
100
56
|
|
|
57
|
+
await startRpcServer();
|
|
58
|
+
|
|
101
59
|
process.nextTick(async () => {
|
|
102
60
|
await next(session);
|
|
103
61
|
});
|
|
@@ -134,7 +92,7 @@ const resumeMaintain = async (session) => {
|
|
|
134
92
|
|
|
135
93
|
// 2. install new version
|
|
136
94
|
if (session.stage === NODE_MAINTAIN_PROGRESS.INSTALLING) {
|
|
137
|
-
const result = await
|
|
95
|
+
const result = await doRpcCall({ command: 'install', version: to });
|
|
138
96
|
logger.info('new version installed', { from, to, sessionId });
|
|
139
97
|
if (result.code !== 0) {
|
|
140
98
|
await sleep(3000);
|
|
@@ -144,7 +102,7 @@ const resumeMaintain = async (session) => {
|
|
|
144
102
|
|
|
145
103
|
// 2. verify new version
|
|
146
104
|
if (session.stage === NODE_MAINTAIN_PROGRESS.VERIFYING) {
|
|
147
|
-
const result = await
|
|
105
|
+
const result = await doRpcCall({ command: 'verify', version: to });
|
|
148
106
|
await sleep(3000);
|
|
149
107
|
if (result.code === 0) {
|
|
150
108
|
logger.info('new version verified', { from, to, sessionId });
|
|
@@ -164,6 +122,7 @@ const resumeMaintain = async (session) => {
|
|
|
164
122
|
await states.node.updateNodeInfo({ nextVersion: '', version: from, upgradeSessionId: '' });
|
|
165
123
|
try {
|
|
166
124
|
await states.node.exitMode(NODE_MODES.MAINTENANCE);
|
|
125
|
+
await doRpcCall({ command: 'shutdown' });
|
|
167
126
|
} catch (error) {
|
|
168
127
|
logger.error('Failed to rollback', { error });
|
|
169
128
|
}
|
|
@@ -177,7 +136,7 @@ const resumeMaintain = async (session) => {
|
|
|
177
136
|
await sleep(3000);
|
|
178
137
|
await goNextState(NODE_MAINTAIN_PROGRESS.CLEANUP, true);
|
|
179
138
|
await sleep(3000);
|
|
180
|
-
await
|
|
139
|
+
await doRpcCall({ command: 'restart', dataDir: process.env.ABT_NODE_DATA_DIR });
|
|
181
140
|
await lock.release();
|
|
182
141
|
return; // we should abort here and resume after restart
|
|
183
142
|
}
|
|
@@ -190,6 +149,7 @@ const resumeMaintain = async (session) => {
|
|
|
190
149
|
await states.node.updateNodeInfo({ nextVersion: '', version: to, upgradeSessionId: '' });
|
|
191
150
|
try {
|
|
192
151
|
await states.node.exitMode(NODE_MODES.MAINTENANCE);
|
|
152
|
+
await doRpcCall({ command: 'shutdown' });
|
|
193
153
|
} catch (error) {
|
|
194
154
|
logger.error('Failed to exit maintenance mode', { error });
|
|
195
155
|
}
|
|
@@ -215,19 +175,4 @@ const isBeingMaintained = async () => {
|
|
|
215
175
|
return session;
|
|
216
176
|
};
|
|
217
177
|
|
|
218
|
-
|
|
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 };
|
|
178
|
+
module.exports = { resumeMaintain, triggerMaintain, isBeingMaintained };
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base on: https://github.com/keymetrics/pm2-logrotate
|
|
3
|
+
*/
|
|
4
|
+
const fs = require('fs-extra');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const moment = require('moment-timezone');
|
|
7
|
+
const zlib = require('zlib');
|
|
8
|
+
const log = require('@abtnode/logger');
|
|
9
|
+
|
|
10
|
+
const buildLogger =
|
|
11
|
+
(func) =>
|
|
12
|
+
(...args) => {
|
|
13
|
+
if (typeof args[0] === 'string') {
|
|
14
|
+
func(`pm2.log.rotate.${args[0]}`, ...args.slice(1));
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
func('pm2.log.rotate', ...args);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// eslint-disable-next-line no-console
|
|
22
|
+
const logInfo = buildLogger(console.info);
|
|
23
|
+
const logError = buildLogger(console.error);
|
|
24
|
+
|
|
25
|
+
module.exports = class LogRotate {
|
|
26
|
+
constructor({ compress, dateFormat, tz, retain } = {}) {
|
|
27
|
+
this.compress = compress;
|
|
28
|
+
this.dateFormat = dateFormat;
|
|
29
|
+
this.tz = tz;
|
|
30
|
+
this.retain = retain;
|
|
31
|
+
this.compressedFileFormat = '.gz';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
getLimitSize(maxSize) {
|
|
35
|
+
if (!maxSize) {
|
|
36
|
+
return 1024 * 1024 * 10; // 10M
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (typeof maxSize === 'number') {
|
|
40
|
+
return maxSize;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (typeof maxSize !== 'string') {
|
|
44
|
+
throw new Error('maxSize must be a string or number');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// eslint-disable-next-line no-param-reassign
|
|
48
|
+
maxSize = maxSize.trim();
|
|
49
|
+
|
|
50
|
+
const unit = maxSize[maxSize.length - 1].toUpperCase();
|
|
51
|
+
if (unit === 'G') {
|
|
52
|
+
return parseInt(maxSize, 10) * 1024 * 1024 * 1024;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (unit === 'M') {
|
|
56
|
+
return parseInt(maxSize, 10) * 1024 * 1024;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (unit === 'K') {
|
|
60
|
+
return parseInt(maxSize, 10) * 1024;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return parseInt(maxSize, 10);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Apply the rotation process of the log file.
|
|
68
|
+
*
|
|
69
|
+
* @param {string} file
|
|
70
|
+
*/
|
|
71
|
+
proceed(file, callback = () => {}) {
|
|
72
|
+
// set default final time
|
|
73
|
+
let finalTime = moment().subtract(1, 'day').format(this.dateFormat);
|
|
74
|
+
// check for a timezone
|
|
75
|
+
if (this.tz) {
|
|
76
|
+
try {
|
|
77
|
+
finalTime = moment().tz(this.tz).subtract(1, 'day').format(this.dateFormat);
|
|
78
|
+
} catch (err) {
|
|
79
|
+
// use default
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const filename = path.basename(file, path.extname(file));
|
|
84
|
+
const dirname = path.dirname(file);
|
|
85
|
+
const extName = this.compress ? `.log${this.compressedFileFormat}` : '.log';
|
|
86
|
+
|
|
87
|
+
let finalName = `${path.join(dirname, filename)}-${finalTime}${extName}`;
|
|
88
|
+
|
|
89
|
+
// 这种情况是为了兼容,之间的日志文件名中的日志大了一天,所以升级时旧数据可能存在重复
|
|
90
|
+
if (fs.existsSync(finalName)) {
|
|
91
|
+
finalName = `${path.join(dirname, filename)}-${finalTime}.1${extName}`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// if compression is enabled, add gz extention and create a gzip instance
|
|
95
|
+
let GZIP;
|
|
96
|
+
if (this.compress) {
|
|
97
|
+
GZIP = zlib.createGzip({
|
|
98
|
+
level: zlib.constants.Z_BEST_COMPRESSION,
|
|
99
|
+
memLevel: zlib.constants.Z_BEST_COMPRESSION,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// create our read/write streams
|
|
104
|
+
const readStream = fs.createReadStream(file);
|
|
105
|
+
const writeStream = fs.createWriteStream(finalName, { flags: 'w+' });
|
|
106
|
+
|
|
107
|
+
// pipe all stream
|
|
108
|
+
if (this.compress) {
|
|
109
|
+
readStream.pipe(GZIP).pipe(writeStream);
|
|
110
|
+
} else {
|
|
111
|
+
readStream.pipe(writeStream);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const onError = (err) => {
|
|
115
|
+
logError(err);
|
|
116
|
+
callback(err);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// listen for error
|
|
120
|
+
readStream.on('error', onError);
|
|
121
|
+
writeStream.on('error', onError);
|
|
122
|
+
if (this.compression) {
|
|
123
|
+
GZIP.on('error', onError);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// when the read is done, empty the file and check for retain option
|
|
127
|
+
writeStream.on('finish', () => {
|
|
128
|
+
if (GZIP) {
|
|
129
|
+
GZIP.close();
|
|
130
|
+
}
|
|
131
|
+
readStream.close();
|
|
132
|
+
writeStream.close();
|
|
133
|
+
|
|
134
|
+
fs.truncateSync(file);
|
|
135
|
+
logInfo('rotated file:', finalName);
|
|
136
|
+
|
|
137
|
+
callback(null, finalName);
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
proceedSync(file) {
|
|
142
|
+
return new Promise((resolve, reject) => {
|
|
143
|
+
this.proceed(file, (err, res) => {
|
|
144
|
+
if (err) {
|
|
145
|
+
return reject(err);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return resolve(res);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Apply the rotation process if the `file` size exceeds the `SIZE_LIMIT`.
|
|
155
|
+
*
|
|
156
|
+
* @param {string} file
|
|
157
|
+
*/
|
|
158
|
+
async proceedFile(file) {
|
|
159
|
+
if (!fs.existsSync(file)) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (fs.statSync(file).size > 0) {
|
|
164
|
+
await this.proceedSync(file);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
log.deleteOldLogfiles(file, this.retain);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Apply the rotation process of all log files of `app` where the file size exceeds the`SIZE_LIMIT`.
|
|
172
|
+
*
|
|
173
|
+
* @param {Object} app
|
|
174
|
+
*/
|
|
175
|
+
async proceedPm2App(app) {
|
|
176
|
+
// Check all log path
|
|
177
|
+
// Note: If same file is defined for multiple purposes, it will be processed once only.
|
|
178
|
+
if (app.pm2_env.pm_out_log_path) {
|
|
179
|
+
await this.proceedFile(app.pm2_env.pm_out_log_path);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (app.pm2_env.pm_err_log_path && app.pm2_env.pm_err_log_path !== app.pm2_env.pm_out_log_path) {
|
|
183
|
+
await this.proceedFile(app.pm2_env.pm_err_log_path);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (
|
|
187
|
+
app.pm2_env.pm_log_path &&
|
|
188
|
+
app.pm2_env.pm_log_path !== app.pm2_env.pm_out_log_path &&
|
|
189
|
+
app.pm2_env.pm_log_path !== app.pm2_env.pm_err_log_path
|
|
190
|
+
) {
|
|
191
|
+
await this.proceedFile(app.pm2_env.pm_log_path);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
};
|
package/lib/util/rpc.js
CHANGED
|
@@ -1,16 +1,77 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
1
2
|
const axon = require('axon');
|
|
3
|
+
const kill = require('kill-port');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const spawn = require('cross-spawn');
|
|
6
|
+
|
|
2
7
|
const logger = require('@abtnode/logger')('@abtnode/core:rpc');
|
|
3
8
|
|
|
4
|
-
const
|
|
5
|
-
|
|
9
|
+
const host = '127.0.0.1';
|
|
10
|
+
const port = Number(process.env.ABT_NODE_UPDATER_PORT || 40405);
|
|
6
11
|
|
|
7
|
-
const
|
|
12
|
+
const doRpcCall = async (message) =>
|
|
8
13
|
new Promise((resolve) => {
|
|
9
|
-
logger.info('send rpc request', message);
|
|
10
|
-
sock.
|
|
11
|
-
|
|
12
|
-
|
|
14
|
+
logger.info('try send rpc request', message);
|
|
15
|
+
const sock = axon.socket('req');
|
|
16
|
+
sock.connect(port, host);
|
|
17
|
+
sock.on('connect', () => {
|
|
18
|
+
sock.send(JSON.stringify(message), (result) => {
|
|
19
|
+
logger.info('receive rpc response', result);
|
|
20
|
+
sock.close();
|
|
21
|
+
resolve(result);
|
|
22
|
+
});
|
|
13
23
|
});
|
|
14
24
|
});
|
|
15
25
|
|
|
16
|
-
|
|
26
|
+
const startRpcServer = async () => {
|
|
27
|
+
try {
|
|
28
|
+
await kill(port, 'tcp');
|
|
29
|
+
console.info('Killed existing rpc server');
|
|
30
|
+
} catch (err) {
|
|
31
|
+
console.error('Failed to kill existing rpc server', err);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
console.info(`Rpc Server started on ${new Date().toISOString()}`);
|
|
35
|
+
const child = spawn('node', [path.join(__dirname, '../processes/updater.js')], {
|
|
36
|
+
detached: true,
|
|
37
|
+
windowsHide: true, // required for Windows
|
|
38
|
+
cwd: process.cwd(),
|
|
39
|
+
timeout: 60 * 60 * 1000, // 60 minutes
|
|
40
|
+
shell: process.env.SHELL || false,
|
|
41
|
+
stdio: ['ignore', process.stdout, process.stderr],
|
|
42
|
+
env: {
|
|
43
|
+
PATH: process.env.PATH,
|
|
44
|
+
PM2_HOME: process.env.PM2_HOME,
|
|
45
|
+
ABT_NODE_UPDATER_PORT: process.env.ABT_NODE_UPDATER_PORT,
|
|
46
|
+
ABT_NODE_PACKAGE_NAME: process.env.ABT_NODE_PACKAGE_NAME,
|
|
47
|
+
ABT_NODE_BINARY_NAME: process.env.ABT_NODE_BINARY_NAME,
|
|
48
|
+
ABT_NODE_COMMAND_NAME: process.env.ABT_NODE_COMMAND_NAME,
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
child.on('error', (err) => {
|
|
53
|
+
console.error('Rpc Server errored', err);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
child.on('close', (code) => {
|
|
57
|
+
console.info(`Rpc Server exited with code ${code} on ${new Date().toISOString()}`);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
child.unref();
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
module.exports = { doRpcCall, startRpcServer };
|
|
64
|
+
|
|
65
|
+
// Following script used to test the upgrade process
|
|
66
|
+
// process.env.SHELL = '/opt/homebrew/bin/zsh';
|
|
67
|
+
// process.env.PM2_HOME = '/Users/wangshijun/.arcblock/abtnode';
|
|
68
|
+
// process.env.ABT_NODE_UPDATER_PORT = '40405';
|
|
69
|
+
// process.env.ABT_NODE_PACKAGE_NAME = '@blocklet/cli';
|
|
70
|
+
// process.env.ABT_NODE_BINARY_NAME = 'blocklet';
|
|
71
|
+
// process.env.ABT_NODE_COMMAND_NAME = 'blocklet server';
|
|
72
|
+
// startRpcServer().then(async () => {
|
|
73
|
+
// let result = await doRpcCall({ command: 'verify', version: '1.16.5' });
|
|
74
|
+
// console.log(result);
|
|
75
|
+
// result = await doRpcCall({ command: 'shutdown', version: '1.16.5' });
|
|
76
|
+
// console.log(result);
|
|
77
|
+
// });
|
package/lib/util/sysinfo.js
CHANGED
|
@@ -31,13 +31,15 @@ const getSysInfo = async () => {
|
|
|
31
31
|
},
|
|
32
32
|
mem: info.mem,
|
|
33
33
|
os: info.osInfo,
|
|
34
|
-
disks: drives
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
disks: drives
|
|
35
|
+
.map((x) => ({
|
|
36
|
+
device: x.fs,
|
|
37
|
+
mountPoint: x.mount,
|
|
38
|
+
total: x.size,
|
|
39
|
+
used: x.used,
|
|
40
|
+
free: x.size - x.used,
|
|
41
|
+
}))
|
|
42
|
+
.filter((x) => x.total > 0),
|
|
41
43
|
};
|
|
42
44
|
};
|
|
43
45
|
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "1.16.6-beta-
|
|
6
|
+
"version": "1.16.6-beta-4ea1eb90",
|
|
7
7
|
"description": "",
|
|
8
8
|
"main": "lib/index.js",
|
|
9
9
|
"files": [
|
|
@@ -19,39 +19,41 @@
|
|
|
19
19
|
"author": "wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)",
|
|
20
20
|
"license": "MIT",
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@abtnode/auth": "1.16.6-beta-
|
|
23
|
-
"@abtnode/certificate-manager": "1.16.6-beta-
|
|
24
|
-
"@abtnode/constant": "1.16.6-beta-
|
|
25
|
-
"@abtnode/cron": "1.16.6-beta-
|
|
26
|
-
"@abtnode/db": "1.16.6-beta-
|
|
27
|
-
"@abtnode/logger": "1.16.6-beta-
|
|
28
|
-
"@abtnode/queue": "1.16.6-beta-
|
|
29
|
-
"@abtnode/rbac": "1.16.6-beta-
|
|
30
|
-
"@abtnode/router-provider": "1.16.6-beta-
|
|
31
|
-
"@abtnode/static-server": "1.16.6-beta-
|
|
32
|
-
"@abtnode/timemachine": "1.16.6-beta-
|
|
33
|
-
"@abtnode/util": "1.16.6-beta-
|
|
34
|
-
"@arcblock/did": "1.18.
|
|
22
|
+
"@abtnode/auth": "1.16.6-beta-4ea1eb90",
|
|
23
|
+
"@abtnode/certificate-manager": "1.16.6-beta-4ea1eb90",
|
|
24
|
+
"@abtnode/constant": "1.16.6-beta-4ea1eb90",
|
|
25
|
+
"@abtnode/cron": "1.16.6-beta-4ea1eb90",
|
|
26
|
+
"@abtnode/db": "1.16.6-beta-4ea1eb90",
|
|
27
|
+
"@abtnode/logger": "1.16.6-beta-4ea1eb90",
|
|
28
|
+
"@abtnode/queue": "1.16.6-beta-4ea1eb90",
|
|
29
|
+
"@abtnode/rbac": "1.16.6-beta-4ea1eb90",
|
|
30
|
+
"@abtnode/router-provider": "1.16.6-beta-4ea1eb90",
|
|
31
|
+
"@abtnode/static-server": "1.16.6-beta-4ea1eb90",
|
|
32
|
+
"@abtnode/timemachine": "1.16.6-beta-4ea1eb90",
|
|
33
|
+
"@abtnode/util": "1.16.6-beta-4ea1eb90",
|
|
34
|
+
"@arcblock/did": "1.18.72",
|
|
35
|
+
"@arcblock/did-auth": "1.18.72",
|
|
35
36
|
"@arcblock/did-motif": "^1.1.10",
|
|
36
|
-
"@arcblock/did-util": "1.18.
|
|
37
|
-
"@arcblock/event-hub": "1.18.
|
|
38
|
-
"@arcblock/jwt": "^1.18.
|
|
37
|
+
"@arcblock/did-util": "1.18.72",
|
|
38
|
+
"@arcblock/event-hub": "1.18.72",
|
|
39
|
+
"@arcblock/jwt": "^1.18.72",
|
|
39
40
|
"@arcblock/pm2-events": "^0.0.5",
|
|
40
|
-
"@arcblock/vc": "1.18.
|
|
41
|
-
"@blocklet/constant": "1.16.6-beta-
|
|
42
|
-
"@blocklet/meta": "1.16.6-beta-
|
|
43
|
-
"@blocklet/sdk": "1.16.6-beta-
|
|
44
|
-
"@did-space/client": "^0.2.
|
|
41
|
+
"@arcblock/vc": "1.18.72",
|
|
42
|
+
"@blocklet/constant": "1.16.6-beta-4ea1eb90",
|
|
43
|
+
"@blocklet/meta": "1.16.6-beta-4ea1eb90",
|
|
44
|
+
"@blocklet/sdk": "1.16.6-beta-4ea1eb90",
|
|
45
|
+
"@did-space/client": "^0.2.80",
|
|
45
46
|
"@fidm/x509": "^1.2.1",
|
|
46
|
-
"@ocap/client": "1.18.
|
|
47
|
-
"@ocap/mcrypto": "1.18.
|
|
48
|
-
"@ocap/util": "1.18.
|
|
49
|
-
"@ocap/wallet": "1.18.
|
|
47
|
+
"@ocap/client": "1.18.72",
|
|
48
|
+
"@ocap/mcrypto": "1.18.72",
|
|
49
|
+
"@ocap/util": "1.18.72",
|
|
50
|
+
"@ocap/wallet": "1.18.72",
|
|
50
51
|
"@slack/webhook": "^5.0.4",
|
|
51
52
|
"archiver": "^5.3.1",
|
|
52
53
|
"axios": "^0.27.2",
|
|
53
54
|
"axon": "^2.0.3",
|
|
54
55
|
"chalk": "^4.1.2",
|
|
56
|
+
"cross-spawn": "^7.0.3",
|
|
55
57
|
"dayjs": "^1.11.7",
|
|
56
58
|
"deep-diff": "^1.0.2",
|
|
57
59
|
"detect-port": "^1.5.1",
|
|
@@ -67,8 +69,10 @@
|
|
|
67
69
|
"is-url": "^1.2.4",
|
|
68
70
|
"joi": "17.7.0",
|
|
69
71
|
"js-yaml": "^4.1.0",
|
|
72
|
+
"kill-port": "^2.0.1",
|
|
70
73
|
"lodash": "^4.17.21",
|
|
71
74
|
"lru-cache": "^6.0.0",
|
|
75
|
+
"moment-timezone": "^0.5.37",
|
|
72
76
|
"node-stream-zip": "^1.15.0",
|
|
73
77
|
"p-limit": "^3.1.0",
|
|
74
78
|
"p-retry": "4.6.1",
|
|
@@ -78,7 +82,7 @@
|
|
|
78
82
|
"ssri": "^8.0.1",
|
|
79
83
|
"stream-throttle": "^0.1.3",
|
|
80
84
|
"stream-to-promise": "^3.0.0",
|
|
81
|
-
"systeminformation": "^5.12
|
|
85
|
+
"systeminformation": "^5.17.12",
|
|
82
86
|
"tar": "^6.1.11",
|
|
83
87
|
"transliteration": "^2.3.5",
|
|
84
88
|
"ua-parser-js": "^1.0.2",
|
|
@@ -93,5 +97,5 @@
|
|
|
93
97
|
"express": "^4.18.2",
|
|
94
98
|
"jest": "^27.5.1"
|
|
95
99
|
},
|
|
96
|
-
"gitHead": "
|
|
100
|
+
"gitHead": "0f0f63b552a88343a3f8e0d434b47c3bca6a75b9"
|
|
97
101
|
}
|