From 7b03706821c604f59fe8263286203d57d634c421 Mon Sep 17 00:00:00 2001 From: Menci Date: Wed, 27 Mar 2019 02:35:15 +0800 Subject: [PATCH 1/2] Add restart service option for admins --- app.js | 57 +++++++++++++++++++++++++++++------------ modules/admin.js | 32 +++++++++++++++++++++++ package.json | 1 + views/admin_restart.ejs | 46 +++++++++++++++++++++++++++++++++ views/header.ejs | 1 + 5 files changed, 120 insertions(+), 17 deletions(-) create mode 100644 views/admin_restart.ejs diff --git a/app.js b/app.js index 7ac8114..aeaa9fe 100644 --- a/app.js +++ b/app.js @@ -2,6 +2,7 @@ let fs = require('fs'), path = require('path'), util = require('util'); const serializejs = require('serialize-javascript'); +const UUID = require('uuid'); const commandLineArgs = require('command-line-args'); const optionDefinitions = [ @@ -18,6 +19,7 @@ global.syzoj = { models: [], modules: [], db: null, + serviceID: UUID(), log(obj) { if (obj instanceof ErrorMessage) return; console.log(obj); @@ -38,15 +40,6 @@ global.syzoj = { let winstonLib = require('./libs/winston'); winstonLib.configureWinston(!syzoj.production); - app.server = require('http').createServer(app); - - if (!module.parent) { - // Loaded by `require()`, not node CLI. - app.server.listen(parseInt(syzoj.config.port), syzoj.config.hostname, () => { - this.log(`SYZOJ is listening on ${syzoj.config.hostname}:${parseInt(syzoj.config.port)}...`); - }); - } - // Set assets dir app.use(Express.static(__dirname + '/static', { maxAge: syzoj.production ? '1y' : 0 })); @@ -79,9 +72,7 @@ global.syzoj = { })()); await this.connectDatabase(); - if (!module.parent) { - await this.lib('judger').connect(); - } + this.loadModules(); // redis and redisCache is for syzoj-renderer const redis = require('redis'); @@ -91,7 +82,40 @@ global.syzoj = { set: util.promisify(this.redis.set).bind(this.redis) }; - this.loadModules(); + if (!module.parent) { + // Loaded by node CLI, not by `require()`. + + if (process.send) { + // if it's started by child_process.fork(), it must be requested to restart + // wait until parent process quited. + await new Promise((resolve, reject) => { + process.on('message', message => { + if (message === 'quited') resolve(); + }); + process.send('quit'); + }); + } + + await this.lib('judger').connect(); + + app.server = require('http').createServer(app); + app.server.listen(parseInt(syzoj.config.port), syzoj.config.hostname, () => { + this.log(`SYZOJ is listening on ${syzoj.config.hostname}:${parseInt(syzoj.config.port)}...`); + }); + } + }, + restart() { + console.log('Will now fork a new process.'); + const child = require('child_process').fork(__filename, ['-c', options.config]); + child.on('message', (message) => { + if (message !== 'quit') return; + + console.log('Child process requested "quit".') + child.send('quited', err => { + if (err) console.error('Error sending "quited" to child process:', err); + process.exit(); + }); + }); }, async connectDatabase() { let Sequelize = require('sequelize'); @@ -143,7 +167,7 @@ global.syzoj = { global.Promise = Sequelize.Promise; this.db.countQuery = async (sql, options) => (await this.db.query(`SELECT COUNT(*) FROM (${sql}) AS \`__tmp_table\``, options))[0][0]['COUNT(*)']; - this.loadModels(); + await this.loadModels(); }, loadModules() { fs.readdir('./modules/', (err, files) => { @@ -155,7 +179,7 @@ global.syzoj = { .forEach((file) => this.modules.push(require(`./modules/${file}`))); }); }, - loadModels() { + async loadModels() { fs.readdir('./models/', (err, files) => { if (err) { this.log(err); @@ -163,9 +187,8 @@ global.syzoj = { } files.filter((file) => file.endsWith('.js')) .forEach((file) => require(`./models/${file}`)); - - this.db.sync(); }); + await this.db.sync(); }, lib(name) { return require(`./libs/${name}`); diff --git a/modules/admin.js b/modules/admin.js index 0505b7b..23372f4 100644 --- a/modules/admin.js +++ b/modules/admin.js @@ -442,3 +442,35 @@ app.post('/admin/raw', async (req, res) => { }) } }); + +app.post('/admin/restart', async (req, res) => { + try { + if (!res.locals.user || !res.locals.user.is_admin) throw new ErrorMessage('您没有权限进行此操作。'); + + syzoj.restart(); + + res.render('admin_restart', { + data: JSON.stringify(syzoj.config, null, 2) + }); + } catch (e) { + syzoj.log(e); + res.render('error', { + err: e + }) + } +}); + +app.get('/admin/serviceID', async (req, res) => { + try { + if (!res.locals.user || !res.locals.user.is_admin) throw new ErrorMessage('您没有权限进行此操作。'); + + res.send({ + serviceID: syzoj.serviceID + }); + } catch (e) { + syzoj.log(e); + res.render('error', { + err: e + }) + } +}); diff --git a/package.json b/package.json index d4d77c3..d79fdad 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "syzoj-renderer": "^1.0.5", "tempfile": "^2.0.0", "tmp-promise": "^1.0.3", + "uuid": "^3.3.2", "waliyun": "^3.1.1", "winston": "^3.2.1", "xss": "^1.0.6" diff --git a/views/admin_restart.ejs b/views/admin_restart.ejs new file mode 100644 index 0000000..e07b767 --- /dev/null +++ b/views/admin_restart.ejs @@ -0,0 +1,46 @@ +<% this.title = '重启服务' %> +<% include header %> + +
+ +
+
+ 服务重启中 +
+

已等待 0 秒。

+
+
+ + + +<% include footer %> diff --git a/views/header.ejs b/views/header.ejs index 9d786c1..f91a8de 100644 --- a/views/header.ejs +++ b/views/header.ejs @@ -72,6 +72,7 @@ 修改资料 <% if (user.is_admin) { %> 后台管理 + 重启服务 <% } %> 注销 From 4b649698ec15aea2b428355aa13ac74754b8bcd7 Mon Sep 17 00:00:00 2001 From: Menci Date: Wed, 27 Mar 2019 02:39:53 +0800 Subject: [PATCH 2/2] Update README --- README.en.md | 2 ++ README.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/README.en.md b/README.en.md index 886a02f..8ddde47 100644 --- a/README.en.md +++ b/README.en.md @@ -48,3 +48,5 @@ ALTER TABLE `user` ADD `prefer_formatted_code` TINYINT(1) NOT NULL DEFAULT 1 AFT To make code formatting work, `clang-format` needs to be installed. [migrates/format-old-codes.js](migrates/format-old-codes.js) may help formating old submissions' codes. Who upgraded from a commit BEFORE [c192e8001ac81cab132ae033b39f09a094587633](https://github.com/syzoj/syzoj/commit/c192e8001ac81cab132ae033b39f09a094587633) (Mar 23, 2019) **MUST** install `redis-server` and [pygments](http://pygments.org/) on the web server. Markdown contents may be broken by switching to new renderer, [migrates/html-table-merge-cell-to-md.js](migrates/html-table-merge-cell-to-md.js) may help the migration。 + +Who upgraded from a commit BEFORE [7b03706821c604f59fe8263286203d57d634c421](https://github.com/syzoj/syzoj/commit/c192e8001ac81cab132ae033b39f09a094587633) (Mar 27, 2019) **MUST** add `RemainAfterExit=yes` to the systemd config file `syzoj-web.service`'s `[Service]` section to make sure that restart service can work properly. diff --git a/README.md b/README.md index de290c4..eed47d8 100644 --- a/README.md +++ b/README.md @@ -48,3 +48,5 @@ ALTER TABLE `user` ADD `prefer_formatted_code` TINYINT(1) NOT NULL DEFAULT 1 AFT 为使代码格式化功能正常工作,`clang-format` 需要被安装。[migrates/format-old-codes.js](migrates/format-old-codes.js) 可能对格式化旧提交记录的代码有帮助。 从该 commit [c192e8001ac81cab132ae033b39f09a094587633](https://github.com/syzoj/syzoj/commit/c192e8001ac81cab132ae033b39f09a094587633)(2019 年 3 月 23 日)前更新的用户**必须**在网站服务器上安装 `redis-server` 与 [pygments](http://pygments.org/)。旧的 Markdown 内容可能因切换到新渲染器被破坏,[migrates/html-table-merge-cell-to-md.js](migrates/html-table-merge-cell-to-md.js) 可能对迁移有所帮助。 + +从该 commit [7b03706821c604f59fe8263286203d57d634c421](https://github.com/syzoj/syzoj/commit/c192e8001ac81cab132ae033b39f09a094587633)(2019 年 3 月 27 日)前更新的用户**必须**在其 systemd 配置文件 `syzoj-web.service` 中的 `[Service]` 中加入一行 `RemainAfterExit=yes`,以使得重启服务功能正常工作。