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
+243 -160

No files matched your search

+43 -34
View File
@@ -2,46 +2,55 @@
const os = require('os');
const path = require('path');
const cluster = require('cluster');
const fs = require('fs-extra');
const { argv } = require('yargs');
const hydro = require('../dist/loader');
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 {
const ui = argv.ui || '@hydrooj/ui-default';
require.resolve(ui);
addons.push(ui);
} catch (e) {
console.error('Please also install @hydrooj/ui-default');
}
if (argv._[0]) {
const operation = argv._[0];
const arg1 = argv._[1];
const arg2 = argv._[2];
if (operation === 'addon') {
if (arg1 === 'add') addons.push(arg2);
else if (arg1 === 'remove') {
for (let i = 0; i < addons.length; i++) {
if (addons[i] === arg2) {
addons.splice(i, 1);
break;
}
}
}
addons = Array.from(new Set(addons));
console.log('Current Addons: ', addons);
fs.writeFileSync(addonPath, JSON.stringify(addons, null, 2));
}
} else {
addons = Array.from(new Set(addons));
for (const addon of addons) hydro.addon(addon);
if (!cluster.isMaster) {
// Forked by hydro
hydro.load().catch((e) => {
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 {
const ui = argv.ui || '@hydrooj/ui-default';
require.resolve(ui);
addons.push(ui);
} catch (e) {
console.error('Please also install @hydrooj/ui-default');
}
if (argv._[0] && argv._[0] !== 'cli') {
const operation = argv._[0];
const arg1 = argv._[1];
const arg2 = argv._[2];
if (operation === 'addon') {
if (arg1 === 'add') addons.push(arg2);
else if (arg1 === 'remove') {
for (let i = 0; i < addons.length; i++) {
if (addons[i] === arg2) {
addons.splice(i, 1);
break;
}
}
}
addons = Array.from(new Set(addons));
console.log('Current Addons: ', addons);
fs.writeFileSync(addonPath, JSON.stringify(addons, null, 2));
}
} else {
addons = Array.from(new Set(addons));
for (const addon of addons) hydro.addon(addon);
(argv._[0] === 'cli' ? hydro.loadCli : hydro.load)().catch((e) => {
console.error(e);
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);
require('../service/db');
});
require('../service/monitor');
for (const i of builtinModel) require(`../model/${i}`);
const modelSystem = require('../model/system');
const dbVer = await modelSystem.get('db.ver');
if (dbVer !== 1) {
const ins = require('../script/install');
const ins = require('../script/upgrade0_1');
await ins.run({ username: 'Root', password: 'rootroot' });
}
for (const i in global.Hydro.service) {
+69 -63
View File
@@ -2,12 +2,14 @@ import os from 'os';
import path from 'path';
import http from 'http';
import fs from 'fs-extra';
import Koa from 'koa';
import Koa, { Context } from 'koa';
import Body from 'koa-body';
import Router from 'koa-router';
import cache from 'koa-static-cache';
import nunjucks from 'nunjucks';
import mongodb from 'mongodb';
import { argv } from 'yargs';
const listenPort = argv.port || 8888;
class Loader {
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 });
function render(name) {
function render(name: string) {
return new Promise((resolve, reject) => {
env.render(name, {
_: (str: string) => str,
@@ -94,70 +96,74 @@ function render(name) {
});
}
async function get(ctx: Context) {
ctx.body = await render('setup.html');
ctx.response.type = 'text/html';
}
async function post(ctx: Context) {
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 },
),
db.collection('system').updateOne(
{ _id: 'server.port' },
{ $set: { value: parseInt(listenPort as string, 10) } },
{ upsert: true },
),
]);
fs.ensureDirSync(path.resolve(os.homedir(), '.hydro'));
fs.writeFileSync(path.resolve(os.homedir(), '.hydro', 'config.json'), JSON.stringify({
host, port, name, username, password,
}));
ctx.body = `<h1>This page will reload in 3 secs.</h1>
<script>
setTimeout(function (){
window.location.href = '/';
}, 3000);
</script>`;
setTimeout(() => process.exit(0), 500);
} catch (e) {
ctx.body = `Error connecting to database: ${e.message}\n${e.stack}`;
}
}
export 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 listenPort = global.argv.port || 8888;
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 },
),
db.collection('system').updateOne(
{ _id: 'server.port' },
{ $set: { value: parseInt(listenPort, 10) } },
{ upsert: true },
),
]);
fs.ensureDirSync(path.resolve(os.homedir(), '.hydro'));
fs.writeFileSync(path.resolve(os.homedir(), '.hydro', 'config.json'), JSON.stringify({
host, port, name, username, password,
}));
ctx.body = `<h1>This page will reload in 3 secs.</h1>
<script>
setTimeout(function (){
window.location.href = '/';
}, 3000);
</script>`;
setTimeout(() => process.exit(0), 500);
} catch (e) {
ctx.body = `Error connecting to database: ${e.message}\n${e.stack}`;
}
});
app.use(router.routes()).use(router.allowedMethods());
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);
});
server.listen(listenPort);
console.log('Server listening at:', listenPort);
}
-1
View File
@@ -300,7 +300,6 @@ declare global {
},
onDestory: Function[],
addons: string[],
argv: any,
}
}
}
+25 -20
View File
@@ -2,7 +2,6 @@
/* eslint-disable import/no-dynamic-require */
/* eslint-disable no-await-in-loop */
/* eslint-disable no-eval */
import './interface';
import os from 'os';
import path from 'path';
@@ -11,7 +10,6 @@ import fs from 'fs-extra';
import { argv } from 'yargs';
import AdmZip from 'adm-zip';
global.argv = argv;
global.Hydro = {
stat: { reqCount: 0 },
handler: {},
@@ -35,15 +33,16 @@ global.Hydro = {
global.onDestory = [];
global.addons = [];
if (global.argv.debug) {
if (argv.debug) {
console.log(process.argv);
process.env.debug = 'enable';
console.log(argv);
}
async function terminate() {
for (const task of global.onDestory) {
// eslint-disable-next-line no-await-in-loop
await task();
try {
for (const task of global.onDestory) await task();
} catch (e) {
process.exit(1);
}
process.exit(0);
}
@@ -80,9 +79,8 @@ async function stopWorker() {
}
async function startWorker(cnt: number) {
const sargv = [`--args=${Buffer.from(JSON.stringify(global.addons)).toString('base64')}`];
await fork(['--firstWorker', ...sargv]);
for (let i = 1; i < cnt; i++) await fork(sargv);
await fork(['--firstWorker']);
for (let i = 1; i < cnt; i++) await fork();
}
async function executeCommand(input: string) {
@@ -163,6 +161,9 @@ export function addon(addonPath: string) {
} else throw new Error(`Addon not found: ${addonPath}`);
}
process.on('unhandledRejection', (e) => console.error(e));
process.on('SIGINT', terminate);
export async function load() {
while (!global.addons) {
await new Promise((resolve) => {
@@ -171,11 +172,9 @@ export async function load() {
}
addon(path.resolve(__dirname, '..'));
Error.stackTraceLimit = 50;
process.on('unhandledRejection', (e) => console.error(e));
process.on('SIGINT', terminate);
process.on('message', messageHandler);
cluster.on('message', messageHandler);
if (cluster.isMaster || global.argv.startAsMaster) {
if (cluster.isMaster || argv.startAsMaster) {
console.log(`Master ${process.pid} Starting`);
process.stdin.setEncoding('utf8');
process.stdin.on('data', (buf) => {
@@ -205,12 +204,12 @@ export async function load() {
worker.send({ event: 'setAddon', addons: global.addons });
});
await startWorker(cnt);
} else if (global.argv.entry) {
console.log(`Worker ${process.pid} Starting as ${global.argv.entry}`);
await entry({ entry: global.argv.entry });
console.log(`Worker ${process.pid} Started as ${global.argv.entry}`);
} else if (argv.entry) {
console.log(`Worker ${process.pid} Starting as ${argv.entry}`);
await entry({ entry: argv.entry });
console.log(`Worker ${process.pid} Started as ${argv.entry}`);
} else {
if (global.argv.firstWorker) cluster.isFirstWorker = true;
if (argv.firstWorker) cluster.isFirstWorker = true;
else cluster.isFirstWorker = false;
console.log(`Worker ${process.pid} Starting`);
await entry({ entry: 'worker' });
@@ -219,8 +218,14 @@ export async function load() {
if (global.gc) global.gc();
}
if (global.argv.pandora || !module.parent) {
load().catch((e) => {
export async function loadCli() {
await entry({ entry: 'cli' });
return terminate();
}
if (argv.pandora || !module.parent) {
const func = argv._[0] === 'cli' ? load : loadCli;
func().catch((e) => {
console.error(e);
process.exit(1);
});
+1 -1
View File
@@ -1,6 +1,6 @@
import { Dictionary } from 'lodash';
import { BUILTIN_ROLES, PRIV } from './builtin';
import { Udoc, DomainDoc } from '../interface';
import { DomainDoc } from '../interface';
import * as db from '../service/db';
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 path from 'path';
import os from 'os';
import http, { Server } from 'http';
import http from 'http';
import moment from 'moment-timezone';
import { isSafeInteger, Dictionary } from 'lodash';
import { ObjectID } from 'mongodb';
@@ -776,8 +776,8 @@ export async function start() {
}
app.use(router.routes()).use(router.allowedMethods());
Route('notfound_handler', '*', Handler);
server.listen(global.argv.port || port);
console.log('Server listening at: %s', global.argv.port || port);
server.listen(argv.port || port);
console.log('Server listening at: %s', argv.port || port);
}
global.Hydro.service.server = {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "hydrooj",
"version": "2.9.10",
"version": "2.9.11",
"main": "dist/loader.js",
"bin": "bin/hydrooj.js",
"repository": "https://github.com/hydro-dev/Hydro.git",