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);