diff --git a/install/install.js b/install/install.js
index 149b0ed76..b987caf03 100644
--- a/install/install.js
+++ b/install/install.js
@@ -2,40 +2,107 @@
/* eslint-disable no-undef */
///
-log.info('开始运行 HydroOJ 安装工具 / Starting HydroOJ installation tool');
+const locales = {
+ zh: {
+ 'install.start': '开始运行 Hydro 安装工具',
+ 'info.mirror': '将首选 %s 镜像。可以使用 MIRROR=tsinghua|tencent|official 更改。',
+ 'error.rootRequired': '请使用 root 用户运行该工具。',
+ 'error.unsupportedArch': '不支持的架构 %s ,请尝试手动安装。',
+ 'error.osreleaseNotFound': '无法获取系统版本信息(/etc/os-release 文件未找到),请尝试手动安装。',
+ 'error.unsupportedOS': '不支持的操作系统 %s ,请尝试手动安装,',
+ 'install.preparing': '正在初始化安装...',
+ 'install.mongodb': '正在安装 mongodb...',
+ 'install.nvm': '正在安装 NVM...',
+ 'error.nodeWithoutNVMDetected': '检测到您的系统中安装了 Node,但未使用 NVM,请尝试手动安装或卸载当前 Node 后再试。',
+ 'install.nodejs': '正在安装 NodeJS...',
+ 'error.nodeVersionPraseFail': '无法解析 Node 版本号,请尝试手动安装。',
+ 'install.pm2': '正在安装 PM2...',
+ 'install.createDatabaseUser': '正在创建数据库用户...',
+ 'install.minio': '正在安装 MinIO...',
+ 'install.compiler': '正在安装编译器...',
+ 'install.hydro': '正在安装 Hydro...',
+ 'install.done': 'Hydro 安装成功!',
+ 'extra.restartTerm': '请重启终端(或重新连接ssh)并切换到 root 用户执行剩下的操作。',
+ 'extra.dbUser': '数据库用户名: hydro',
+ 'extra.dbPassword': '数据库密码: %s',
+ 'info.skip': '步骤已跳过。',
+ },
+ en: {
+ 'install.start': 'Starting Hydro installation tool',
+ 'info.mirror': 'Using preferred %s mirror. You can use MIRROR=tsinghua|tencent|official to change.',
+ 'error.rootRequired': 'Please run this tool as root user.',
+ 'error.unsupportedArch': 'Unsupported architecture %s, please try to install manually.',
+ 'error.osreleaseNotFound': 'Unable to get system version information (/etc/os-release file not found), please try to install manually.',
+ 'error.unsupportedOS': 'Unsupported operating system %s, please try to install manually.',
+ 'install.preparing': 'Initializing installation...',
+ 'install.mongodb': 'Installing mongodb...',
+ 'install.nvm': 'Installing NVM...',
+ 'error.nodeWithoutNVMDetected': 'Detected Node installation without NVM, please try to install manually or uninstall current Node first.',
+ 'install.nodejs': 'Installing NodeJS...',
+ 'error.nodeVersionPraseFail': 'Unable to parse Node version, please try to install manually.',
+ 'install.pm2': 'Installing PM2...',
+ 'install.createDatabaseUser': 'Creating database user...',
+ 'install.minio': 'Installing MinIO...',
+ 'install.compiler': 'Installing compiler...',
+ 'install.hydro': 'Installing Hydro...',
+ 'install.done': 'Hydro installation completed!',
+ 'extra.restartTerm': 'Please restart your terminal (or reconnect ssh) and switch to root user to execute the remaining operations.',
+ 'extra.dbUser': 'Database username: hydro',
+ 'extra.dbPassword': 'Database password: %s',
+ 'info.skip': 'Step skipped.',
+ },
+};
+const preferredMirror = __env.MIRROR || 'tsinghua';
+const mirrors = {
+ node: {
+ tsinghua: 'https://mirrors.tuna.tsinghua.edu.cn/nodejs-release',
+ tencent: 'https://mirrors.cloud.tencent.com/nodejs-release',
+ official: 'https://nodejs.org/dist',
+ },
+ mongodb: {
+ tsinghua: 'https://mirrors.tuna.tsinghua.edu.cn/mongodb/apt/ubuntu',
+ tencent: 'https://mirrors.cloud.tencent.com/mongodb/apt/ubuntu',
+ official: 'https://repo.mongodb.org/apt/ubuntu',
+ },
+ minio: {
+ china: 'http://dl.minio.org.cn/server/minio/release/linux-amd64/minio',
+ undefined: 'https://s3.undefined.moe/public/minio',
+ official: 'https://dl.min.io/server/minio/release/linux-amd64/minio',
+ },
+ sandbox: {
+ undefined: 'https://s3.undefined.moe/file/executor-amd64',
+ official: 'https://github.com/criyle/go-judge/releases/download/v1.2.0/executorserver-amd64',
+ },
+};
+let retry = 0;
+/** @argument {keyof typeof mirrors} target */
+function getMirror(target) {
+ if (!mirrors[target]) log.fatal('Unknown resource:', target);
+ const res = [];
+ if (mirrors[target][preferredMirror]) res.push(mirrors[target][preferredMirror]);
+ res.push(...Object.keys(mirrors[target]).map((i) => mirrors[target][i]));
+ return res[retry % res.length];
+}
+let locale = __env.LANG?.includes('zh') ? 'zh' : 'en';
+if (__env.TERM === 'linux') locale = 'en';
+log.info = ((orig) => (str, ...args) => orig(locales[locale][str] || str, ...args) && 0)(log.info);
+log.warn = ((orig) => (str, ...args) => orig(locales[locale][str] || str, ...args) && 0)(log.warn);
+log.fatal = ((orig) => (str, ...args) => orig(locales[locale][str] || str, ...args) && 0)(log.fatal);
+
+log.info('install.start');
const MINIO_ACCESS_KEY = randomstring(32);
const MINIO_SECRET_KEY = randomstring(32);
let DATABASE_PASSWORD = randomstring(32);
-let retry = 0;
-
-const source_nvm = `\
+const source_nvm = `
# load nvm env (by hydro installer)
export NVM_DIR="$HOME/.nvm"
-[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"`;
-if (__user !== 'root') log.fatal('请使用 root 用户运行该工具。');
-if (__arch !== 'amd64') log.fatal('不支持的架构 %s ,请尝试手动安装', __arch);
+[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
+`;
+if (__user !== 'root') log.fatal('error.rootRequired');
+if (__arch !== 'amd64') log.fatal('error.unsupportedArch', __arch);
const dev = !!cli.get('dev');
-
-const _NODE_ = [
- 'https://mirrors.tuna.tsinghua.edu.cn/nodejs-release',
- 'https://mirrors.cloud.tencent.com/nodejs-release',
- 'https://nodejs.org/dist',
-];
-const _MONGODB_ = [
- 'https://mirrors.tuna.tsinghua.edu.cn/mongodb/apt/ubuntu',
- 'https://mirrors.cloud.tencent.com/mongodb/apt/ubuntu',
- 'https://repo.mongodb.org/apt/ubuntu',
-];
-const _MINIO_ = [
- 'http://dl.minio.org.cn/server/minio/release/linux-amd64/minio',
- 'https://s3.undefined.moe/public/minio',
-];
-const _SANDBOX_ = [
- 'https://s3.undefined.moe/file/executor-amd64',
- 'https://github.com/criyle/go-judge/releases/download/v1.2.0/executorserver-amd64',
-];
-if (!fs.exist('/etc/os-release')) log.fatal('/etc/os-release 文件未找到');
+if (!fs.exist('/etc/os-release')) log.fatal('error.osreleaseNotFound');
const osinfoFile = fs.readfile('/etc/os-release');
const lines = osinfoFile.split('\n');
const values = {};
@@ -45,20 +112,27 @@ for (const line of lines) {
if (d[1].startsWith('"')) values[d[0].toLowerCase()] = d[1].substr(1, d[1].length - 2);
else values[d[0].toLowerCase()] = d[1];
}
-if (!['ubuntu', 'arch'].includes(values.id)) log.fatal('不支持的系统,请尝试手动安装');
+if (!['ubuntu', 'arch'].includes(values.id)) log.fatal('error.unsupportedOS', values.id);
const Arch = values.id === 'arch';
const steps = [
{
- init: '正在初始化安装 / Preparing',
+ init: 'install.preparing',
operations: [
+ () => log.info('info.mirror', preferredMirror),
'mkdir -p /data/db /data/file ~/.hydro',
Arch ? 'pacman --needed --quiet --noconfirm -Sy' : 'apt-get -qq update',
- Arch ? 'pacman --needed --quiet --noconfirm -S gnupg curl' : 'apt-get install -y curl wget gnupg',
+ Arch ? 'pacman --needed --quiet --noconfirm -S gnupg curl' : 'apt-get install -qy curl wget gnupg qrencode',
+ () => {
+ if (locale === 'zh' && !Arch) {
+ log.info('扫码加入QQ群:');
+ exec('echo https://qm.qq.com/cgi-bin/qm/qr\\?k\\=0aTZfDKURRhPBZVpTYBohYG6P6sxABTw | qrencode -o - -m 2 -t UTF8', {}, 0);
+ }
+ },
],
},
{
- init: '正在安装 MongoDB / Installing MongoDB',
+ init: 'install.mongodb',
skip: () => fs.exist('/usr/bin/mongo'),
operations: Arch
? [
@@ -72,17 +146,17 @@ const steps = [
// https://letsencrypt.org/docs/dst-root-ca-x3-expiration-september-2021/
['apt-get upgrade openssl -y', { retry: true }],
['wget -qO - https://www.mongodb.org/static/pgp/server-4.4.asc | apt-key add -', { retry: true }],
- [`echo "deb [ arch=amd64 ] ${_MONGODB_[retry % _MONGODB_.length]} ${values.ubuntu_codename}\
+ [`echo "deb [ arch=amd64 ] ${getMirror('mongodb')} ${values.ubuntu_codename}\
/mongodb-org/4.4 multiverse" >/etc/apt/sources.list.d/mongodb-org-4.4.list && \
apt-get -qq update && apt-get -q install -y mongodb-org`, { retry: true }],
],
},
{
- init: '正在安装 NVM / Installing NVM',
+ init: 'install.nvm',
skip: () => {
const nvm = fs.exist('/root/.nvm');
const node = !exec('node -v').code;
- if (node && !nvm) log.fatal('检测到安装了 Node,但未使用 nvm 环境');
+ if (node && !nvm) log.fatal('error.nodeWithoutNVMDetected');
return nvm;
},
operations: [
@@ -97,21 +171,22 @@ apt-get -qq update && apt-get -q install -y mongodb-org`, { retry: true }],
],
},
{
- init: '正在安装 NodeJS / Installing NodeJS',
+ init: 'install.nodejs',
operations: [
() => {
const res = exec1('bash -c "source /root/.nvm/nvm.sh && nvm install 14"', {
- NVM_NODEJS_ORG_MIRROR: _NODE_[retry % _NODE_.length],
+ NVM_NODEJS_ORG_MIRROR: getMirror('node'),
});
let ver;
try {
ver = res.output.split('Now using node v')[1].split(' ')[0];
} catch (e) {
- log.error('Cannot parse version. Installation fail.');
+ log.error('error.nodeVersionPraseFail');
return 'retry';
}
setenv('PATH', `/root/.nvm/versions/node/v${ver}/bin:${__env.PATH}`);
- const rc_path = `/root/.${__env['SHELL'].endsWith('zsh') ? 'zsh' : 'bash'}rc`;
+ const shell = __env.SHELL.split('/');
+ const rc_path = `/root/.${shell[shell.length - 1]}rc`;
if (!fs.exist(rc_path)) fs.writefile(rc_path, source_nvm);
else {
const rc_file = fs.readfile(rc_path);
@@ -122,12 +197,12 @@ apt-get -qq update && apt-get -q install -y mongodb-org`, { retry: true }],
],
},
{
- init: '正在安装 pm2 / Installing pm2',
+ init: 'install.pm2',
skip: () => fs.exist('/usr/local/bin/pm2'),
operations: ['yarn global add pm2'],
},
{
- init: '正在创建数据库用户 / Creating database user',
+ init: 'install.createDatabaseUser',
skip: () => fs.exist('/root/.hydro/config.json'),
operations: [
'pm2 start mongod',
@@ -151,28 +226,28 @@ apt-get -qq update && apt-get -q install -y mongodb-org`, { retry: true }],
],
},
{
- init: '正在安装 MinIO / Installing MinIO',
+ init: 'install.minio',
skip: () => fs.exist('/root/.hydro/env'),
operations: [
- [`curl -fSL ${_MINIO_[retry % _MINIO_.length]} -o /usr/bin/minio`, { retry: true }],
+ [`curl -fSL ${getMirror('minio')} -o /usr/bin/minio`, { retry: true }],
'chmod +x /usr/bin/minio',
],
},
{
- init: '正在安装编译器 / Installing compiler',
+ init: 'install.compiler',
operations: [
Arch ? 'pacman --needed --quiet --noconfirm -S gcc fpc' : 'apt-get install -y g++ fp-compiler >/dev/null',
],
},
{
- init: '正在安装沙箱 / Installing sandbox',
+ init: 'install.sandbox',
operations: [
- [`curl -fSL ${_SANDBOX_[retry % _SANDBOX_.length]} -o /usr/bin/hydro-sandbox`, { retry: true }],
+ [`curl -fSL ${getMirror('sandbox')} -o /usr/bin/hydro-sandbox`, { retry: true }],
'chmod +x /usr/bin/hydro-sandbox',
],
},
{
- init: '正在安装 HydroOJ / Installing HydroOJ',
+ init: 'install.hydro',
operations: [
...(dev
? [
@@ -187,7 +262,7 @@ apt-get -qq update && apt-get -q install -y mongodb-org`, { retry: true }],
],
},
{
- init: '正在启动 / Starting',
+ init: 'install.starting',
operations: [
`echo "MINIO_ACCESS_KEY=${MINIO_ACCESS_KEY}\nMINIO_SECRET_KEY=${MINIO_SECRET_KEY}" >/root/.hydro/env`,
`pm2 start "MINIO_ACCESS_KEY=${MINIO_ACCESS_KEY} MINIO_SECRET_KEY=${MINIO_SECRET_KEY} minio server /data/file" --name minio`,
@@ -200,15 +275,14 @@ apt-get -qq update && apt-get -q install -y mongodb-org`, { retry: true }],
],
},
{
- init: '安装完成 / Install done',
+ init: 'install.done',
operations: [
() => {
DATABASE_PASSWORD = loadconfig('/root/.hydro/config.json').password;
},
- () => log.info('请重启终端并切换到 root 用户执行其他操作'),
- () => log.info('Please restart terminal and switch to root user to perform other operations'),
- () => log.info('数据库用户名:hydro / Database username: hydro'),
- () => log.info('数据库密码:%s / Database password: %s', DATABASE_PASSWORD, DATABASE_PASSWORD),
+ () => log.info('extra.restartTerm'),
+ () => log.info('extra.dbUser'),
+ () => log.info('extra.dbPassword', DATABASE_PASSWORD),
() => log.info('MINIO_ACCESS_KEY=%s', MINIO_ACCESS_KEY),
() => log.info('MINIO_SECRET_KEY=%s', MINIO_SECRET_KEY),
],
@@ -226,14 +300,14 @@ for (let i = 0; i < steps.length; i++) {
exec(op[0], op[1]);
while (__code !== 0) {
if (op[1].retry && retry < 30) {
- log.warn('Retry...');
+ log.warn('Retry... (%s)', op[0]);
exec(op[0], op[1]);
retry++;
} else log.fatal('Error when running %s', op[0]);
}
} else {
- let res = 'retry';
retry = 0;
+ let res = op[0](op[1]);
while (res === 'retry') {
if (retry < 30) {
log.warn('Retry...');
@@ -243,7 +317,7 @@ for (let i = 0; i < steps.length; i++) {
}
}
}
- } else log.info('已跳过该步骤 / Step skipped');
+ } else log.info('info.skip');
}
exit(0);
diff --git a/install/jssh.d.ts b/install/jssh.d.ts
index 6e877495e..6d59b33a6 100644
--- a/install/jssh.d.ts
+++ b/install/jssh.d.ts
@@ -522,6 +522,69 @@ interface HttpModule {
* @return 存储的文件名
*/
download(url: string, filename?: string): string;
+
+ /**
+ * 发送HTTP GET请求
+ * @param url 请求URL
+ * @param headers 请求头
+ * @return 响应结果
+ */
+ get(url: String, headers?: Record): HttpResponse;
+
+ /**
+ * 发送HTTP HEAD请求
+ * @param url 请求URL
+ * @param headers 请求头
+ * @return 响应结果
+ */
+ head(url: String, headers?: Record): HttpResponse;
+
+ /**
+ * 发送HTTP OPTIONS请求
+ * @param url 请求URL
+ * @param headers 请求头
+ * @return 响应结果
+ */
+ options(url: String, headers?: Record): HttpResponse;
+
+ /**
+ * 发送HTTP POST请求
+ * @param url 请求URL
+ * @param headers 请求头
+ * @param body 请求体
+ * @return 响应结果
+ */
+ post(
+ url: String,
+ headers?: Record,
+ body?: string
+ ): HttpResponse;
+
+ /**
+ * 发送HTTP PUT请求
+ * @param url 请求URL
+ * @param headers 请求头
+ * @param body 请求体
+ * @return 响应结果
+ */
+ put(
+ url: String,
+ headers?: Record,
+ body?: string
+ ): HttpResponse;
+
+ /**
+ * 发送HTTP DELETE请求
+ * @param url 请求URL
+ * @param headers 请求头
+ * @param body 请求体
+ * @return 响应结果
+ */
+ delete(
+ url: String,
+ headers?: Record,
+ body?: string
+ ): HttpResponse;
}
interface HttpResponse {
@@ -712,4 +775,4 @@ interface SqlExecResult {
* @param ok 要断言的值
* @param message 提示信息
*/
-declare function assert(ok: boolean, message?: string);
\ No newline at end of file
+declare function assert(ok: boolean, message?: string);