diff --git a/.gitignore b/.gitignore index 766153eb..e201ecd0 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,4 @@ __pycache__ /shell/preload/lang_env.sh .deepseek/ +.claude/ \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..4d87c2f3 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,43 @@ + +# GitNexus — Code Intelligence + +This project is indexed by GitNexus as **qinglong** (2740 symbols, 6583 relationships, 230 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. + +> Index stale? Run `node .gitnexus/run.cjs analyze` from the project root — it auto-selects an available runner. No `.gitnexus/run.cjs` yet? `npx gitnexus analyze` (npm 11 crash → `npm i -g gitnexus`; #1939). + +## Always Do + +- **MUST run impact analysis before editing any symbol.** Before modifying a function, class, or method, run `impact({target: "symbolName", direction: "upstream"})` and report the blast radius (direct callers, affected processes, risk level) to the user. +- **MUST run `detect_changes()` before committing** to verify your changes only affect expected symbols and execution flows. For regression review, compare against the default branch: `detect_changes({scope: "compare", base_ref: "develop"})`. +- **MUST warn the user** if impact analysis returns HIGH or CRITICAL risk before proceeding with edits. +- When exploring unfamiliar code, use `query({query: "concept"})` to find execution flows instead of grepping. It returns process-grouped results ranked by relevance. +- When you need full context on a specific symbol — callers, callees, which execution flows it participates in — use `context({name: "symbolName"})`. + +## Never Do + +- NEVER edit a function, class, or method without first running `impact` on it. +- NEVER ignore HIGH or CRITICAL risk warnings from impact analysis. +- NEVER rename symbols with find-and-replace — use `rename` which understands the call graph. +- NEVER commit changes without running `detect_changes()` to check affected scope. + +## Resources + +| Resource | Use for | +|----------|---------| +| `gitnexus://repo/qinglong/context` | Codebase overview, check index freshness | +| `gitnexus://repo/qinglong/clusters` | All functional areas | +| `gitnexus://repo/qinglong/processes` | All execution flows | +| `gitnexus://repo/qinglong/process/{name}` | Step-by-step execution trace | + +## CLI + +| Task | Read this skill file | +|------|---------------------| +| Understand architecture / "How does X work?" | `.claude/skills/gitnexus/gitnexus-exploring/SKILL.md` | +| Blast radius / "What breaks if I change X?" | `.claude/skills/gitnexus/gitnexus-impact-analysis/SKILL.md` | +| Trace bugs / "Why is X failing?" | `.claude/skills/gitnexus/gitnexus-debugging/SKILL.md` | +| Rename / extract / split / refactor | `.claude/skills/gitnexus/gitnexus-refactoring/SKILL.md` | +| Tools, resources, schema reference | `.claude/skills/gitnexus/gitnexus-guide/SKILL.md` | +| Index, status, clean, wiki CLI commands | `.claude/skills/gitnexus/gitnexus-cli/SKILL.md` | + + \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..4d87c2f3 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,43 @@ + +# GitNexus — Code Intelligence + +This project is indexed by GitNexus as **qinglong** (2740 symbols, 6583 relationships, 230 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. + +> Index stale? Run `node .gitnexus/run.cjs analyze` from the project root — it auto-selects an available runner. No `.gitnexus/run.cjs` yet? `npx gitnexus analyze` (npm 11 crash → `npm i -g gitnexus`; #1939). + +## Always Do + +- **MUST run impact analysis before editing any symbol.** Before modifying a function, class, or method, run `impact({target: "symbolName", direction: "upstream"})` and report the blast radius (direct callers, affected processes, risk level) to the user. +- **MUST run `detect_changes()` before committing** to verify your changes only affect expected symbols and execution flows. For regression review, compare against the default branch: `detect_changes({scope: "compare", base_ref: "develop"})`. +- **MUST warn the user** if impact analysis returns HIGH or CRITICAL risk before proceeding with edits. +- When exploring unfamiliar code, use `query({query: "concept"})` to find execution flows instead of grepping. It returns process-grouped results ranked by relevance. +- When you need full context on a specific symbol — callers, callees, which execution flows it participates in — use `context({name: "symbolName"})`. + +## Never Do + +- NEVER edit a function, class, or method without first running `impact` on it. +- NEVER ignore HIGH or CRITICAL risk warnings from impact analysis. +- NEVER rename symbols with find-and-replace — use `rename` which understands the call graph. +- NEVER commit changes without running `detect_changes()` to check affected scope. + +## Resources + +| Resource | Use for | +|----------|---------| +| `gitnexus://repo/qinglong/context` | Codebase overview, check index freshness | +| `gitnexus://repo/qinglong/clusters` | All functional areas | +| `gitnexus://repo/qinglong/processes` | All execution flows | +| `gitnexus://repo/qinglong/process/{name}` | Step-by-step execution trace | + +## CLI + +| Task | Read this skill file | +|------|---------------------| +| Understand architecture / "How does X work?" | `.claude/skills/gitnexus/gitnexus-exploring/SKILL.md` | +| Blast radius / "What breaks if I change X?" | `.claude/skills/gitnexus/gitnexus-impact-analysis/SKILL.md` | +| Trace bugs / "Why is X failing?" | `.claude/skills/gitnexus/gitnexus-debugging/SKILL.md` | +| Rename / extract / split / refactor | `.claude/skills/gitnexus/gitnexus-refactoring/SKILL.md` | +| Tools, resources, schema reference | `.claude/skills/gitnexus/gitnexus-guide/SKILL.md` | +| Index, status, clean, wiki CLI commands | `.claude/skills/gitnexus/gitnexus-cli/SKILL.md` | + + \ No newline at end of file diff --git a/shell/api.sh b/shell/api.sh index 56fcbd50..b7366cb8 100755 --- a/shell/api.sh +++ b/shell/api.sh @@ -50,9 +50,9 @@ add_cron_api() { code=$(echo "$api" | jq -r .code) message=$(echo "$api" | jq -r .message) if [[ $code == 200 ]]; then - echo -e "$name -> 添加成功" + t '%s -> 添加成功' "$name" else - echo -e "$name -> 添加失败(${message})" + t '%s -> 添加失败(%s)' "$name" "$message" fi } @@ -81,9 +81,9 @@ update_cron_api() { code=$(echo "$api" | jq -r .code) message=$(echo "$api" | jq -r .message) if [[ $code == 200 ]]; then - echo -e "$name -> 更新成功" + t '%s -> 更新成功' "$name" else - echo -e "$name -> 更新失败(${message})" + t '%s -> 更新失败(%s)' "$name" "$message" fi } @@ -108,9 +108,9 @@ update_cron_command_api() { code=$(echo "$api" | jq -r .code) message=$(echo "$api" | jq -r .message) if [[ $code == 200 ]]; then - echo -e "$command -> 更新成功" + t '%s -> 更新成功' "$command" else - echo -e "$command -> 更新失败(${message})" + t '%s -> 更新失败(%s)' "$command" "$message" fi } @@ -128,9 +128,9 @@ del_cron_api() { code=$(echo "$api" | jq -r .code) message=$(echo "$api" | jq -r .message) if [[ $code == 200 ]]; then - echo -e "成功" + t '成功' else - echo -e "失败(${message})" + t '失败(%s)' "$message" fi } @@ -175,9 +175,9 @@ notify_api() { code=$(echo "$api" | jq -r .code) message=$(echo "$api" | jq -r .message) if [[ $code == 200 ]]; then - echo -e "通知发送成功🎉" + t '通知发送成功🎉' else - echo -e "通知失败(${message})" + t '通知失败(%s)' "$message" fi } @@ -214,9 +214,9 @@ update_auth_config() { code=$(echo "$api" | jq -r .code) message=$(echo "$api" | jq -r .message) if [[ $code == 200 ]]; then - echo -e "${tip}成功🎉" + t '%s成功🎉' "$tip" else - echo -e "${tip}失败(${message})" + t '%s失败(%s)' "$tip" "$message" fi } diff --git a/shell/bot.sh b/shell/bot.sh index 3d8e799d..9eb74780 100755 --- a/shell/bot.sh +++ b/shell/bot.sh @@ -8,7 +8,7 @@ else repo_path="${dir_repo}/diybot" fi -echo -e "\n1、安装bot依赖...\n" +t '\n1、安装bot依赖...\n' os_name="${QL_OS_TYPE:-}" if [ -z "$os_name" ]; then os_name=$(source /etc/os-release && echo "$ID") @@ -28,13 +28,13 @@ case "$os_name" in $SUDO apt-get install -y gcc python3-dev musl-dev zlib1g-dev libjpeg-dev libfreetype-dev ;; *) - echo -e "暂不支持此系统 $os_name" + t '暂不支持此系统 %s' "$os_name" exit 1 ;; esac -echo -e "\nbot依赖安装成功...\n" +t '\nbot依赖安装成功...\n' -echo -e "2、下载bot所需文件...\n" +t '2、下载bot所需文件...\n' if [[ ! -d ${repo_path}/.git ]]; then rm -rf ${repo_path} git_clone_scripts ${url} ${repo_path} "main" @@ -44,9 +44,9 @@ cp -rf "$repo_path/jbot" $dir_data if [[ ! -f "$dir_config/bot.json" ]]; then cp -f "$repo_path/config/bot.json" "$dir_config" fi -echo -e "\nbot文件下载成功...\n" +t '\nbot文件下载成功...\n' -echo -e "3、安装python3依赖...\n" +t '3、安装python3依赖...\n' cp -f "$repo_path/jbot/requirements.txt" "$dir_data" cd $dir_data @@ -56,11 +56,11 @@ cat requirements.txt | while read LREAD; do fi done -echo -e "\npython3依赖安装成功...\n" +t '\npython3依赖安装成功...\n' -echo -e "4、启动bot程序...\n" +t '4、启动bot程序...\n' make_dir $dir_log/bot cd $dir_data ps -eo pid,command | grep "python3 -m jbot" | grep -v grep | awk '{print $1}' | xargs kill -9 2>/dev/null nohup python3 -m jbot >$dir_log/bot/nohup.log 2>&1 & -echo -e "bot启动成功...\n" +t 'bot启动成功...\n' diff --git a/shell/check.sh b/shell/check.sh index bc6c7b7e..ce28001b 100755 --- a/shell/check.sh +++ b/shell/check.sh @@ -1,29 +1,29 @@ #!/usr/bin/env bash reset_env() { - echo -e "---> 1. 开始检测配置文件\n" + t '---> 1. 开始检测配置文件\n' fix_config - echo -e "---> 配置文件检测完成\n" + t '---> 配置文件检测完成\n' - echo -e "---> 2. 开始安装青龙依赖\n" + t '---> 2. 开始安装青龙依赖\n' npm_install_2 $dir_root - echo -e "---> 青龙依赖安装完成\n" + t '---> 青龙依赖安装完成\n' - echo -e "---> 脚本依赖安装完成\n" + t '---> 脚本依赖安装完成\n' } copy_dep() { - echo -e "---> 1. 复制通知文件\n" - echo -e "---> 复制一份 $file_notify_py_sample 为 $file_notify_py\n" + t '---> 1. 复制通知文件\n' + t '---> 复制一份 %s 为 %s\n' "$file_notify_py_sample" "$file_notify_py" cp -fv $file_notify_py_sample $file_notify_py echo - echo -e "---> 复制一份 $file_notify_js_sample 为 $file_notify_js\n" + t '---> 复制一份 %s 为 %s\n' "$file_notify_js_sample" "$file_notify_js" cp -fv $file_notify_js_sample $file_notify_js - echo -e "---> 通知文件复制完成\n" + t '---> 通知文件复制完成\n' } pm2_log() { - echo -e "---> pm2日志" + t '---> pm2日志' local panelOut="/root/.pm2/logs/qinglong-out.log" local panelError="/root/.pm2/logs/qinglong-error.log" tail -n 300 "$panelOut" @@ -32,9 +32,10 @@ pm2_log() { check_ql() { local api=$(curl -s --noproxy "*" "http://localhost:${ql_port}") - echo -e "\n=====> 检测面板\n\n$api\n" + t '\n=====> 检测面板' + echo -e "\n\n$api\n" if [[ $api =~ "
" ]]; then - echo -e "=====> 面板服务启动正常\n" + t '=====> 面板服务启动正常\n' fi } @@ -49,14 +50,15 @@ check_pm2() { -H 'Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7' \ --compressed ) - echo -e "\n=====> 检测后台\n\n$api\n" + t '\n=====> 检测后台' + echo -e "\n\n$api\n" if [[ $api =~ "{\"code\"" ]]; then - echo -e "=====> 后台服务启动正常\n" + t '=====> 后台服务启动正常\n' fi } main() { - echo -e "=====> 开始检测" + t '=====> 开始检测' npm i -g pnpm@8.3.1 pm2 ts-node reset_env @@ -64,7 +66,7 @@ main() { check_ql check_pm2 reload_pm2 - echo -e "\n=====> 检测结束\n" + t '\n=====> 检测结束\n' } main diff --git a/shell/lang/en.sh b/shell/lang/en.sh new file mode 100644 index 00000000..7e823d25 --- /dev/null +++ b/shell/lang/en.sh @@ -0,0 +1,105 @@ +# English language pack +declare -gA LANG_MESSAGES=( + ['任务随机延迟 %s 秒,将于 %s 开始,配置文件参数 RandomDelay 置空可取消延迟\n']='Task delayed %s seconds, will start at %s. Set RandomDelay to empty to cancel delay\n' + ['开始执行...\n']='Starting execution...\n' + ['已停止']='Stopped' + ['完成']='Completed' + ['失败(退出码 %s)']='Failed (exit code %s)' + ['安装 %s 依赖包...\n']='Installing %s dependencies...\n' + ['开始拉取仓库 %s 到 %s\n']='Cloning repository %s to %s\n' + ['添加成功']='Added successfully' + ['添加失败(%s)']='Add failed (%s)' + ['更新成功']='Updated successfully' + ['更新失败(%s)']='Update failed (%s)' + ['成功']='Success' + ['失败(%s)']='Failed (%s)' + ['通知发送成功🎉']='Notification sent successfully 🎉' + ['通知失败(%s)']='Notification failed (%s)' + ['当前有以下脚本可以运行:']='Available scripts:' + ['暂无脚本可以执行']='No scripts available' + ['警告:工作目录不存在 %s']='Warning: working directory does not exist: %s' + ['\n缺少并发运行的环境变量参数']='\nMissing concurrency environment variable' + ['\n缺少单独运行的参数 task xxx.js desi Test']='\nMissing parameter: task xxx.js desi Test' + ['暂不支持此系统 %s']='Unsupported system: %s' + ['检测到 pm2 服务正在运行']='pm2 service is running' + ['npm 模块位置: %s']='npm module location: %s' + ['导入数据成功 %s']='Data imported successfully: %s' + ['导出数据成功 %s']='Data exported successfully: %s' + ['当前版本: %s / 最新版本: %s']='Current: %s / Latest: %s' + ['已是最新版本']='Already up to date' + ['%s 个定时任务正在运行']='%s scheduled tasks running' + ['执行前置命令\n']='Running pre-command\n' + ['\n执行前置命令结束\n']='\nPre-command finished\n' + ['\n执行后置命令\n']='\nRunning post-command\n' + ['\n执行后置命令结束']='\nPost-command finished' + ['警告: PM2 启动失败 (退出码: %s),可能是由于硬件不兼容']='Warning: PM2 start failed (exit code: %s), possibly due to hardware incompatibility' + ['正在尝试直接使用 Node.js 启动服务...']='Attempting to start service with Node.js directly...' + ['已使用 Node.js 直接启动服务 (PID: %s)']='Service started with Node.js directly (PID: %s)' + ['注意: 使用此模式时,部分 PM2 管理功能将不可用']='Note: some PM2 management features are unavailable in this mode' + ['## 开始执行... %s\n']='## Starting... %s\n' + ['\n## 已停止 🛑... %s 耗时 %s 秒%s']='\n## Stopped 🛑... %s took %s seconds%s' + ['\n## 完成 ✅... %s 耗时 %s 秒%s']='\n## Completed ✅... %s took %s seconds%s' + ['\n## 失败 ❌(退出码 %s)... %s 耗时 %s 秒%s']='\n## Failed ❌(exit code %s)... %s took %s seconds%s' + ['%s -> 添加成功']='%s -> Added successfully' + ['%s -> 添加失败(%s)']='%s -> Add failed (%s)' + ['%s -> 更新成功']='%s -> Updated successfully' + ['%s -> 更新失败(%s)']='%s -> Update failed (%s)' + ['%s成功🎉']='%s succeeded 🎉' + ['%s失败(%s)']='%s failed (%s)' + ['检测到有%s的定时任务:']='Found %s scheduled tasks:' + ['\n开始尝试自动删除失效的定时任务...']='\nAttempting to remove invalid scheduled tasks...' + ['\n开始尝试自动添加定时任务...']='\nAttempting to add scheduled tasks...' + ['拉取 %s 成功...\n']='Pull %s succeeded...\n' + ['拉取 %s 失败,请检查日志...\n']='Pull %s failed, check logs...\n' + ['开始下载:%s 保存路径:%s\n']='Downloading: %s to: %s\n' + ['下载 %s 成功...\n']='Download %s succeeded...\n' + ['下载 %s 失败,保留之前正常下载的版本...\n']='Download %s failed, keeping previous version...\n' + ['%s文件不存在,跳过执行...\n']='%s does not exist, skipping...\n' + ['使用 %s 源更新...\n']='Updating using %s mirror...\n' + ['更新青龙源文件成功...\n']='Qinglong source updated successfully\n' + ['更新青龙源文件失败,请检查网络...\n']='Qinglong source update failed, check network\n' + ['更新青龙静态资源成功...\n']='Static assets updated successfully\n' + ['更新青龙静态资源失败,请检查网络...\n']='Static assets update failed, check network\n' + ['\n开始检测依赖...\n']='\nChecking dependencies...\n' + ['\n依赖检测安装成功...\n']='\nDependencies installed successfully\n' + ['更新包下载成功...\n']='Package download succeeded\n' + ['\n依赖检测安装失败,请检查网络...\n']='\nDependency installation failed, check network\n' + ['\n1、安装bot依赖...\n']='\n1. Installing bot dependencies...\n' + ['\nbot依赖安装成功...\n']='\nBot dependencies installed\n' + ['2、下载bot所需文件...\n']='2. Downloading bot files...\n' + ['\nbot文件下载成功...\n']='\nBot files downloaded\n' + ['3、安装python3依赖...\n']='3. Installing python3 dependencies...\n' + ['\npython3依赖安装成功...\n']='\nPython3 dependencies installed\n' + ['4、启动bot程序...\n']='4. Starting bot...\n' + ['bot启动成功...\n']='Bot started successfully\n' + # check.sh + ['---> 1. 开始检测配置文件\n']='---> 1. Checking config...\n' + ['---> 配置文件检测完成\n']='---> Config check complete\n' + ['---> 2. 开始安装青龙依赖\n']='---> 2. Installing qinglong dependencies...\n' + ['---> 青龙依赖安装完成\n']='---> Dependencies installed\n' + ['---> 脚本依赖安装完成\n']='---> Script dependencies installed\n' + ['---> 1. 复制通知文件\n']='---> 1. Copying notification files...\n' + ['---> 复制一份 %s 为 %s\n']='---> Copying %s to %s\n' + ['---> 通知文件复制完成\n']='---> Notification files copied\n' + ['---> pm2日志']='---> pm2 log' + ['\n=====> 检测面板']='\n=====> Checking panel' + ['=====> 面板服务启动正常\n']='=====> Panel service running normally\n' + ['\n=====> 检测后台']='\n=====> Checking backend' + ['=====> 后台服务启动正常\n']='=====> Backend service running normally\n' + ['=====> 开始检测']='=====> Starting check' + ['\n=====> 检测结束\n']='\n=====> Check complete\n' + # rmlog.sh + ['查询文件 %s']='Checking file: %s' + ['删除中~']='Deleting...' + ['正在被 %s 使用,跳过~']='In use by %s, skipping...' + ['查找旧日志文件中...\n']='Looking for old log files...\n' + ['删除旧日志执行完毕\n']='Old log cleanup complete\n' + # start.sh + ['未找到 qinglong 模块,请先执行 npm i -g @whyour/qinglong 安装']='Module not found. Run: npm i -g @whyour/qinglong' + ['请先手动设置 export QL_DIR=%s,环境变量,并手动添加到系统环境变量,然后再次执行命令 qinglong 启动服务']='Set env: export QL_DIR=%s, then run qinglong to start' + ['请先手动设置数据存储目录 export QL_DATA_DIR 环境变量,目录必须以斜杠开头的绝对路径,并且以 /data 结尾,例如 /ql/data 并手动添加到系统环境变量']='Set QL_DATA_DIR (absolute path ending with /data, e.g. /ql/data)' + ['QL_DATA_DIR 必须以 /data 结尾,例如 /ql/data,如果有历史数据,请新建 data 目录,把历史数据放到 data 目录中']='QL_DATA_DIR must end with /data, e.g. /ql/data' + ['暂不支持此系统部署 %s']='Unsupported system for deployment: %s' + # update.sh + ['命令输入错误...\n']='Invalid command...\n' +) diff --git a/shell/lang/zh.sh b/shell/lang/zh.sh new file mode 100644 index 00000000..0a3c668c --- /dev/null +++ b/shell/lang/zh.sh @@ -0,0 +1,105 @@ +# 中文语言包(zh key → zh value 恒等,en 包做实际翻译) +declare -gA LANG_MESSAGES=( + ['任务随机延迟 %s 秒,将于 %s 开始,配置文件参数 RandomDelay 置空可取消延迟\n']='任务随机延迟 %s 秒,将于 %s 开始,配置文件参数 RandomDelay 置空可取消延迟\n' + ['开始执行...\n']='开始执行...\n' + ['已停止']='已停止' + ['完成']='完成' + ['失败(退出码 %s)']='失败(退出码 %s)' + ['安装 %s 依赖包...\n']='安装 %s 依赖包...\n' + ['开始拉取仓库 %s 到 %s\n']='开始拉取仓库 %s 到 %s\n' + ['添加成功']='添加成功' + ['添加失败(%s)']='添加失败(%s)' + ['更新成功']='更新成功' + ['更新失败(%s)']='更新失败(%s)' + ['成功']='成功' + ['失败(%s)']='失败(%s)' + ['通知发送成功🎉']='通知发送成功🎉' + ['通知失败(%s)']='通知失败(%s)' + ['当前有以下脚本可以运行:']='当前有以下脚本可以运行:' + ['暂无脚本可以执行']='暂无脚本可以执行' + ['警告:工作目录不存在 %s']='警告:工作目录不存在 %s' + ['\n缺少并发运行的环境变量参数']='\n缺少并发运行的环境变量参数' + ['\n缺少单独运行的参数 task xxx.js desi Test']='\n缺少单独运行的参数 task xxx.js desi Test' + ['暂不支持此系统 %s']='暂不支持此系统 %s' + ['检测到 pm2 服务正在运行']='检测到 pm2 服务正在运行' + ['npm 模块位置: %s']='npm 模块位置: %s' + ['导入数据成功 %s']='导入数据成功 %s' + ['导出数据成功 %s']='导出数据成功 %s' + ['当前版本: %s / 最新版本: %s']='当前版本: %s / 最新版本: %s' + ['已是最新版本']='已是最新版本' + ['%s 个定时任务正在运行']='%s 个定时任务正在运行' + ['执行前置命令\n']='执行前置命令\n' + ['\n执行前置命令结束\n']='\n执行前置命令结束\n' + ['\n执行后置命令\n']='\n执行后置命令\n' + ['\n执行后置命令结束']='\n执行后置命令结束' + ['警告: PM2 启动失败 (退出码: %s),可能是由于硬件不兼容']='警告: PM2 启动失败 (退出码: %s),可能是由于硬件不兼容' + ['正在尝试直接使用 Node.js 启动服务...']='正在尝试直接使用 Node.js 启动服务...' + ['已使用 Node.js 直接启动服务 (PID: %s)']='已使用 Node.js 直接启动服务 (PID: %s)' + ['注意: 使用此模式时,部分 PM2 管理功能将不可用']='注意: 使用此模式时,部分 PM2 管理功能将不可用' + ['## 开始执行... %s\n']='## 开始执行... %s\n' + ['\n## 已停止 🛑... %s 耗时 %s 秒%s']='\n## 已停止 🛑... %s 耗时 %s 秒%s' + ['\n## 完成 ✅... %s 耗时 %s 秒%s']='\n## 完成 ✅... %s 耗时 %s 秒%s' + ['\n## 失败 ❌(退出码 %s)... %s 耗时 %s 秒%s']='\n## 失败 ❌(退出码 %s)... %s 耗时 %s 秒%s' + ['%s -> 添加成功']='%s -> 添加成功' + ['%s -> 添加失败(%s)']='%s -> 添加失败(%s)' + ['%s -> 更新成功']='%s -> 更新成功' + ['%s -> 更新失败(%s)']='%s -> 更新失败(%s)' + ['%s成功🎉']='%s成功🎉' + ['%s失败(%s)']='%s失败(%s)' + ['检测到有%s的定时任务:']='检测到有%s的定时任务:' + ['\n开始尝试自动删除失效的定时任务...']='\n开始尝试自动删除失效的定时任务...' + ['\n开始尝试自动添加定时任务...']='\n开始尝试自动添加定时任务...' + ['拉取 %s 成功...\n']='拉取 %s 成功...\n' + ['拉取 %s 失败,请检查日志...\n']='拉取 %s 失败,请检查日志...\n' + ['开始下载:%s 保存路径:%s\n']='开始下载:%s 保存路径:%s\n' + ['下载 %s 成功...\n']='下载 %s 成功...\n' + ['下载 %s 失败,保留之前正常下载的版本...\n']='下载 %s 失败,保留之前正常下载的版本...\n' + ['%s文件不存在,跳过执行...\n']='%s文件不存在,跳过执行...\n' + ['使用 %s 源更新...\n']='使用 %s 源更新...\n' + ['更新青龙源文件成功...\n']='更新青龙源文件成功...\n' + ['更新青龙源文件失败,请检查网络...\n']='更新青龙源文件失败,请检查网络...\n' + ['更新青龙静态资源成功...\n']='更新青龙静态资源成功...\n' + ['更新青龙静态资源失败,请检查网络...\n']='更新青龙静态资源失败,请检查网络...\n' + ['\n开始检测依赖...\n']='\n开始检测依赖...\n' + ['\n依赖检测安装成功...\n']='\n依赖检测安装成功...\n' + ['更新包下载成功...\n']='更新包下载成功...\n' + ['\n依赖检测安装失败,请检查网络...\n']='\n依赖检测安装失败,请检查网络...\n' + ['\n1、安装bot依赖...\n']='\n1、安装bot依赖...\n' + ['\nbot依赖安装成功...\n']='\nbot依赖安装成功...\n' + ['2、下载bot所需文件...\n']='2、下载bot所需文件...\n' + ['\nbot文件下载成功...\n']='\nbot文件下载成功...\n' + ['3、安装python3依赖...\n']='3、安装python3依赖...\n' + ['\npython3依赖安装成功...\n']='\npython3依赖安装成功...\n' + ['4、启动bot程序...\n']='4、启动bot程序...\n' + ['bot启动成功...\n']='bot启动成功...\n' + # check.sh + ['---> 1. 开始检测配置文件\n']='---> 1. 开始检测配置文件\n' + ['---> 配置文件检测完成\n']='---> 配置文件检测完成\n' + ['---> 2. 开始安装青龙依赖\n']='---> 2. 开始安装青龙依赖\n' + ['---> 青龙依赖安装完成\n']='---> 青龙依赖安装完成\n' + ['---> 脚本依赖安装完成\n']='---> 脚本依赖安装完成\n' + ['---> 1. 复制通知文件\n']='---> 1. 复制通知文件\n' + ['---> 复制一份 %s 为 %s\n']='---> 复制一份 %s 为 %s\n' + ['---> 通知文件复制完成\n']='---> 通知文件复制完成\n' + ['---> pm2日志']='---> pm2日志' + ['\n=====> 检测面板']='\n=====> 检测面板' + ['=====> 面板服务启动正常\n']='=====> 面板服务启动正常\n' + ['\n=====> 检测后台']='\n=====> 检测后台' + ['=====> 后台服务启动正常\n']='=====> 后台服务启动正常\n' + ['=====> 开始检测']='=====> 开始检测' + ['\n=====> 检测结束\n']='\n=====> 检测结束\n' + # rmlog.sh + ['查询文件 %s']='查询文件 %s' + ['删除中~']='删除中~' + ['正在被 %s 使用,跳过~']='正在被 %s 使用,跳过~' + ['查找旧日志文件中...\n']='查找旧日志文件中...\n' + ['删除旧日志执行完毕\n']='删除旧日志执行完毕\n' + # start.sh + ['未找到 qinglong 模块,请先执行 npm i -g @whyour/qinglong 安装']='未找到 qinglong 模块,请先执行 npm i -g @whyour/qinglong 安装' + ['请先手动设置 export QL_DIR=%s,环境变量,并手动添加到系统环境变量,然后再次执行命令 qinglong 启动服务']='请先手动设置 export QL_DIR=%s,环境变量,并手动添加到系统环境变量,然后再次执行命令 qinglong 启动服务' + ['请先手动设置数据存储目录 export QL_DATA_DIR 环境变量,目录必须以斜杠开头的绝对路径,并且以 /data 结尾,例如 /ql/data 并手动添加到系统环境变量']='请先手动设置数据存储目录 export QL_DATA_DIR 环境变量,目录必须以斜杠开头的绝对路径,并且以 /data 结尾,例如 /ql/data 并手动添加到系统环境变量' + ['QL_DATA_DIR 必须以 /data 结尾,例如 /ql/data,如果有历史数据,请新建 data 目录,把历史数据放到 data 目录中']='QL_DATA_DIR 必须以 /data 结尾,例如 /ql/data,如果有历史数据,请新建 data 目录,把历史数据放到 data 目录中' + ['暂不支持此系统部署 %s']='暂不支持此系统部署 %s' + # update.sh + ['命令输入错误...\n']='命令输入错误...\n' +) diff --git a/shell/otask.sh b/shell/otask.sh index a6d0a07b..75451e36 100755 --- a/shell/otask.sh +++ b/shell/otask.sh @@ -30,7 +30,7 @@ random_delay() { else start_time=$(date -d "+${delay_second} seconds" "+%Y-%m-%d %H:%M:%S") fi - echo -e "任务随机延迟 $delay_second 秒,将于 $start_time 开始,配置文件参数 RandomDelay 置空可取消延迟 \n" + t '任务随机延迟 %s 秒,将于 %s 开始,配置文件参数 RandomDelay 置空可取消延迟\n' "$delay_second" "$start_time" sleep $delay_second fi } @@ -122,7 +122,7 @@ enter_script_workdir() { fi return fi - echo -e "警告:工作目录不存在 ${_target_dir}" + t '警告:工作目录不存在 %s' "${_target_dir}" fi cd $dir_scripts @@ -173,7 +173,7 @@ run_concurrent() { local env_param="$2" local num_param=$(echo "$3" | perl -pe "s|.*$2(.*)|\1|" | awk '{$1=$1};1') if [[ ! $env_param ]]; then - echo -e "\n 缺少并发运行的环境变量参数" + t '\n缺少并发运行的环境变量参数' exit 1 fi @@ -210,7 +210,7 @@ run_designated() { local env_param="$2" local num_param=$(echo "$3" | perl -pe "s|.*$2(.*)|\1|" | awk '{$1=$1};1') if [[ ! $env_param ]]; then - echo -e "\n 缺少单独运行的参数 task xxx.js desi Test" + t '\n缺少单独运行的参数 task xxx.js desi Test' exit 1 fi diff --git a/shell/rmlog.sh b/shell/rmlog.sh index 9e67f3a2..cc67ff24 100755 --- a/shell/rmlog.sh +++ b/shell/rmlog.sh @@ -22,12 +22,12 @@ remove_js_log() { if [[ $diff_time -gt $((${days} * 86400)) ]]; then local log_path=$(echo "$log" | sed "s,${dir_log}/,,g") local result=$(find_cron_api "log_path=$log_path") - echo -e "查询文件 $log_path" + t '查询文件 %s' "$log_path" if [[ -z $result ]]; then - echo -e "删除中~" + t '删除中~' rm -vf $log else - echo -e "正在被 $result 使用,跳过~" + t '正在被 %s 使用,跳过~' "$result" fi fi done @@ -43,8 +43,8 @@ remove_empty_dir() { } if [[ ${days} ]]; then - echo -e "查找旧日志文件中...\n" + t '查找旧日志文件中...\n' remove_js_log remove_empty_dir - echo -e "删除旧日志执行完毕\n" + t '删除旧日志执行完毕\n' fi diff --git a/shell/share.sh b/shell/share.sh index 1fb3e93c..027f65d8 100755 --- a/shell/share.sh +++ b/shell/share.sh @@ -80,6 +80,15 @@ import_config() { [[ -f $file_config_user ]] && . $file_config_user [[ -f $dir_preload/lang_env.sh ]] && . $dir_preload/lang_env.sh + # 加载语言包(bash 4+ 支持 declare -A,不兼容时回退输出中文 key) + local lang=${QL_LANG:-zh} + local lang_file="$dir_shell/lang/${lang}.sh" + if [[ ${BASH_VERSINFO[0]} -ge 4 ]] && [[ -f $lang_file ]]; then + . $lang_file + elif [[ ${BASH_VERSINFO[0]} -ge 4 ]]; then + . "$dir_shell/lang/zh.sh" + fi + load_ql_envs command_timeout_time=${CommandTimeoutTime:-""} file_extensions=${RepoFileExtensions:-"js py"} @@ -92,6 +101,18 @@ import_config() { fi } +t() { + local key="$1" + shift + local msg + if declare -p LANG_MESSAGES &>/dev/null; then + msg="${LANG_MESSAGES["$key"]}" + fi + [[ -z $msg ]] && msg="$key" + # shellcheck disable=SC2059 + printf "$msg\n" "$@" +} + set_proxy() { local proxy="$1" if [[ $proxy ]]; then @@ -226,7 +247,7 @@ npm_install_2() { local dir_work=$1 cd $dir_work - echo -e "安装 $dir_work 依赖包...\n" + t '安装 %s 依赖包...\n' "$dir_work" npm_install_sub cd $dir_current } @@ -245,7 +266,7 @@ git_clone_scripts() { local branch="$3" local proxy="$4" [[ $branch ]] && local part_cmd="-b $branch " - echo -e "开始拉取仓库 ${uniq_path} 到 $dir\n" + t '开始拉取仓库 %s 到 %s\n' "${uniq_path}" "$dir" set_proxy "$proxy" @@ -278,18 +299,18 @@ reload_pm2() { return 0 else local exit_code=$? - echo "警告: PM2 启动失败 (退出码: $exit_code),可能是由于硬件不兼容" - echo "正在尝试直接使用 Node.js 启动服务..." - + t '警告: PM2 启动失败 (退出码: %s),可能是由于硬件不兼容' "$exit_code" + t '正在尝试直接使用 Node.js 启动服务...' + # Kill any existing node processes for qinglong pkill -f "node.*static/build/app.js" 2>/dev/null || true - + # Start node directly in the background nohup node static/build/app.js > $dir_log/qinglong.log 2>&1 & local node_pid=$! - - echo "已使用 Node.js 直接启动服务 (PID: $node_pid)" - echo "注意: 使用此模式时,部分 PM2 管理功能将不可用" + + t '已使用 Node.js 直接启动服务 (PID: %s)' "$node_pid" + t '注意: 使用此模式时,部分 PM2 管理功能将不可用' return 0 fi } @@ -361,16 +382,16 @@ handle_task_start() { error_message=", 任务状态更新失败(${error})" fi fi - echo -e "## 开始执行... ${begin_time}${error_message}\n" + t '## 开始执行... %s\n' "${begin_time}${error_message}" } run_task_before() { . $file_task_before "$@" if [[ ${task_before:=} ]]; then - echo -e "执行前置命令\n" + t '执行前置命令\n' eval "${task_before%;}" - echo -e "\n执行前置命令结束\n" + t '\n执行前置命令结束\n' fi } @@ -378,9 +399,9 @@ run_task_after() { . $file_task_after "$@" if [[ ${task_after:=} ]]; then - echo -e "\n执行后置命令\n" + t '\n执行后置命令\n' eval "${task_after%;}" - echo -e "\n执行后置命令结束" + t '\n执行后置命令结束' fi } @@ -402,11 +423,11 @@ handle_task_end() { record_cron_stat "$ID" "${exit_code:-0}" "$diff_time" if [[ "${MANUAL:=}" == "true" ]]; then - echo -e "\n## 已停止 🛑... $end_time 耗时 $diff_time 秒${error_message:=}     " + t '\n## 已停止 🛑... %s 耗时 %s 秒%s' "$end_time" "$diff_time" "${error_message:=}     " elif [[ $exit_code -eq 0 ]]; then - echo -e "\n## 完成 ✅... $end_time 耗时 $diff_time 秒${error_message:=}     " + t '\n## 完成 ✅... %s 耗时 %s 秒%s' "$end_time" "$diff_time" "${error_message:=}     " else - echo -e "\n## 失败 ❌(退出码 ${exit_code})... $end_time 耗时 $diff_time 秒${error_message:=}     " + t '\n## 失败 ❌(退出码 %s)... %s 耗时 %s 秒%s' "$exit_code" "$end_time" "$diff_time" "${error_message:=}     " fi } diff --git a/shell/start.sh b/shell/start.sh index 3f4bb9cd..ab6b7648 100644 --- a/shell/start.sh +++ b/shell/start.sh @@ -12,23 +12,23 @@ if [[ ! $QL_DIR ]]; then elif [[ -d "$pnpm_dir/@whyour/qinglong" ]]; then QL_DIR="$pnpm_dir/@whyour/qinglong" else - echo -e "未找到 qinglong 模块,请先执行 npm i -g @whyour/qinglong 安装" + t '未找到 qinglong 模块,请先执行 npm i -g @whyour/qinglong 安装' fi if [[ $QL_DIR ]]; then - echo -e "请先手动设置 export QL_DIR=$QL_DIR,环境变量,并手动添加到系统环境变量,然后再次执行命令 qinglong 启动服务" + t '请先手动设置 export QL_DIR=%s,环境变量,并手动添加到系统环境变量,然后再次执行命令 qinglong 启动服务' "$QL_DIR" fi exit 1 fi if [[ ! $QL_DATA_DIR ]]; then - echo -e "请先手动设置数据存储目录 export QL_DATA_DIR 环境变量,目录必须以斜杠开头的绝对路径,并且以 /data 结尾,例如 /ql/data 并手动添加到系统环境变量" + t '请先手动设置数据存储目录 export QL_DATA_DIR 环境变量,目录必须以斜杠开头的绝对路径,并且以 /data 结尾,例如 /ql/data 并手动添加到系统环境变量' exit 1 fi if [[ $QL_DATA_DIR != */data ]]; then - echo -e "QL_DATA_DIR 必须以 /data 结尾,例如 /ql/data,如果有历史数据,请新建 data 目录,把历史数据放到 data 目录中" + t 'QL_DATA_DIR 必须以 /data 结尾,例如 /ql/data,如果有历史数据,请新建 data 目录,把历史数据放到 data 目录中' exit 1 fi @@ -69,7 +69,7 @@ if [[ $command != "reload" ]]; then $SUDO apt-get install -y git curl wget tzdata perl openssl jq nginx procps netcat-openbsd openssh-client ;; *) - echo -e "暂不支持此系统部署 $os_name" + t '暂不支持此系统部署 %s' "$os_name" exit 1 ;; esac diff --git a/shell/task.sh b/shell/task.sh index ec18ce1a..e18e18cb 100755 --- a/shell/task.sh +++ b/shell/task.sh @@ -4,7 +4,13 @@ dir_shell=$QL_DIR/shell . $dir_shell/share.sh . $dir_shell/api.sh -trap "single_hanle" 2 3 20 15 14 19 1 +trap 'single_hanle SIGINT' INT +trap 'single_hanle SIGTERM' TERM +trap 'single_hanle SIGHUP' HUP +trap 'single_hanle SIGALRM' ALRM +trap 'single_hanle SIGTSTP' TSTP +trap 'single_hanle SIGQUIT' QUIT + single_hanle() { _task_exit_code="${_task_exit_code:-$?}" [[ "$_task_exit_code" == "0" ]] && _task_exit_code="1" diff --git a/shell/update.sh b/shell/update.sh index 613ebe69..cdd6a6dc 100755 --- a/shell/update.sh +++ b/shell/update.sh @@ -33,7 +33,7 @@ output_list_add_drop() { local list=$1 local type=$2 if [[ -s $list ]]; then - echo -e "检测到有${type}的定时任务:" + t '检测到有%s的定时任务:' "$type" cat $list fi } @@ -45,7 +45,7 @@ del_cron() { local path=$2 local detail="" local ids="" - echo -e "\n开始尝试自动删除失效的定时任务..." + t '\n开始尝试自动删除失效的定时任务...' for cron in $(cat $list_drop); do local id=$(cat $list_crontab_user | grep -E "$cmd_task.* $cron" | perl -pe "s|.*ID=(.*) $cmd_task.* $cron\.*|\1|" | head -1 | awk -F " " '{print $1}') if [[ $ids ]]; then @@ -76,7 +76,7 @@ del_cron() { add_cron() { local list_add=$1 local path=$2 - echo -e "\n开始尝试自动添加定时任务..." + t '\n开始尝试自动添加定时任务...' local detail="" cd $dir_scripts for file in $(cat $list_add); do @@ -136,10 +136,10 @@ update_repo() { git_clone_scripts "${formatUrl}" ${repo_path} "${branch}" "${proxy}" if [[ $exit_status -eq 0 ]]; then - echo -e "拉取 ${uniq_path} 成功...\n" + t '拉取 %s 成功...\n' "${uniq_path}" diff_scripts "$repo_path" "$author" "$path" "$blackword" "$dependence" "$extensions" "$autoAddCron" "$autoDelCron" else - echo -e "拉取 ${uniq_path} 失败,请检查日志...\n" + t '拉取 %s 失败,请检查日志...\n' "${uniq_path}" fi } @@ -160,7 +160,7 @@ update_raw() { local raw_url="$url" local suffix="${raw_url##*.}" local raw_file_name="${uniq_path}.${suffix}" - echo -e "开始下载:${raw_url} \n\n保存路径:$dir_raw/${raw_file_name}\n" + t '开始下载:%s 保存路径:%s\n' "${raw_url}" "$dir_raw/${raw_file_name}" set_proxy "$proxy" wget -q --no-check-certificate -O "$dir_raw/${raw_file_name}.new" ${raw_url} @@ -169,7 +169,7 @@ update_raw() { if [[ $? -eq 0 ]]; then mv "$dir_raw/${raw_file_name}.new" "$dir_raw/${raw_file_name}" - echo -e "下载 ${raw_file_name} 成功...\n" + t '下载 %s 成功...\n' "${raw_file_name}" cd $dir_raw local filename="raw_${raw_file_name}" local cron_id=$(cat $list_crontab_user | grep -E "$cmd_task.* $filename" | perl -pe "s|.*ID=(.*) $cmd_task.* $filename\.*|\1|" | head -1 | awk -F " " '{print $1}') @@ -196,7 +196,7 @@ update_raw() { # update_cron_api "$cron_line:$cmd_task $filename:$cron_name:$cron_id" fi else - echo -e "下载 ${raw_file_name} 失败,保留之前正常下载的版本...\n" + t '下载 %s 失败,保留之前正常下载的版本...\n' "${raw_file_name}" [[ -f "$dir_raw/${raw_file_name}.new" ]] && rm -f "$dir_raw/${raw_file_name}.new" fi @@ -207,13 +207,13 @@ run_extra_shell() { if [[ -f $file_extra_shell ]]; then . $file_extra_shell else - echo -e "$file_extra_shell文件不存在,跳过执行...\n" + t '%s文件不存在,跳过执行...\n' "$file_extra_shell" fi } ## 脚本用法 usage() { - echo -e "$cmd_update 命令使用方法:" + t "$cmd_update 命令使用方法:" echo -e "1. $cmd_update update # 更新并重启青龙" echo -e "2. $cmd_update extra # 运行自定义脚本" echo -e "3. $cmd_update raw # 更新单个脚本文件" @@ -268,7 +268,7 @@ update_qinglong() { downloadQLUrl="https://github.com/whyour/qinglong/archive/refs/heads" downloadStaticUrl="https://github.com/whyour/qinglong-static/archive/refs/heads" fi - echo -e "使用 ${mirror} 源更新...\n" + t '使用 %s 源更新...\n' "${mirror}" local primary_branch="master" if [[ "${QL_BRANCH}" == "develop" ]] || [[ "${QL_BRANCH}" == "debian" ]] || [[ "${QL_BRANCH}" == "debian-dev" ]]; then @@ -279,13 +279,13 @@ update_qinglong() { exit_status=$? if [[ $exit_status -eq 0 ]]; then - echo -e "更新青龙源文件成功...\n" + t '更新青龙源文件成功...\n' unzip -oq ${dir_tmp}/ql.zip -d ${dir_tmp} update_qinglong_static else - echo -e "更新青龙源文件失败,请检查网络...\n" + t '更新青龙源文件失败,请检查网络...\n' fi } @@ -294,30 +294,30 @@ update_qinglong_static() { exit_status=$? if [[ $exit_status -eq 0 ]]; then - echo -e "更新青龙静态资源成功...\n" + t '更新青龙静态资源成功...\n' unzip -oq ${dir_tmp}/static.zip -d ${dir_tmp} check_update_dep else - echo -e "更新青龙静态资源失败,请检查网络...\n" + t '更新青龙静态资源失败,请检查网络...\n' fi } check_update_dep() { - echo -e "\n开始检测依赖...\n" + t '\n开始检测依赖...\n' if [[ $(diff $dir_root/package.json ${dir_tmp}/qinglong-${primary_branch}/package.json) ]]; then npm_install_2 "${dir_tmp}/qinglong-${primary_branch}" fi if [[ $exit_status -eq 0 ]]; then - echo -e "\n依赖检测安装成功...\n" - echo -e "更新包下载成功..." + t '\n依赖检测安装成功...\n' + t '更新包下载成功...\n' if [[ "$needRestart" == 'true' ]]; then reload_qinglong "system" fi else - echo -e "\n依赖检测安装失败,请检查网络...\n" + t '\n依赖检测安装失败,请检查网络...\n' fi } @@ -514,7 +514,7 @@ main() { if [[ -n $p2 ]]; then update_repo "$p2" "$p3" "$p4" "$p5" "$p6" "$p7" "$p8" "$p9" "$p10" else - eval echo -e "命令输入错误...\\\n" $cmd + t '命令输入错误...\n' eval usage $cmd fi ;; @@ -523,7 +523,7 @@ main() { if [[ -n $p2 ]]; then update_raw "$p2" "$p3" "$p4" "$p5" else - eval echo -e "命令输入错误...\\\n" $cmd + t '命令输入错误...\n' eval usage $cmd fi ;; @@ -549,7 +549,7 @@ main() { eval update_auth_config "\\\"username\\\":\\\"$p2\\\"" "重置用户名" $cmd ;; *) - eval echo -e "命令输入错误...\\\n" $cmd + t '命令输入错误...\n' eval usage $cmd ;; esac diff --git a/src/layouts/index.tsx b/src/layouts/index.tsx index abc5c759..940d5ddb 100644 --- a/src/layouts/index.tsx +++ b/src/layouts/index.tsx @@ -89,7 +89,7 @@ export default function () { if (code === 200 && data.username) { setUser(data); if (location.pathname === '/') { - history.push('/crontab'); + history.push('/dashboard'); } } needLoading && setLoading(false); @@ -218,7 +218,7 @@ export default function () { if (['/login', '/initialization', '/error'].includes(location.pathname)) { if (systemInfo?.isInitialized && location.pathname === '/initialization') { - history.push('/crontab'); + history.push('/dashboard'); } if (systemInfo || location.pathname === '/error') { diff --git a/src/pages/error/index.tsx b/src/pages/error/index.tsx index a51994fe..df8f5a12 100644 --- a/src/pages/error/index.tsx +++ b/src/pages/error/index.tsx @@ -54,7 +54,7 @@ const Error = () => { useEffect(() => { if (user && user.username) { - history.push('/crontab'); + history.push('/dashboard'); } }, [user]);