multi entry

This commit is contained in:
masnn committed 2020-06-13 12:26:04 +08:00
1 parent c93597703e
commit 26f780ddee
8 files changed
+324 -290

No files matched your search

+158
View File
@@ -0,0 +1,158 @@
/* eslint-disable no-eval */
const os = require('os');
const fs = require('fs');
const builtinLib = [
'axios', 'download', 'i18n', 'mail', 'markdown',
'md5', 'misc', 'paginate', 'hash.hydro', 'rank',
'template', 'validator', 'nav', 'sysinfo',
];
const builtinModel = [
'builtin', 'document', 'domain', 'blacklist', 'opcount',
'setting', 'token', 'user', 'problem', 'record',
'contest', 'message', 'solution', 'training', 'file',
'discussion', 'system',
];
const builtinHandler = [
'home', 'problem', 'record', 'judge', 'user',
'contest', 'training', 'discussion', 'manage', 'import',
'misc', 'homework', 'domain', 'wiki',
];
const builtinScript = [
'install', 'uninstall', 'rating', 'recalcRating', 'register',
'blacklist', 'setSuperadmin',
];
async function handler(pending, fail) {
for (const i of pending) {
const p = `${os.tmpdir()}/hydro/tmp/${i}/handler.js`;
if (fs.existsSync(p) && !fail.includes(i)) {
try {
console.log(`Handler init: ${i}`);
console.time(`Handler init: ${i}`);
eval('require')(p);
console.timeEnd(`Handler init: ${i}`);
} catch (e) {
fail.push(i);
console.error(`Handler Load Fail: ${i}`);
}
}
}
}
async function locale(pending, fail) {
for (const i of pending) {
const p = `${os.tmpdir()}/hydro/tmp/${i}/locale.json`;
if (fs.existsSync(p) && !fail.includes(i)) {
try {
global.Hydro.lib.i18n(eval('require')(p));
console.log(`Locale init: ${i}`);
} catch (e) {
fail.push(i);
console.error(`Locale Load Fail: ${i}`);
}
}
}
}
async function template(pending, fail) {
for (const i of pending) {
const p = `${os.tmpdir()}/hydro/tmp/${i}/template.json`;
if (fs.existsSync(p) && !fail.includes(i)) {
try {
Object.assign(global.Hydro.template, eval('require')(p));
console.log(`Template init: ${i}`);
} catch (e) {
fail.push(i);
console.error(`Template Load Fail: ${i}`);
}
}
}
}
async function model(pending, fail) {
for (const i of pending) {
const p = `${os.tmpdir()}/hydro/tmp/${i}/model.js`;
if (fs.existsSync(p) && !fail.includes(i)) {
try {
console.log(`Model init: ${i}`);
console.time(`Model init: ${i}`);
eval('require')(p);
console.timeEnd(`Model init: ${i}`);
} catch (e) {
fail.push(i);
console.error(`Model Load Fail: ${i}`);
}
}
}
}
async function lib(pending, fail) {
for (const i of pending) {
const p = `${os.tmpdir()}/hydro/tmp/${i}/lib.js`;
if (fs.existsSync(p) && !fail.includes(i)) {
try {
console.log(`Lib init: ${i}`);
console.time(`Lib init: ${i}`);
eval('require')(p);
console.timeEnd(`Lib init: ${i}`);
} catch (e) {
fail.push(i);
console.error(`Lib Load Fail: ${i}`);
}
}
}
}
async function service(pending, fail) {
for (const i of pending) {
const p = `${os.tmpdir()}/hydro/tmp/${i}/service.js`;
if (fs.existsSync(p) && !fail.includes(i)) {
try {
console.log(`Service init: ${i}`);
console.time(`Service init: ${i}`);
eval('require')(p);
console.timeEnd(`Service init: ${i}`);
} catch (e) {
fail.push(i);
console.error(`Service Load Fail: ${i}`);
console.error(e);
}
}
}
}
async function script(pending, fail, active) {
for (const i of pending) {
const p = `${os.tmpdir()}/hydro/tmp/${i}/script.js`;
if (fs.existsSync(p) && !fail.includes(i)) {
try {
console.time(`Script init: ${i}`);
eval('require')(p);
console.timeEnd(`Script init: ${i}`);
} catch (e) {
fail.push(i);
console.error(`Script Load Fail: ${i}`);
console.error(e);
}
}
active.push(i);
}
}
module.exports = {
builtinLib,
builtinModel,
builtinHandler,
builtinScript,
handler,
lib,
locale,
model,
script,
service,
template,
};
+89
View File
@@ -0,0 +1,89 @@
/* eslint-disable no-await-in-loop */
/* eslint-disable import/no-dynamic-require */
const os = require('os');
const fs = require('fs');
const path = require('path');
const {
lib, service, model, handler, script,
builtinLib, builtinScript, builtinHandler, builtinModel,
} = require('./common');
function ensureDir(dir) {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
} else if (!fs.statSync(dir).isDirectory()) {
fs.unlinkSync(dir);
fs.mkdirSync(dir);
}
}
async function load(call) {
ensureDir(path.resolve(os.tmpdir(), 'hydro'));
ensureDir(path.resolve(os.tmpdir(), 'hydro', 'tmp'));
ensureDir(path.resolve(os.tmpdir(), 'hydro', 'public'));
// TODO better run in another process as this needs lots of memory
await call({ entry: 'unzip', newProcess: true });
let pending = await require('../lib/hpm').getInstalled();
const fail = [];
const active = [];
require('../lib/i18n');
require('../utils');
require('../error');
require('../permission');
try {
require('../options');
} catch (e) {
await call({ entry: 'install' });
require('../options');
}
const bus = require('../service/bus');
await new Promise((resolve) => {
const h = () => {
console.log('Database connected');
bus.unsubscribe(['system_database_connected'], h);
resolve();
};
bus.subscribe(['system_database_connected'], h);
require('../service/db');
});
for (const i of builtinLib) require(`../lib/${i}`);
await lib(pending, fail);
require('../service/gridfs');
require('../service/monitor');
const server = require('../service/server');
await server.prepare();
await service(pending, fail);
for (const i of builtinModel) require(`../model/${i}`);
for (const i of builtinHandler) require(`../handler/${i}`);
await model(pending, fail);
for (const m in global.Hydro.model) {
if (global.Hydro.model[m].ensureIndexes) {
await global.Hydro.model[m].ensureIndexes();
}
}
const system = require('../model/system');
const dbVer = await system.get('db.ver');
if (dbVer !== 1) {
const ins = require('../script/install');
await ins.run({ username: 'Root', password: 'rootroot' });
}
await handler(pending, fail);
for (const i in global.Hydro.handler) {
await global.Hydro.handler[i]();
}
for (const i in global.Hydro.service) {
if (global.Hydro.service[i].postInit) {
try {
await global.Hydro.service[i].postInit();
} catch (e) {
console.error(e);
}
}
}
for (const i of builtinScript) require(`../script/${i}`);
await script(pending, fail, active);
pending = [];
return { fail, active };
}
module.exports = load;
+162
View File
@@ -0,0 +1,162 @@
const fs = require('fs');
const os = require('os');
const path = require('path');
const http = require('http');
const Koa = require('koa');
const Body = require('koa-body');
const Router = require('koa-router');
const cache = require('koa-static-cache');
const nunjucks = require('nunjucks');
const mongodb = require('mongodb');
class Loader {
getSource(name) { // eslint-disable-line class-methods-use-this
return {
src: `
<!DOCTYPE html>
<html data-page="setup" data-layout="immersive" class="layout--immersive page--setup nojs">
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="chrome=1"/>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
<meta name="msapplication-TileColor" content="#579e9a">
<meta name="theme-color" content="#56758f">
<link rel="stylesheet" media="all" href="/vj4.css">
<title>{{ _('Setup') }}</title>
</head>
<body>
<div class="slideout-panel" id="panel">
<div class="main">
<div class="row"><div class="columns">
<div class="immersive--content immersive--center">
<h1>{{ _('Setup') }}</h1>
<form method="POST">
<div class="row"><div class="columns">
<label class="inverse material textbox">
{{ _('Database Host') }}
<input name="host" type="text" value="127.0.0.1" autofocus>
</label>
</div></div>
<div class="row"><div class="columns">
<label class="inverse material textbox">
{{ _('Database Port') }}
<input name="port" type="number" value="27017">
</label>
</div></div>
<div class="row"><div class="columns">
<label class="inverse material textbox">
{{ _('Database Name') }}
<input name="name" type="text" value="hydro">
</label>
</div></div>
<div class="row"><div class="columns">
<label class="inverse material textbox">
{{ _('Database Username') }}
<input name="username" type="text" placeholder="{{ _('Leave blank if none') }}">
</label>
</div></div>
<div class="row"><div class="columns">
<label class="inverse material textbox">
{{ _('Database Password') }}
<input name="password" type="password" placeholder="{{ _('Leave blank if none') }}">
</label>
</div></div>
<div class="row"><div class="columns">
<div class="text-center">
<input type="submit" value="{{ _('Confirm') }}" class="inverse expanded rounded primary button">
</div>
</div></div>
</form>
</div>
</div></div>
</div>
</div>
</body>
</html>
`,
path: name,
};
}
}
const env = new nunjucks.Environment(new Loader(), { autoescape: true, trimBlocks: true });
function render(name) {
return new Promise((resolve, reject) => {
env.render(name, {
_: (str) => str,
}, (err, res) => {
if (err) reject(err);
else resolve(res);
});
});
}
async function load() {
const app = new Koa();
const server = http.createServer(app.callback());
const router = new Router();
app.keys = ['Hydro'];
app.use(cache(path.join(os.tmpdir(), 'hydro', 'public'), {
maxAge: 365 * 24 * 60 * 60,
}));
app.use(Body());
router.get('/', async (ctx) => {
ctx.body = await render('setup.html');
ctx.response.type = 'text/html';
});
const p = new Promise((resolve) => {
router.post('/', async (ctx) => {
const {
host, port, name, username, password,
} = ctx.request.body;
let mongourl = 'mongodb://';
if (username) mongourl += `${username}:${password}@`;
mongourl += `${host}:${port}/${name}`;
try {
const Database = await mongodb.MongoClient.connect(mongourl, {
useNewUrlParser: true, useUnifiedTopology: true,
});
const db = Database.db(name);
await Promise.all([
db.collection('system').updateOne(
{ _id: 'server.host' },
{ $set: { value: ctx.request.host } },
{ upsert: true },
),
db.collection('system').updateOne(
{ _id: 'server.hostname' },
{ $set: { value: ctx.request.hostname } },
{ upsert: true },
),
db.collection('system').updateOne(
{ _id: 'server.url' },
{ $set: { value: ctx.request.href } },
{ upsert: true },
),
]);
fs.writeFileSync(path.resolve(process.cwd(), 'config.json'), JSON.stringify({
host, port, name, username, password,
}));
ctx.redirect('/');
resolve();
} catch (e) {
ctx.body = `Error connecting to database: ${e.message}\n${e.stack}`;
}
});
});
app.use(router.routes()).use(router.allowedMethods());
server.listen(8888);
console.log('Server listening at: 8888');
await p;
await new Promise((resolve, reject) => {
server.close((err) => {
if (err) reject(err);
else resolve();
});
});
return 'Done! Restarting...';
}
module.exports = load;
+114
View File
@@ -0,0 +1,114 @@
const fs = require('fs');
const os = require('os');
const path = require('path');
const zlib = require('zlib');
const yaml = require('js-yaml');
function ensureDir(dir) {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
} else if (!fs.statSync(dir).isDirectory()) {
fs.unlinkSync(dir);
fs.mkdirSync(dir);
}
}
function root(name) {
return path.resolve(process.cwd(), name);
}
const moduleRoots = [
root('.build/module'),
root('module'),
root(path.resolve(os.homedir(), '.hydro', 'module')),
root('.'),
];
let moduleRoot;
for (const i of moduleRoots) {
if (fs.existsSync(i) && fs.statSync(i).isDirectory()) {
moduleRoot = i;
break;
}
}
async function load() {
const files = fs.readdirSync(moduleRoot);
const t = ['service', 'lib', 'model', 'handler', 'script'];
ensureDir(`${os.tmpdir()}/hydro`);
ensureDir(`${os.tmpdir()}/hydro/tmp`);
for (const file of files) {
if (file.endsWith('.hydro')) {
try {
const f = fs.readFileSync(root(`${moduleRoot}/${file}`));
const s = fs.statSync(root(`${moduleRoot}/${file}`));
const m = yaml.safeLoad(zlib.gunzipSync(f));
const { id } = m;
if (m.os) {
if (!m.os.includes(os.platform().toLowerCase())) {
console.error(`Module load fail: ${id} ${file.split('.')[0]} Unsupported OS`);
// eslint-disable-next-line no-continue
continue;
}
}
ensureDir(`${os.tmpdir()}/hydro/tmp/${id}`);
if (m.locale) {
fs.writeFileSync(
`${os.tmpdir()}/hydro/tmp/${id}/locale.json`,
JSON.stringify(m.locale),
);
}
if (m.template) {
fs.writeFileSync(
`${os.tmpdir()}/hydro/tmp/${id}/template.json`,
JSON.stringify(m.template),
);
}
for (const i of t) {
if (m[i]) {
fs.writeFileSync(
`${os.tmpdir()}/hydro/tmp/${id}/${i}.js`,
m[i],
);
}
}
if (m.file) {
ensureDir(path.resolve(os.tmpdir(), 'hydro', m.id));
for (const n in m.file) {
if (m.file[n] === null) {
ensureDir(path.resolve(os.tmpdir(), 'hydro', m.id, n));
} else {
const e = path.resolve(os.tmpdir(), 'hydro', m.id, n);
fs.writeFileSync(e, Buffer.from(m.file[n], 'base64'), { mode: 755 });
}
}
}
if (m.public) {
ensureDir(path.resolve(os.tmpdir(), 'hydro', 'public'));
for (const n in m.public) {
if (m.public[n] === null) {
ensureDir(path.resolve(os.tmpdir(), 'hydro', 'public', n));
} else {
const e = path.resolve(os.tmpdir(), 'hydro', 'public', n);
fs.writeFileSync(e, Buffer.from(m.public[n], 'base64'), { mode: 755 });
}
}
}
fs.writeFileSync(
root(`${os.tmpdir()}/hydro/tmp/${id}/hydro.json`),
JSON.stringify({
size: s.size,
version: m.version,
id: m.id,
name: m.name,
description: m.description,
}),
);
} catch (e) {
if (e.code === 'Z_DATA_ERROR') {
console.error(`Module Load Fail: ${file} (File Corrupted)`);
} else console.error(`Module Load Fail: ${file} ${e}`);
}
}
}
}
module.exports = load;
+59
View File
@@ -0,0 +1,59 @@
/* eslint-disable no-await-in-loop */
/* eslint-disable import/no-dynamic-require */
const {
locale, template, lib, service, model, handler, script,
builtinLib, builtinScript, builtinHandler, builtinModel,
} = require('./common');
async function load() {
let pending = await require('../lib/hpm').getInstalled();
const fail = [];
const active = [];
require('../lib/i18n');
require('../utils');
require('../error');
require('../permission');
require('../options');
await Promise.all([locale(pending, fail), template(pending, fail)]);
const bus = require('../service/bus');
await new Promise((resolve) => {
const h = () => {
console.log('Database connected');
bus.unsubscribe(['system_database_connected'], h);
resolve();
};
bus.subscribe(['system_database_connected'], h);
require('../service/db');
});
for (const i of builtinLib) require(`../lib/${i}`);
await lib(pending, fail);
require('../service/gridfs');
const server = require('../service/server');
await server.prepare();
await service(pending, fail);
for (const i of builtinModel) require(`../model/${i}`);
for (const i of builtinHandler) require(`../handler/${i}`);
await model(pending, fail);
await handler(pending, fail);
for (const i in global.Hydro.handler) {
await global.Hydro.handler[i]();
}
const notfound = require('../handler/notfound');
await notfound();
for (const i in global.Hydro.service) {
if (global.Hydro.service[i].postInit) {
try {
await global.Hydro.service[i].postInit();
} catch (e) {
console.error(e);
}
}
}
for (const i of builtinScript) require(`../script/${i}`);
await script(pending, fail, active);
pending = [];
await server.start();
return { active, fail };
}
module.exports = load;