@abtnode/core 1.16.6-beta-4562aa60 → 1.16.6-beta-eaa4d39d

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.
@@ -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 doRpc = require('./rpc');
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 doRpc({ command: 'install', version: to });
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 doRpc({ command: 'verify', version: to });
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 doRpc({ command: 'restart', dataDir: process.env.ABT_NODE_DATA_DIR });
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
- 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 };
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 sock = axon.socket('req');
5
- sock.connect(Number(process.env.ABT_NODE_UPDATER_PORT || 40405), '127.0.0.1');
9
+ const host = '127.0.0.1';
10
+ const port = Number(process.env.ABT_NODE_UPDATER_PORT || 40405);
6
11
 
7
- const doRpc = async (message) =>
12
+ const doRpcCall = async (message) =>
8
13
  new Promise((resolve) => {
9
- logger.info('send rpc request', message);
10
- sock.send(JSON.stringify(message), (result) => {
11
- logger.info('receive rpc response', result);
12
- resolve(result);
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
- module.exports = doRpc;
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
+ // });
@@ -0,0 +1,44 @@
1
+ const JOI = require('joi');
2
+ const { didExtension } = require('@blocklet/meta/lib/extension');
3
+
4
+ const Joi = JOI.extend(didExtension);
5
+
6
+ // TODO: @zhanghan 这里目前仅包含必填的字段,后续需要增加其他字段
7
+ const passportSchema = Joi.object({
8
+ id: Joi.string().required(),
9
+ role: Joi.string().required(),
10
+ name: Joi.string().required(),
11
+ })
12
+ .unknown()
13
+ .empty(null);
14
+
15
+ // TODO: @zhanghan 这里目前仅包含必填的字段,后续需要增加其他字段
16
+ const connectedAccountSchema = Joi.object({
17
+ provider: Joi.string().required(),
18
+ did: Joi.DID().trim().required(),
19
+ pk: Joi.string().optional(),
20
+ id: Joi.string().optional(),
21
+ firstLoginAt: Joi.string().optional(),
22
+ lastLoginAt: Joi.string().optional(),
23
+ })
24
+ .unknown()
25
+ .empty(null);
26
+
27
+ const loginSchema = Joi.object({
28
+ did: Joi.DID().trim().required(),
29
+ pk: Joi.string().required(),
30
+ fullName: Joi.string().empty(''),
31
+ avatar: Joi.string().empty(''),
32
+ email: Joi.string().empty(''),
33
+ role: Joi.string().empty(''),
34
+ locale: Joi.string().empty(''),
35
+ extra: Joi.object(),
36
+ remark: Joi.string().empty(''),
37
+ lastLoginIp: Joi.string().empty(''),
38
+ passport: passportSchema.optional(),
39
+ connectedAccount: Joi.alternatives()
40
+ .try(connectedAccountSchema.required(), Joi.array().items(connectedAccountSchema).min(1).sparse(true))
41
+ .required(),
42
+ }).unknown();
43
+
44
+ exports.loginSchema = loginSchema;
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.16.6-beta-4562aa60",
6
+ "version": "1.16.6-beta-eaa4d39d",
7
7
  "description": "",
8
8
  "main": "lib/index.js",
9
9
  "files": [
@@ -19,39 +19,42 @@
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-4562aa60",
23
- "@abtnode/certificate-manager": "1.16.6-beta-4562aa60",
24
- "@abtnode/constant": "1.16.6-beta-4562aa60",
25
- "@abtnode/cron": "1.16.6-beta-4562aa60",
26
- "@abtnode/db": "1.16.6-beta-4562aa60",
27
- "@abtnode/logger": "1.16.6-beta-4562aa60",
28
- "@abtnode/queue": "1.16.6-beta-4562aa60",
29
- "@abtnode/rbac": "1.16.6-beta-4562aa60",
30
- "@abtnode/router-provider": "1.16.6-beta-4562aa60",
31
- "@abtnode/static-server": "1.16.6-beta-4562aa60",
32
- "@abtnode/timemachine": "1.16.6-beta-4562aa60",
33
- "@abtnode/util": "1.16.6-beta-4562aa60",
34
- "@arcblock/did": "1.18.72",
22
+ "@abtnode/auth": "1.16.6-beta-eaa4d39d",
23
+ "@abtnode/certificate-manager": "1.16.6-beta-eaa4d39d",
24
+ "@abtnode/constant": "1.16.6-beta-eaa4d39d",
25
+ "@abtnode/cron": "1.16.6-beta-eaa4d39d",
26
+ "@abtnode/db": "1.16.6-beta-eaa4d39d",
27
+ "@abtnode/logger": "1.16.6-beta-eaa4d39d",
28
+ "@abtnode/queue": "1.16.6-beta-eaa4d39d",
29
+ "@abtnode/rbac": "1.16.6-beta-eaa4d39d",
30
+ "@abtnode/router-provider": "1.16.6-beta-eaa4d39d",
31
+ "@abtnode/static-server": "1.16.6-beta-eaa4d39d",
32
+ "@abtnode/timemachine": "1.16.6-beta-eaa4d39d",
33
+ "@abtnode/util": "1.16.6-beta-eaa4d39d",
34
+ "@arcblock/did": "1.18.73",
35
+ "@arcblock/did-auth": "1.18.73",
36
+ "@arcblock/did-ext": "^1.18.73",
35
37
  "@arcblock/did-motif": "^1.1.10",
36
- "@arcblock/did-util": "1.18.72",
37
- "@arcblock/event-hub": "1.18.72",
38
- "@arcblock/jwt": "^1.18.72",
38
+ "@arcblock/did-util": "1.18.73",
39
+ "@arcblock/event-hub": "1.18.73",
40
+ "@arcblock/jwt": "^1.18.73",
39
41
  "@arcblock/pm2-events": "^0.0.5",
40
- "@arcblock/vc": "1.18.72",
41
- "@blocklet/constant": "1.16.6-beta-4562aa60",
42
- "@blocklet/meta": "1.16.6-beta-4562aa60",
43
- "@blocklet/sdk": "1.16.6-beta-4562aa60",
44
- "@did-space/client": "^0.2.77",
42
+ "@arcblock/vc": "1.18.73",
43
+ "@blocklet/constant": "1.16.6-beta-eaa4d39d",
44
+ "@blocklet/meta": "1.16.6-beta-eaa4d39d",
45
+ "@blocklet/sdk": "1.16.6-beta-eaa4d39d",
46
+ "@did-space/client": "^0.2.80",
45
47
  "@fidm/x509": "^1.2.1",
46
- "@ocap/client": "1.18.72",
47
- "@ocap/mcrypto": "1.18.72",
48
- "@ocap/util": "1.18.72",
49
- "@ocap/wallet": "1.18.72",
48
+ "@ocap/client": "1.18.73",
49
+ "@ocap/mcrypto": "1.18.73",
50
+ "@ocap/util": "1.18.73",
51
+ "@ocap/wallet": "1.18.73",
50
52
  "@slack/webhook": "^5.0.4",
51
53
  "archiver": "^5.3.1",
52
54
  "axios": "^0.27.2",
53
55
  "axon": "^2.0.3",
54
56
  "chalk": "^4.1.2",
57
+ "cross-spawn": "^7.0.3",
55
58
  "dayjs": "^1.11.7",
56
59
  "deep-diff": "^1.0.2",
57
60
  "detect-port": "^1.5.1",
@@ -67,8 +70,10 @@
67
70
  "is-url": "^1.2.4",
68
71
  "joi": "17.7.0",
69
72
  "js-yaml": "^4.1.0",
73
+ "kill-port": "^2.0.1",
70
74
  "lodash": "^4.17.21",
71
75
  "lru-cache": "^6.0.0",
76
+ "moment-timezone": "^0.5.37",
72
77
  "node-stream-zip": "^1.15.0",
73
78
  "p-limit": "^3.1.0",
74
79
  "p-retry": "4.6.1",
@@ -93,5 +98,5 @@
93
98
  "express": "^4.18.2",
94
99
  "jest": "^27.5.1"
95
100
  },
96
- "gitHead": "e49856099ffc3c16eda77480a7ad93b2e9760764"
101
+ "gitHead": "0d2ab99f67109e989f8182fc70bc3ee9fa430585"
97
102
  }