hydro cli

This commit is contained in:
undefined committed 2020-07-23 18:08:45 +08:00
1 parent 9e96c471af
commit 81868cafbb
11 files changed
+174 -91

No files matched your search

+19 -10
View File
@@ -2,24 +2,32 @@
const os = require('os'); const os = require('os');
const path = require('path'); const path = require('path');
const cluster = require('cluster');
const fs = require('fs-extra'); const fs = require('fs-extra');
const { argv } = require('yargs'); const { argv } = require('yargs');
const hydro = require('../dist/loader'); const hydro = require('../dist/loader');
fs.ensureDirSync(path.resolve(os.homedir(), '.hydro')); if (!cluster.isMaster) {
const addonPath = path.resolve(os.homedir(), '.hydro', 'addon.json'); // Forked by hydro
if (!fs.existsSync(addonPath)) fs.writeFileSync(addonPath, '[]'); hydro.load().catch((e) => {
let addons = JSON.parse(fs.readFileSync(addonPath).toString()); console.error(e);
process.exit(1);
});
} else {
fs.ensureDirSync(path.resolve(os.homedir(), '.hydro'));
const addonPath = path.resolve(os.homedir(), '.hydro', 'addon.json');
if (!fs.existsSync(addonPath)) fs.writeFileSync(addonPath, '[]');
let addons = JSON.parse(fs.readFileSync(addonPath).toString());
try { try {
const ui = argv.ui || '@hydrooj/ui-default'; const ui = argv.ui || '@hydrooj/ui-default';
require.resolve(ui); require.resolve(ui);
addons.push(ui); addons.push(ui);
} catch (e) { } catch (e) {
console.error('Please also install @hydrooj/ui-default'); console.error('Please also install @hydrooj/ui-default');
} }
if (argv._[0]) { if (argv._[0] && argv._[0] !== 'cli') {
const operation = argv._[0]; const operation = argv._[0];
const arg1 = argv._[1]; const arg1 = argv._[1];
const arg2 = argv._[2]; const arg2 = argv._[2];
@@ -37,11 +45,12 @@ if (argv._[0]) {
console.log('Current Addons: ', addons); console.log('Current Addons: ', addons);
fs.writeFileSync(addonPath, JSON.stringify(addons, null, 2)); fs.writeFileSync(addonPath, JSON.stringify(addons, null, 2));
} }
} else { } else {
addons = Array.from(new Set(addons)); addons = Array.from(new Set(addons));
for (const addon of addons) hydro.addon(addon); for (const addon of addons) hydro.addon(addon);
hydro.load().catch((e) => { (argv._[0] === 'cli' ? hydro.loadCli : hydro.load)().catch((e) => {
console.error(e); console.error(e);
process.exit(1); process.exit(1);
}); });
}
} }
+88
View File
@@ -0,0 +1,88 @@
/* eslint-disable no-eval */
/* eslint-disable no-await-in-loop */
/* eslint-disable import/no-dynamic-require */
import { argv } from 'yargs';
import {
lib, service, model, script,
builtinLib, builtinScript, builtinModel,
} from './common';
const COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
const ARR = /=>.*$/mg;
function parseParameters(fn: Function) {
const code = fn.toString()
.replace(COMMENTS, '')
.replace(ARR, '');
const result = code.slice(code.indexOf('(') + 1, code.indexOf(')'))
.match(/([^,]+)/g).map((i) => i.trim());
return result === null ? [] : result;
}
async function cli() {
const [, modelName, func, ...args] = argv._;
if (!global.Hydro.model[modelName]) {
return console.error(`Model ${modelName} doesn't exist.`);
}
if (!global.Hydro.model[modelName][func]) {
return console.error(`Function ${func} doesn't exist in model ${modelName}.`);
}
if (typeof global.Hydro.model[modelName][func] !== 'function') {
return console.error(`${func} in model ${modelName} is not a function.`);
}
const parameterMin = global.Hydro.model[modelName][func].length;
const parameters = parseParameters(global.Hydro.model[modelName][func]);
const parameterMax = parameters.length;
if (args.length > parameterMax) {
console.error(`Too many arguments. Max ${parameterMax}`);
return console.error(parameters.join(', '));
}
if (args.length < parameterMin) {
console.error(`Too few arguments. Min ${parameterMin}`);
return console.error(parameters.join(', '));
}
let result = global.Hydro.model[modelName][func](...args);
if (result instanceof Promise) result = await result;
return console.log(result);
}
export async function load(call, args) {
let pending = args;
const fail = [];
const active = [];
require('../lib/i18n');
require('../utils');
require('../error');
require('../options');
const bus = require('../service/bus');
await new Promise((resolve) => {
const h = () => {
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');
await service(pending, fail);
for (const i of builtinModel) require(`../model/${i}`);
await model(pending, fail);
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);
for (const postInit of global.Hydro.postInit) {
await postInit();
}
pending = [];
await cli();
return { active, fail };
}
+1 -2
View File
@@ -21,12 +21,11 @@ export async function load(call) {
bus.subscribe(['system_database_connected'], h); bus.subscribe(['system_database_connected'], h);
require('../service/db'); require('../service/db');
}); });
require('../service/monitor');
for (const i of builtinModel) require(`../model/${i}`); for (const i of builtinModel) require(`../model/${i}`);
const modelSystem = require('../model/system'); const modelSystem = require('../model/system');
const dbVer = await modelSystem.get('db.ver'); const dbVer = await modelSystem.get('db.ver');
if (dbVer !== 1) { if (dbVer !== 1) {
const ins = require('../script/install'); const ins = require('../script/upgrade0_1');
await ins.run({ username: 'Root', password: 'rootroot' }); await ins.run({ username: 'Root', password: 'rootroot' });
} }
for (const i in global.Hydro.service) { for (const i in global.Hydro.service) {
+24 -18
View File
@@ -2,12 +2,14 @@ import os from 'os';
import path from 'path'; import path from 'path';
import http from 'http'; import http from 'http';
import fs from 'fs-extra'; import fs from 'fs-extra';
import Koa from 'koa'; import Koa, { Context } from 'koa';
import Body from 'koa-body'; import Body from 'koa-body';
import Router from 'koa-router';
import cache from 'koa-static-cache'; import cache from 'koa-static-cache';
import nunjucks from 'nunjucks'; import nunjucks from 'nunjucks';
import mongodb from 'mongodb'; import mongodb from 'mongodb';
import { argv } from 'yargs';
const listenPort = argv.port || 8888;
class Loader { class Loader {
getSource(name: string) { // eslint-disable-line class-methods-use-this getSource(name: string) { // eslint-disable-line class-methods-use-this
@@ -83,7 +85,7 @@ class Loader {
const env = new nunjucks.Environment(new Loader(), { autoescape: true, trimBlocks: true }); const env = new nunjucks.Environment(new Loader(), { autoescape: true, trimBlocks: true });
function render(name) { function render(name: string) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
env.render(name, { env.render(name, {
_: (str: string) => str, _: (str: string) => str,
@@ -94,21 +96,12 @@ function render(name) {
}); });
} }
export function load() { async function get(ctx: Context) {
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.body = await render('setup.html');
ctx.response.type = 'text/html'; ctx.response.type = 'text/html';
}); }
const listenPort = global.argv.port || 8888;
router.post('/', async (ctx) => { async function post(ctx: Context) {
const { const {
host, port, name, username, password, host, port, name, username, password,
} = ctx.request.body; } = ctx.request.body;
@@ -138,7 +131,7 @@ export function load() {
), ),
db.collection('system').updateOne( db.collection('system').updateOne(
{ _id: 'server.port' }, { _id: 'server.port' },
{ $set: { value: parseInt(listenPort, 10) } }, { $set: { value: parseInt(listenPort as string, 10) } },
{ upsert: true }, { upsert: true },
), ),
]); ]);
@@ -156,8 +149,21 @@ export function load() {
} catch (e) { } catch (e) {
ctx.body = `Error connecting to database: ${e.message}\n${e.stack}`; ctx.body = `Error connecting to database: ${e.message}\n${e.stack}`;
} }
}
export function load() {
const app = new Koa();
const server = http.createServer(app.callback());
app.keys = ['Hydro'];
app
.use(cache(path.join(os.tmpdir(), 'hydro', 'public'), {
maxAge: 365 * 24 * 60 * 60,
}))
.use(Body())
.use((ctx) => {
if (ctx.request.method.toLowerCase() === 'post') return post(ctx);
return get(ctx);
}); });
app.use(router.routes()).use(router.allowedMethods());
server.listen(listenPort); server.listen(listenPort);
console.log('Server listening at:', listenPort); console.log('Server listening at:', listenPort);
} }
-1
View File
@@ -300,7 +300,6 @@ declare global {
}, },
onDestory: Function[], onDestory: Function[],
addons: string[], addons: string[],
argv: any,
} }
} }
} }
+25 -20
View File
@@ -2,7 +2,6 @@
/* eslint-disable import/no-dynamic-require */ /* eslint-disable import/no-dynamic-require */
/* eslint-disable no-await-in-loop */ /* eslint-disable no-await-in-loop */
/* eslint-disable no-eval */ /* eslint-disable no-eval */
import './interface'; import './interface';
import os from 'os'; import os from 'os';
import path from 'path'; import path from 'path';
@@ -11,7 +10,6 @@ import fs from 'fs-extra';
import { argv } from 'yargs'; import { argv } from 'yargs';
import AdmZip from 'adm-zip'; import AdmZip from 'adm-zip';
global.argv = argv;
global.Hydro = { global.Hydro = {
stat: { reqCount: 0 }, stat: { reqCount: 0 },
handler: {}, handler: {},
@@ -35,15 +33,16 @@ global.Hydro = {
global.onDestory = []; global.onDestory = [];
global.addons = []; global.addons = [];
if (global.argv.debug) { if (argv.debug) {
console.log(process.argv); console.log(process.argv);
process.env.debug = 'enable'; console.log(argv);
} }
async function terminate() { async function terminate() {
for (const task of global.onDestory) { try {
// eslint-disable-next-line no-await-in-loop for (const task of global.onDestory) await task();
await task(); } catch (e) {
process.exit(1);
} }
process.exit(0); process.exit(0);
} }
@@ -80,9 +79,8 @@ async function stopWorker() {
} }
async function startWorker(cnt: number) { async function startWorker(cnt: number) {
const sargv = [`--args=${Buffer.from(JSON.stringify(global.addons)).toString('base64')}`]; await fork(['--firstWorker']);
await fork(['--firstWorker', ...sargv]); for (let i = 1; i < cnt; i++) await fork();
for (let i = 1; i < cnt; i++) await fork(sargv);
} }
async function executeCommand(input: string) { async function executeCommand(input: string) {
@@ -163,6 +161,9 @@ export function addon(addonPath: string) {
} else throw new Error(`Addon not found: ${addonPath}`); } else throw new Error(`Addon not found: ${addonPath}`);
} }
process.on('unhandledRejection', (e) => console.error(e));
process.on('SIGINT', terminate);
export async function load() { export async function load() {
while (!global.addons) { while (!global.addons) {
await new Promise((resolve) => { await new Promise((resolve) => {
@@ -171,11 +172,9 @@ export async function load() {
} }
addon(path.resolve(__dirname, '..')); addon(path.resolve(__dirname, '..'));
Error.stackTraceLimit = 50; Error.stackTraceLimit = 50;
process.on('unhandledRejection', (e) => console.error(e));
process.on('SIGINT', terminate);
process.on('message', messageHandler); process.on('message', messageHandler);
cluster.on('message', messageHandler); cluster.on('message', messageHandler);
if (cluster.isMaster || global.argv.startAsMaster) { if (cluster.isMaster || argv.startAsMaster) {
console.log(`Master ${process.pid} Starting`); console.log(`Master ${process.pid} Starting`);
process.stdin.setEncoding('utf8'); process.stdin.setEncoding('utf8');
process.stdin.on('data', (buf) => { process.stdin.on('data', (buf) => {
@@ -205,12 +204,12 @@ export async function load() {
worker.send({ event: 'setAddon', addons: global.addons }); worker.send({ event: 'setAddon', addons: global.addons });
}); });
await startWorker(cnt); await startWorker(cnt);
} else if (global.argv.entry) { } else if (argv.entry) {
console.log(`Worker ${process.pid} Starting as ${global.argv.entry}`); console.log(`Worker ${process.pid} Starting as ${argv.entry}`);
await entry({ entry: global.argv.entry }); await entry({ entry: argv.entry });
console.log(`Worker ${process.pid} Started as ${global.argv.entry}`); console.log(`Worker ${process.pid} Started as ${argv.entry}`);
} else { } else {
if (global.argv.firstWorker) cluster.isFirstWorker = true; if (argv.firstWorker) cluster.isFirstWorker = true;
else cluster.isFirstWorker = false; else cluster.isFirstWorker = false;
console.log(`Worker ${process.pid} Starting`); console.log(`Worker ${process.pid} Starting`);
await entry({ entry: 'worker' }); await entry({ entry: 'worker' });
@@ -219,8 +218,14 @@ export async function load() {
if (global.gc) global.gc(); if (global.gc) global.gc();
} }
if (global.argv.pandora || !module.parent) { export async function loadCli() {
load().catch((e) => { await entry({ entry: 'cli' });
return terminate();
}
if (argv.pandora || !module.parent) {
const func = argv._[0] === 'cli' ? load : loadCli;
func().catch((e) => {
console.error(e); console.error(e);
process.exit(1); process.exit(1);
}); });
+1 -1
View File
@@ -1,6 +1,6 @@
import { Dictionary } from 'lodash'; import { Dictionary } from 'lodash';
import { BUILTIN_ROLES, PRIV } from './builtin'; import { BUILTIN_ROLES, PRIV } from './builtin';
import { Udoc, DomainDoc } from '../interface'; import { DomainDoc } from '../interface';
import * as db from '../service/db'; import * as db from '../service/db';
const coll = db.collection('domain'); const coll = db.collection('domain');
-35
View File
@@ -1,35 +0,0 @@
import { PRIV } from '../model/builtin';
import * as user from '../model/user';
import * as domain from '../model/domain';
import pwhash from '../lib/hash.hydro';
export const description = 'Install';
export async function run({ username = '', password = '' } = {}) {
const ddoc = await domain.get('system');
if (!ddoc) await domain.add('system', 0, 'Hydro', 'Hydro System');
if (username && password) {
const udoc = await user.getById('system', -1);
if (!udoc) {
await user.create('root@hydro.local', username, password, -1, '127.0.0.1', PRIV.PRIV_ALL);
} else {
const salt = String.random();
await user.setById(-1, {
uname: username,
unameLower: username.trim().toLowerCase(),
salt,
hash: pwhash(password, salt),
hashType: 'hydro',
});
}
}
}
export const validate = {
$or: [
{ username: 'string', password: 'string' },
{ username: 'undefined', password: 'undefined' },
],
};
global.Hydro.script.install = { run, description, validate };
+12
View File
@@ -0,0 +1,12 @@
import * as domain from '../model/domain';
export const description = 'Upgrade database from 0 to 1';
export async function run() {
const ddoc = await domain.get('system');
if (!ddoc) await domain.add('system', 0, 'Hydro', 'Hydro System');
}
export const validate = {};
global.Hydro.script.setSuperadmin = { run, description, validate };
+3 -3
View File
@@ -2,7 +2,7 @@
import assert from 'assert'; import assert from 'assert';
import path from 'path'; import path from 'path';
import os from 'os'; import os from 'os';
import http, { Server } from 'http'; import http from 'http';
import moment from 'moment-timezone'; import moment from 'moment-timezone';
import { isSafeInteger, Dictionary } from 'lodash'; import { isSafeInteger, Dictionary } from 'lodash';
import { ObjectID } from 'mongodb'; import { ObjectID } from 'mongodb';
@@ -776,8 +776,8 @@ export async function start() {
} }
app.use(router.routes()).use(router.allowedMethods()); app.use(router.routes()).use(router.allowedMethods());
Route('notfound_handler', '*', Handler); Route('notfound_handler', '*', Handler);
server.listen(global.argv.port || port); server.listen(argv.port || port);
console.log('Server listening at: %s', global.argv.port || port); console.log('Server listening at: %s', argv.port || port);
} }
global.Hydro.service.server = { global.Hydro.service.server = {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "hydrooj", "name": "hydrooj",
"version": "2.9.10", "version": "2.9.11",
"main": "dist/loader.js", "main": "dist/loader.js",
"bin": "bin/hydrooj.js", "bin": "bin/hydrooj.js",
"repository": "https://github.com/hydro-dev/Hydro.git", "repository": "https://github.com/hydro-dev/Hydro.git",