mirror of
https://github.com/whyour/qinglong.git
synced 2026-10-17 07:20:51 +08:00
fix(python): share safe startup and honor explicit virtual environments
This commit is contained in:
+108
-95
@@ -10,16 +10,61 @@ create_token() {
|
||||
}
|
||||
|
||||
get_token() {
|
||||
if [[ -f $file_auth_token ]]; then
|
||||
__ql_token__=$(cat $file_auth_token | jq -r .value)
|
||||
local expiration=$(cat $file_auth_token | jq -r .expiration)
|
||||
local currentTimeStamp=$(date +%s)
|
||||
if [[ $currentTimeStamp -ge $expiration ]]; then
|
||||
create_token
|
||||
local token_data expiration token_value
|
||||
local currentTimeStamp=${EPOCHSECONDS:-$(date +%s)}
|
||||
if [[ -f "$file_auth_token" ]] && token_data=$(jq -er \
|
||||
'select((.expiration | type) == "number" and (.value | type) == "string" and (.value | length) > 0 and (.value | test("[\r\n]") | not)) | .expiration, .value' \
|
||||
"$file_auth_token" 2>/dev/null); then
|
||||
expiration=${token_data%%$'\n'*}
|
||||
token_value=${token_data#*$'\n'}
|
||||
if [[ "$expiration" =~ ^[1-9][0-9]{0,10}$ && -n "$token_value" && "$token_value" != *$'\n'* && "$token_value" != *$'\r'* ]] && \
|
||||
(( currentTimeStamp < expiration )); then
|
||||
__ql_token__=$token_value
|
||||
return 0
|
||||
fi
|
||||
else
|
||||
create_token
|
||||
fi
|
||||
create_token
|
||||
}
|
||||
|
||||
# Check the cached token before every authenticated API request. A rejected
|
||||
# token is refreshed and retried once; other failures are never retried.
|
||||
# Arguments are curl options, including the URL, method and optional body.
|
||||
ql_api_request() {
|
||||
local api=""
|
||||
local attempt
|
||||
get_token || return $?
|
||||
for attempt in 1 2; do
|
||||
api=$(curl -s --noproxy "*" \
|
||||
-H "Authorization: Bearer ${__ql_token__}" \
|
||||
-H "Content-Type: application/json;charset=UTF-8" \
|
||||
"$@" \
|
||||
--compressed)
|
||||
if [[ "$attempt" == 2 || "$api" == '{"code":200}' ]] ||
|
||||
[[ "$(jq -r '.code' <<< "$api" 2>/dev/null)" != 401 ]]; then
|
||||
break
|
||||
fi
|
||||
create_token || return $?
|
||||
done
|
||||
printf '%s' "$api"
|
||||
}
|
||||
|
||||
# Read the status response once; preserve multiline error messages and the
|
||||
# legacy code/message variables used by the shell API callers.
|
||||
ql_parse_status_response() {
|
||||
# Both task status and statistics endpoints normally return this exact body.
|
||||
# Match the whole document; all other JSON and malformed responses still
|
||||
# use jq. Keep its missing-message result for existing callers.
|
||||
if [[ "$1" == '{"code":200}' ]]; then
|
||||
code=200
|
||||
message=null
|
||||
return 0
|
||||
fi
|
||||
local parsed
|
||||
code=""
|
||||
message=""
|
||||
parsed=$(jq -r '.code, .message' <<< "$1") || return $?
|
||||
code=${parsed%%$'\n'*}
|
||||
message=${parsed#*$'\n'}
|
||||
}
|
||||
|
||||
add_cron_api() {
|
||||
@@ -40,19 +85,16 @@ add_cron_api() {
|
||||
sub_id="null"
|
||||
fi
|
||||
|
||||
local api=$(
|
||||
curl -s --noproxy "*" "http://localhost:${ql_port}/open/crons?t=$currentTimeStamp" \
|
||||
-H "Authorization: Bearer ${__ql_token__}" \
|
||||
-H "Content-Type: application/json;charset=UTF-8" \
|
||||
--data-raw "{\"name\":\"${name//\"/\\\"}\",\"command\":\"${command//\"/\\\"}\",\"schedule\":\"$schedule\",\"sub_id\":$sub_id}" \
|
||||
--compressed
|
||||
)
|
||||
local api=$(ql_api_request \
|
||||
"http://localhost:${ql_port}/open/crons?t=$currentTimeStamp" \
|
||||
--data-raw "{\"name\":\"${name//\"/\\\"}\",\"command\":\"${command//\"/\\\"}\",\"schedule\":\"$schedule\",\"sub_id\":$sub_id}" \
|
||||
-X POST)
|
||||
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
|
||||
}
|
||||
|
||||
@@ -70,20 +112,16 @@ update_cron_api() {
|
||||
local id="$4"
|
||||
fi
|
||||
|
||||
local api=$(
|
||||
curl -s --noproxy "*" "http://localhost:${ql_port}/open/crons?t=$currentTimeStamp" \
|
||||
-X 'PUT' \
|
||||
-H "Authorization: Bearer ${__ql_token__}" \
|
||||
-H "Content-Type: application/json;charset=UTF-8" \
|
||||
--data-raw "{\"name\":\"${name//\"/\\\"}\",\"command\":\"${command//\"/\\\"}\",\"schedule\":\"$schedule\",\"id\":\"$id\"}" \
|
||||
--compressed
|
||||
)
|
||||
local api=$(ql_api_request \
|
||||
"http://localhost:${ql_port}/open/crons?t=$currentTimeStamp" \
|
||||
-X PUT \
|
||||
--data-raw "{\"name\":\"${name//\"/\\\"}\",\"command\":\"${command//\"/\\\"}\",\"schedule\":\"$schedule\",\"id\":\"$id\"}")
|
||||
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
|
||||
}
|
||||
|
||||
@@ -97,40 +135,32 @@ update_cron_command_api() {
|
||||
local id="$2"
|
||||
fi
|
||||
|
||||
local api=$(
|
||||
curl -s --noproxy "*" "http://localhost:${ql_port}/open/crons?t=$currentTimeStamp" \
|
||||
-X 'PUT' \
|
||||
-H "Authorization: Bearer ${__ql_token__}" \
|
||||
-H "Content-Type: application/json;charset=UTF-8" \
|
||||
--data-raw "{\"command\":\"${command//\"/\\\"}\",\"id\":\"$id\"}" \
|
||||
--compressed
|
||||
)
|
||||
local api=$(ql_api_request \
|
||||
"http://localhost:${ql_port}/open/crons?t=$currentTimeStamp" \
|
||||
-X PUT \
|
||||
--data-raw "{\"command\":\"${command//\"/\\\"}\",\"id\":\"$id\"}")
|
||||
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
|
||||
}
|
||||
|
||||
del_cron_api() {
|
||||
local ids="$1"
|
||||
local currentTimeStamp=$(date +%s)
|
||||
local api=$(
|
||||
curl -s --noproxy "*" "http://localhost:${ql_port}/open/crons?t=$currentTimeStamp" \
|
||||
-X 'DELETE' \
|
||||
-H "Authorization: Bearer ${__ql_token__}" \
|
||||
-H "Content-Type: application/json;charset=UTF-8" \
|
||||
--data-raw "[$ids]" \
|
||||
--compressed
|
||||
)
|
||||
local api=$(ql_api_request \
|
||||
"http://localhost:${ql_port}/open/crons?t=$currentTimeStamp" \
|
||||
-X DELETE \
|
||||
--data-raw "[$ids]")
|
||||
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
|
||||
}
|
||||
|
||||
@@ -141,17 +171,18 @@ update_cron() {
|
||||
local logPath="$4"
|
||||
local lastExecutingTime="${5:-0}"
|
||||
local runningTime="${6:-0}"
|
||||
local currentTimeStamp=$(date +%s)
|
||||
local api=$(
|
||||
curl -s --noproxy "*" "http://localhost:${ql_port}/open/crons/status?t=$currentTimeStamp" \
|
||||
-X 'PUT' \
|
||||
-H "Authorization: Bearer ${__ql_token__}" \
|
||||
-H "Content-Type: application/json;charset=UTF-8" \
|
||||
--data-raw "{\"ids\":[$ids],\"status\":\"$status\",\"pid\":\"$pid\",\"log_path\":\"$logPath\",\"last_execution_time\":$lastExecutingTime,\"last_running_time\":$runningTime}" \
|
||||
--compressed
|
||||
)
|
||||
code=$(echo "$api" | jq -r .code)
|
||||
message=$(echo "$api" | jq -r .message)
|
||||
local exitCode="${7:-}"
|
||||
local currentTimeStamp=${EPOCHSECONDS:-$(date +%s)}
|
||||
local dataRaw="{\"ids\":[$ids],\"status\":\"$status\",\"pid\":\"$pid\",\"log_path\":\"$logPath\",\"last_execution_time\":$lastExecutingTime,\"last_running_time\":$runningTime"
|
||||
if [[ -n $exitCode ]]; then
|
||||
dataRaw="${dataRaw},\"exit_code\":$exitCode"
|
||||
fi
|
||||
dataRaw="${dataRaw}}"
|
||||
local api=$(ql_api_request \
|
||||
"http://localhost:${ql_port}/open/crons/status?t=$currentTimeStamp" \
|
||||
-X PUT \
|
||||
--data-raw "$dataRaw")
|
||||
ql_parse_status_response "$api" || true
|
||||
if [[ $code != 200 ]]; then
|
||||
if [[ ! $message ]]; then
|
||||
message="$api"
|
||||
@@ -164,32 +195,25 @@ notify_api() {
|
||||
local title="$1"
|
||||
local content="$2"
|
||||
local currentTimeStamp=$(date +%s)
|
||||
local api=$(
|
||||
curl -s --noproxy "*" "http://localhost:${ql_port}/open/system/notify?t=$currentTimeStamp" \
|
||||
-X 'PUT' \
|
||||
-H "Authorization: Bearer ${__ql_token__}" \
|
||||
-H "Content-Type: application/json;charset=UTF-8" \
|
||||
--data-raw "{\"title\":\"${title//\"/\\\"}\",\"content\":\"${content//\"/\\\"}\"}" \
|
||||
--compressed
|
||||
)
|
||||
local api=$(ql_api_request \
|
||||
"http://localhost:${ql_port}/open/system/notify?t=$currentTimeStamp" \
|
||||
-X PUT \
|
||||
--data-raw "{\"title\":\"${title//\"/\\\"}\",\"content\":\"${content//\"/\\\"}\"}")
|
||||
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
|
||||
}
|
||||
|
||||
find_cron_api() {
|
||||
local params="$1"
|
||||
local currentTimeStamp=$(date +%s)
|
||||
local api=$(
|
||||
curl -s --noproxy "*" "http://localhost:${ql_port}/open/crons/detail?$params&t=$currentTimeStamp" \
|
||||
-H "Authorization: Bearer ${__ql_token__}" \
|
||||
-H "Content-Type: application/json;charset=UTF-8" \
|
||||
--compressed
|
||||
)
|
||||
local api=$(ql_api_request \
|
||||
"http://localhost:${ql_port}/open/crons/detail?$params&t=$currentTimeStamp" \
|
||||
-X GET)
|
||||
data=$(echo "$api" | jq -r .data)
|
||||
if [[ $data == 'null' ]]; then
|
||||
echo -e ""
|
||||
@@ -203,20 +227,16 @@ update_auth_config() {
|
||||
local body="$1"
|
||||
local tip="$2"
|
||||
local currentTimeStamp=$(date +%s)
|
||||
local api=$(
|
||||
curl -s --noproxy "*" "http://localhost:${ql_port}/open/system/auth/reset?t=$currentTimeStamp" \
|
||||
-X 'PUT' \
|
||||
-H "Authorization: Bearer ${__ql_token__}" \
|
||||
-H "Content-Type: application/json;charset=UTF-8" \
|
||||
--data-raw "{$body}" \
|
||||
--compressed
|
||||
)
|
||||
local api=$(ql_api_request \
|
||||
"http://localhost:${ql_port}/open/system/auth/reset?t=$currentTimeStamp" \
|
||||
-X PUT \
|
||||
--data-raw "{$body}")
|
||||
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
|
||||
}
|
||||
|
||||
@@ -226,16 +246,11 @@ record_cron_stat() {
|
||||
local elapsed="${3:-0}"
|
||||
[[ $ref_id ]] && [[ $ref_id -gt 0 ]] 2>/dev/null || return
|
||||
|
||||
local api=$(
|
||||
curl -s --noproxy "*" "http://localhost:${ql_port:-5700}/open/dashboard/record" \
|
||||
local api=$(ql_api_request \
|
||||
"http://localhost:${ql_port:-5700}/open/dashboard/record" \
|
||||
-X POST \
|
||||
-H "Authorization: Bearer ${__ql_token__}" \
|
||||
-H "Content-Type: application/json;charset=UTF-8" \
|
||||
--data-raw "{\"ref_id\":$ref_id,\"code\":$exit_code,\"elapsed\":$elapsed}" \
|
||||
--compressed
|
||||
)
|
||||
code=$(echo "$api" | jq -r .code)
|
||||
message=$(echo "$api" | jq -r .message)
|
||||
--data-raw "{\"ref_id\":$ref_id,\"code\":$exit_code,\"elapsed\":$elapsed}")
|
||||
ql_parse_status_response "$api" || true
|
||||
if [[ $code != 200 ]]; then
|
||||
if [[ ! $message ]]; then
|
||||
message="$api"
|
||||
@@ -243,5 +258,3 @@ record_cron_stat() {
|
||||
echo -e "${message}"
|
||||
fi
|
||||
}
|
||||
|
||||
get_token
|
||||
|
||||
+9
-9
@@ -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'
|
||||
|
||||
+76
-34
@@ -1,70 +1,112 @@
|
||||
#!/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日志"
|
||||
local panelOut="/root/.pm2/logs/qinglong-out.log"
|
||||
local panelError="/root/.pm2/logs/qinglong-error.log"
|
||||
tail -n 300 "$panelOut"
|
||||
tail -n 300 "$panelError"
|
||||
t '---> 服务诊断信息'
|
||||
pm2 status || true
|
||||
|
||||
local systemLogDir="$dir_data/syslog"
|
||||
local latestSystemLog
|
||||
latestSystemLog=$(find "$systemLogDir" -maxdepth 1 -type f -name '*.log' 2>/dev/null | sort | tail -n 1)
|
||||
if [[ -n "$latestSystemLog" ]]; then
|
||||
t '---> 最近的系统日志: %s' "$latestSystemLog"
|
||||
tail -n 300 "$latestSystemLog"
|
||||
else
|
||||
t '---> 未找到系统日志: %s' "$systemLogDir"
|
||||
fi
|
||||
|
||||
if [[ "$QL_CONTAINER" == "true" ]]; then
|
||||
t '---> 容器内 PM2 日志不落盘;早期启动错误请执行 docker logs --tail 300 <容器名>'
|
||||
return
|
||||
fi
|
||||
|
||||
local pm2Home="${PM2_HOME:-$HOME/.pm2}"
|
||||
local panelOut="$pm2Home/logs/qinglong-out.log"
|
||||
local panelError="$pm2Home/logs/qinglong-error.log"
|
||||
[[ -f "$panelOut" ]] && tail -n 300 "$panelOut"
|
||||
[[ -f "$panelError" ]] && tail -n 300 "$panelError"
|
||||
}
|
||||
|
||||
check_ql() {
|
||||
local api=$(curl -s --noproxy "*" "http://localhost:${ql_port}")
|
||||
echo -e "\n=====> 检测面板\n\n$api\n"
|
||||
local basePath="${ql_base_url%/}"
|
||||
local api
|
||||
local attempt
|
||||
for ((attempt = 1; attempt <= 10; attempt++)); do
|
||||
api=$(curl -s --max-time 2 --noproxy "*" "http://localhost:${ql_port}${basePath}/")
|
||||
[[ $api =~ "<div id=\"root\"></div>" ]] && break
|
||||
sleep 1
|
||||
done
|
||||
t '\n=====> 检测面板'
|
||||
echo -e "\n\n$api\n"
|
||||
if [[ $api =~ "<div id=\"root\"></div>" ]]; then
|
||||
echo -e "=====> 面板服务启动正常\n"
|
||||
t '=====> 面板服务启动正常\n'
|
||||
else
|
||||
t '=====> 面板服务启动异常,请检查上方诊断信息\n'
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_pm2() {
|
||||
pm2_log
|
||||
local currentTimeStamp=$(date +%s)
|
||||
local api=$(
|
||||
curl -s --noproxy "*" "http://localhost:${ql_port}/api/system?t=$currentTimeStamp" \
|
||||
-H 'Accept: */*' \
|
||||
-H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36' \
|
||||
-H "Referer: http://localhost:${ql_port}/crontab" \
|
||||
-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"
|
||||
if [[ $api =~ "{\"code\"" ]]; then
|
||||
echo -e "=====> 后台服务启动正常\n"
|
||||
local basePath="${ql_base_url%/}"
|
||||
local api
|
||||
local attempt
|
||||
for ((attempt = 1; attempt <= 10; attempt++)); do
|
||||
api=$(
|
||||
curl -s --max-time 2 --noproxy "*" "http://localhost:${ql_port}${basePath}/api/health?t=$currentTimeStamp" \
|
||||
-H 'Accept: */*' \
|
||||
-H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36' \
|
||||
-H "Referer: http://localhost:${ql_port}/crontab" \
|
||||
-H 'Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7' \
|
||||
--compressed
|
||||
)
|
||||
[[ $api == *'"code":200'* && $api == *'"status":"ok"'* ]] && break
|
||||
sleep 1
|
||||
done
|
||||
t '\n=====> 检测后台'
|
||||
echo -e "\n\n$api\n"
|
||||
if [[ $api == *'"code":200'* && $api == *'"status":"ok"'* ]]; then
|
||||
t '=====> 后台服务启动正常\n'
|
||||
else
|
||||
pm2_log
|
||||
t '=====> 后台服务启动异常,请检查上方诊断信息\n'
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
echo -e "=====> 开始检测"
|
||||
npm i -g pnpm@8.3.1 pm2 ts-node
|
||||
t '=====> 开始检测'
|
||||
npm i -g pnpm@8.3.1 pm2 ts-node typescript@5
|
||||
|
||||
reset_env
|
||||
copy_dep
|
||||
check_ql
|
||||
check_pm2
|
||||
reload_pm2
|
||||
echo -e "\n=====> 检测结束\n"
|
||||
local checkStatus=0
|
||||
check_ql || checkStatus=1
|
||||
check_pm2 || checkStatus=1
|
||||
t '\n=====> 检测结束\n'
|
||||
return $checkStatus
|
||||
}
|
||||
|
||||
main
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# 开发模式下设置 Python venv 环境变量
|
||||
# 用法: source shell/dev-env.sh(需在项目根目录执行)
|
||||
|
||||
export PYTHON_VENV_DIR="${PYTHON_VENV_DIR:-${PWD}/.venv}"
|
||||
|
||||
if [[ -f "${PYTHON_VENV_DIR}/bin/python3" ]]; then
|
||||
# 仅将 venv 的 bin 加入 PATH,Python 的 venv 机制自动处理包路径
|
||||
export PATH="${PYTHON_VENV_DIR}/bin:${PATH}"
|
||||
export PIP_CACHE_DIR="${PYTHON_VENV_DIR}/pip"
|
||||
mkdir -p "${PIP_CACHE_DIR}"
|
||||
fi
|
||||
@@ -0,0 +1,111 @@
|
||||
# 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'
|
||||
['---> 服务诊断信息']='---> Service diagnostics'
|
||||
['---> 最近的系统日志: %s']='---> Recent system log: %s'
|
||||
['---> 未找到系统日志: %s']='---> No system log found: %s'
|
||||
['---> 容器内 PM2 日志不落盘;早期启动错误请执行 docker logs --tail 300 <容器名>']='---> PM2 logs are not persisted in containers; run docker logs --tail 300 <container> for early startup errors'
|
||||
['\n=====> 检测面板']='\n=====> Checking panel'
|
||||
['=====> 面板服务启动正常\n']='=====> Panel service running normally\n'
|
||||
['=====> 面板服务启动异常,请检查上方诊断信息\n']='=====> Panel failed to start; check the diagnostics above\n'
|
||||
['\n=====> 检测后台']='\n=====> Checking backend'
|
||||
['=====> 后台服务启动正常\n']='=====> Backend service running normally\n'
|
||||
['=====> 后台服务启动异常,请检查上方诊断信息\n']='=====> Backend failed to start; check the diagnostics above\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'
|
||||
)
|
||||
@@ -0,0 +1,111 @@
|
||||
# 中文语言包(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日志'
|
||||
['---> 服务诊断信息']='---> 服务诊断信息'
|
||||
['---> 最近的系统日志: %s']='---> 最近的系统日志: %s'
|
||||
['---> 未找到系统日志: %s']='---> 未找到系统日志: %s'
|
||||
['---> 容器内 PM2 日志不落盘;早期启动错误请执行 docker logs --tail 300 <容器名>']='---> 容器内 PM2 日志不落盘;早期启动错误请执行 docker logs --tail 300 <容器名>'
|
||||
['\n=====> 检测面板']='\n=====> 检测面板'
|
||||
['=====> 面板服务启动正常\n']='=====> 面板服务启动正常\n'
|
||||
['=====> 面板服务启动异常,请检查上方诊断信息\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'
|
||||
)
|
||||
@@ -0,0 +1,136 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Cache discovery only, never the installed packages. Ordinary installs at the
|
||||
# same root are visible immediately. TTL bounds changes outside known inputs.
|
||||
ql_node_path_cache_key() {
|
||||
local pnpm_bin node_bin name directory file
|
||||
pnpm_bin=$(type -P pnpm) || return 1
|
||||
node_bin=$(type -P node) || return 1
|
||||
{
|
||||
printf '%s\n' 'v1' "$EUID" "$PWD" "$PATH" "${HOME:-}" \
|
||||
"${PNPM_HOME:-}" "${XDG_CONFIG_HOME:-}" "${XDG_DATA_HOME:-}" \
|
||||
"$pnpm_bin" "$node_bin"
|
||||
for name in ${!npm_config_@} ${!NPM_CONFIG_@}; do
|
||||
printf '%s=%s\n' "$name" "${!name}"
|
||||
done
|
||||
# Follow executable symlinks, detecting upgrades at the same command path.
|
||||
stat -Lc '%n:%i:%s:%Y:%Z' "$pnpm_bin" "$node_bin" 2>/dev/null || \
|
||||
stat -Lf '%N:%i:%z:%m:%c' "$pnpm_bin" "$node_bin" 2>/dev/null
|
||||
directory=$PWD
|
||||
while :; do
|
||||
for file in "$directory/.npmrc" "$directory/pnpm-workspace.yaml"; do
|
||||
if [[ -f "$file" ]]; then
|
||||
printf '%s\n' "$file"
|
||||
cat -- "$file"
|
||||
printf '\n'
|
||||
fi
|
||||
done
|
||||
[[ "$directory" == / ]] && break
|
||||
directory=${directory%/*}
|
||||
[[ -n "$directory" ]] || directory=/
|
||||
done
|
||||
for file in "${HOME:-}/.npmrc" \
|
||||
"${XDG_CONFIG_HOME:-${HOME:-}/.config}/pnpm/rc" \
|
||||
"${npm_config_userconfig:-${NPM_CONFIG_USERCONFIG:-/dev/null}}" \
|
||||
"${npm_config_globalconfig:-${NPM_CONFIG_GLOBALCONFIG:-/dev/null}}"; do
|
||||
if [[ -f "$file" ]]; then
|
||||
printf '%s\n' "$file"
|
||||
cat -- "$file"
|
||||
printf '\n'
|
||||
fi
|
||||
done
|
||||
} | cksum
|
||||
}
|
||||
|
||||
ql_read_node_path_cache() {
|
||||
local cache="$1" key="$2" now
|
||||
local stored_key="" stored_at="" stored_path=""
|
||||
now=${EPOCHSECONDS:-$(date +%s)}
|
||||
if [[ -f "$cache" && ! -L "$cache" && -O "$cache" ]]; then
|
||||
if ! {
|
||||
IFS= read -r stored_key && IFS= read -r stored_at && IFS= read -r stored_path
|
||||
} < "$cache"; then
|
||||
return 1
|
||||
fi
|
||||
if [[ "$stored_key" == "$key" && "$stored_at" =~ ^(0|[1-9][0-9]{0,10})$ && "$stored_path" == /* ]] && \
|
||||
(( now >= stored_at && now - stored_at < 60 )); then
|
||||
printf '%s\n' "$stored_path"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# Keep the lock descriptor in a subshell so sourcing this helper never changes
|
||||
# the caller's descriptors. A crashed refresher releases the kernel lock.
|
||||
ql_refresh_node_global_path() (
|
||||
local cache="$1" key="$2" now result previous_umask
|
||||
# Keep one persistent lock per owner. Removing per-key lock files could
|
||||
# split concurrent waiters across different inodes during cache cleanup.
|
||||
local lock="${dir_tmp}/pnpm-root-${EUID}.lock"
|
||||
previous_umask=$(umask)
|
||||
umask 077
|
||||
if type -P flock &>/dev/null && mkdir -p -- "$dir_tmp" 2>/dev/null; then
|
||||
if [[ ! -L "$lock" && ( ! -e "$lock" || ( -f "$lock" && -O "$lock" ) ) ]] && \
|
||||
{ exec 9>> "$lock"; } 2>/dev/null; then
|
||||
# BusyBox flock (Alpine) has no -w. Both implementations support -n;
|
||||
# bound contention retries and fall back immediately on other failures.
|
||||
# Never remove the lock file while waiters exist.
|
||||
local lock_attempt=0 lock_status
|
||||
while :; do
|
||||
if flock -n 9 2>/dev/null; then
|
||||
if ql_read_node_path_cache "$cache" "$key"; then
|
||||
return 0
|
||||
fi
|
||||
break
|
||||
else
|
||||
lock_status=$?
|
||||
fi
|
||||
[[ "$lock_status" == 1 && "$lock_attempt" -lt 20 ]] || break
|
||||
lock_attempt=$((lock_attempt + 1))
|
||||
sleep 0.1
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
# Private lock creation must not change pnpm's inherited creation mask.
|
||||
umask "$previous_umask"
|
||||
# Warm lookups never scan the directory. Retire old environment records on
|
||||
# refresh, while preserving the shared lock and unrelated cache files.
|
||||
find "$dir_tmp" -maxdepth 1 -type f -user "$EUID" \
|
||||
-name "pnpm-root-${EUID}-*.cache" -mmin +60 -delete 2>/dev/null || true
|
||||
now=${EPOCHSECONDS:-$(date +%s)}
|
||||
result=$(pnpm root -g 9>&- 2>/dev/null) || return $?
|
||||
# Never cache failed, empty, multiline or non-absolute answers.
|
||||
if [[ "$result" == /* && "$result" != *$'\n'* && "$result" != *$'\r'* ]]; then
|
||||
(
|
||||
umask 077
|
||||
mkdir -p -- "$dir_tmp" || exit 0
|
||||
local temporary
|
||||
temporary=$(mktemp "${cache}.XXXXXX") || exit 0
|
||||
if printf '%s\n%s\n%s\n' "$key" "$now" "$result" > "$temporary"; then
|
||||
mv -f -- "$temporary" "$cache" || rm -f -- "$temporary"
|
||||
else
|
||||
rm -f -- "$temporary"
|
||||
fi
|
||||
) 2>/dev/null
|
||||
fi
|
||||
printf '%s\n' "$result"
|
||||
)
|
||||
|
||||
ql_get_node_global_path() {
|
||||
if [[ "${QL_NODE_PATH_CACHE:-1}" == 0 ]]; then
|
||||
pnpm root -g 2>/dev/null
|
||||
return $?
|
||||
fi
|
||||
|
||||
local key cache
|
||||
key=$(ql_node_path_cache_key) || { pnpm root -g 2>/dev/null; return $?; }
|
||||
# Node and crond may have different working directories/configuration.
|
||||
# Preserve both discoveries instead of evicting each other on every run.
|
||||
cache="${dir_tmp}/pnpm-root-${EUID}-${key// /-}.cache"
|
||||
if ql_read_node_path_cache "$cache" "$key"; then
|
||||
return 0
|
||||
fi
|
||||
ql_refresh_node_global_path "$cache" "$key"
|
||||
}
|
||||
+39
-35
@@ -24,7 +24,13 @@ random_delay() {
|
||||
done
|
||||
|
||||
local delay_second=$(($(gen_random_num "$random_delay_max") + 1))
|
||||
echo -e "任务随机延迟 $delay_second 秒,配置文件参数 RandomDelay 置空可取消延迟 \n"
|
||||
local start_time
|
||||
if [[ $is_macos -eq 1 ]]; then
|
||||
start_time=$(date -v "+${delay_second}S" "+%Y-%m-%d %H:%M:%S")
|
||||
else
|
||||
start_time=$(date -d "+${delay_second} seconds" "+%Y-%m-%d %H:%M:%S")
|
||||
fi
|
||||
t '任务随机延迟 %s 秒,将于 %s 开始,配置文件参数 RandomDelay 置空可取消延迟\n' "$delay_second" "$start_time"
|
||||
sleep $delay_second
|
||||
fi
|
||||
}
|
||||
@@ -78,7 +84,9 @@ env_str_to_array() {
|
||||
}
|
||||
|
||||
clear_non_sh_env() {
|
||||
if [[ $file_param != *.sh ]]; then
|
||||
# Only automatic Node/Python preloads reconstruct the generated environment.
|
||||
# Explicit interpreters and other commands must keep the shell-loaded values.
|
||||
if [[ $isJsOrPythonFile == 'true' ]]; then
|
||||
clear_env
|
||||
fi
|
||||
}
|
||||
@@ -86,7 +94,16 @@ clear_non_sh_env() {
|
||||
append_node_dependency_path() {
|
||||
export PREV_NODE_PATH="${NODE_PATH:=}"
|
||||
|
||||
local pnpm_global_path=$(pnpm root -g 2>/dev/null)
|
||||
# 用户依赖目录加入 NODE_PATH,替代 symlink 到 node_modules 的方式
|
||||
export NODE_PATH="${NODE_PATH:+${NODE_PATH}:}${dir_dep}"
|
||||
|
||||
local pnpm_global_path
|
||||
if [[ -f "$dir_shell/node_path_cache.sh" ]]; then
|
||||
. "$dir_shell/node_path_cache.sh"
|
||||
pnpm_global_path=$(ql_get_node_global_path) || true
|
||||
else
|
||||
pnpm_global_path=$(pnpm root -g 2>/dev/null) || true
|
||||
fi
|
||||
if [[ -n "$pnpm_global_path" ]]; then
|
||||
export QL_NODE_GLOBAL_PATH="$pnpm_global_path"
|
||||
export NODE_PATH="${NODE_PATH:+${NODE_PATH}:}${pnpm_global_path}"
|
||||
@@ -104,31 +121,22 @@ enter_script_workdir() {
|
||||
else
|
||||
_target_dir="${dir_scripts}/${work_dir}"
|
||||
fi
|
||||
if [[ ! -d "${_target_dir}" ]]; then
|
||||
echo -e "错误:工作目录不存在 ${_target_dir}"
|
||||
exit 1
|
||||
fi
|
||||
cd "${_target_dir}" || {
|
||||
echo -e "错误:无法进入工作目录 ${_target_dir}"
|
||||
exit 1
|
||||
}
|
||||
# 仍然处理 file_param 中的路径前缀,确保命令路径正确
|
||||
if [[ ${file_param} =~ "/" ]]; then
|
||||
local script_name="${file_param##*/}"
|
||||
if [[ "${use_dot_prefix}" == "true" ]]; then
|
||||
file_param="./${script_name}"
|
||||
else
|
||||
file_param="${script_name}"
|
||||
if [[ -d "${_target_dir}" ]]; then
|
||||
cd "${_target_dir}"
|
||||
if [[ ${file_param} =~ "/" ]]; then
|
||||
local script_name="${file_param##*/}"
|
||||
if [[ "${use_dot_prefix}" == "true" ]]; then
|
||||
file_param="./${script_name}"
|
||||
else
|
||||
file_param="${script_name}"
|
||||
fi
|
||||
fi
|
||||
return
|
||||
fi
|
||||
return
|
||||
t '警告:工作目录不存在 %s' "${_target_dir}"
|
||||
fi
|
||||
|
||||
# 自动检测:从 file_param 中提取脚本所在目录
|
||||
cd $dir_scripts || {
|
||||
echo -e "错误:无法进入 scripts 目录 ${dir_scripts}"
|
||||
exit 1
|
||||
}
|
||||
cd $dir_scripts
|
||||
if [[ ${file_param} =~ "/" ]]; then
|
||||
local script_dir="${file_param%/*}"
|
||||
local script_name="${file_param##*/}"
|
||||
@@ -156,7 +164,7 @@ run_normal() {
|
||||
if [[ $isJsOrPythonFile == 'false' ]]; then
|
||||
clear_non_sh_env
|
||||
fi
|
||||
$timeoutCmd $which_program $file_param "${script_params[@]}"
|
||||
run_task_command $which_program $file_param "${script_params[@]}"
|
||||
}
|
||||
|
||||
handle_env_split() {
|
||||
@@ -176,7 +184,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
|
||||
|
||||
@@ -195,7 +203,7 @@ run_concurrent() {
|
||||
export "${env_param}=${array[$i - 1]}"
|
||||
clear_non_sh_env
|
||||
fi
|
||||
eval envParam="${env_param}" numParam="${i}" $timeoutCmd $which_program $file_param "${script_params[@]}" &>$single_log_path &
|
||||
eval envParam="${env_param}" numParam="${i}" run_task_command $which_program $file_param "${script_params[@]}" &>$single_log_path &
|
||||
done
|
||||
|
||||
wait
|
||||
@@ -213,7 +221,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
|
||||
|
||||
@@ -235,7 +243,7 @@ run_designated() {
|
||||
|
||||
enter_script_workdir
|
||||
|
||||
envParam="${env_param}" numParam="${num_param}" $timeoutCmd $which_program $file_param "${script_params[@]}"
|
||||
envParam="${env_param}" numParam="${num_param}" run_task_command $which_program $file_param "${script_params[@]}"
|
||||
}
|
||||
|
||||
## 运行其他命令
|
||||
@@ -291,7 +299,7 @@ run_else() {
|
||||
fi
|
||||
|
||||
clear_non_sh_env
|
||||
$timeoutCmd $which_program $file_param "$@"
|
||||
run_task_command $which_program $file_param "$@"
|
||||
}
|
||||
|
||||
check_file() {
|
||||
@@ -328,10 +336,6 @@ check_nounset() {
|
||||
|
||||
main() {
|
||||
if [[ $1 == *.js ]] || [[ $1 == *.mjs ]] || [[ $1 == *.py ]] || [[ $1 == *.pyc ]] || [[ $1 == *.sh ]] || [[ $1 == *.ts ]]; then
|
||||
if [[ $1 == *.sh ]]; then
|
||||
timeoutCmd=""
|
||||
fi
|
||||
|
||||
case $# in
|
||||
1)
|
||||
run_normal "$1"
|
||||
@@ -382,4 +386,4 @@ if [[ $isJsOrPythonFile == 'true' ]]; then
|
||||
fi
|
||||
run_task_after "${task_shell_params[@]}"
|
||||
clear_env
|
||||
handle_task_end "${task_shell_params[@]}" "$_task_exit_code"
|
||||
handle_task_end "${task_shell_params[@]}"
|
||||
|
||||
+19
-1
@@ -1,8 +1,26 @@
|
||||
const grpc = require('@grpc/grpc-js');
|
||||
const protoLoader = require('@grpc/proto-loader');
|
||||
const { readFileSync } = require('fs');
|
||||
const { join } = require('path');
|
||||
|
||||
class GrpcClient {
|
||||
static #certDir = join(
|
||||
process.env.QL_DATA_DIR || join(process.env.QL_DIR, 'data'),
|
||||
'config/grpc',
|
||||
);
|
||||
|
||||
static #loadTlsCredentials() {
|
||||
try {
|
||||
return grpc.credentials.createSsl(
|
||||
readFileSync(join(GrpcClient.#certDir, 'ca.crt')),
|
||||
readFileSync(join(GrpcClient.#certDir, 'client.key')),
|
||||
readFileSync(join(GrpcClient.#certDir, 'client.crt')),
|
||||
);
|
||||
} catch {
|
||||
return grpc.credentials.createInsecure();
|
||||
}
|
||||
}
|
||||
|
||||
static #config = {
|
||||
protoPath: join(process.env.QL_DIR, 'back/protos/api.proto'),
|
||||
serverAddress: `localhost:${process.env.GRPC_PORT || '5500'}`,
|
||||
@@ -58,7 +76,7 @@ class GrpcClient {
|
||||
|
||||
this.#client = new apiProto.Api(
|
||||
serverAddress,
|
||||
grpc.credentials.createInsecure(),
|
||||
GrpcClient.#loadTlsCredentials(),
|
||||
grpcOptions,
|
||||
);
|
||||
} catch (error) {
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { existsSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
|
||||
const builtinModules = [
|
||||
'assert', 'buffer', 'child_process', 'cluster', 'crypto', 'dgram', 'dns',
|
||||
'events', 'fs', 'http', 'https', 'net', 'os', 'path', 'process', 'querystring',
|
||||
'readline', 'stream', 'string_decoder', 'timers', 'tls', 'tty', 'url', 'util',
|
||||
'v8', 'zlib',
|
||||
];
|
||||
|
||||
function isBareSpecifier(specifier) {
|
||||
return !specifier.startsWith('.') &&
|
||||
!specifier.startsWith('/') &&
|
||||
!specifier.startsWith('file:') &&
|
||||
!specifier.startsWith('node:') &&
|
||||
!builtinModules.includes(specifier) &&
|
||||
!builtinModules.includes(specifier.split('/')[0]);
|
||||
}
|
||||
|
||||
export function resolve(specifier, context, nextResolve) {
|
||||
if (!isBareSpecifier(specifier)) {
|
||||
return nextResolve(specifier, context);
|
||||
}
|
||||
|
||||
// 解析优先级:全局 pnpm > 系统全局
|
||||
const bases = [
|
||||
process.env.QL_NODE_GLOBAL_PATH,
|
||||
'/usr/local/lib/node_modules',
|
||||
].filter(Boolean);
|
||||
|
||||
for (const base of bases) {
|
||||
if (existsSync(join(base, specifier))) {
|
||||
return nextResolve(specifier, {
|
||||
...context,
|
||||
parentURL: new URL(`${join(base, specifier)}/`, 'file://').href,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return nextResolve(specifier, context);
|
||||
}
|
||||
@@ -4,6 +4,11 @@ const path = require('path');
|
||||
const client = require('./client.js');
|
||||
require(`./env.js`);
|
||||
|
||||
// 注册 ESM loader,使全局安装的包也可通过 import 导入
|
||||
try {
|
||||
Module.register(new URL('esm-loader.mjs', `file://${__dirname}/`).href);
|
||||
} catch (_) {}
|
||||
|
||||
function preferGlobalNodeModules() {
|
||||
const { QL_NODE_GLOBAL_PATH } = process.env;
|
||||
if (!QL_NODE_GLOBAL_PATH || Module._qlGlobalPathPatched) {
|
||||
@@ -93,7 +98,11 @@ function run() {
|
||||
const newEnvObject = JSON.parse(envStr);
|
||||
if (typeof newEnvObject === 'object' && newEnvObject !== null) {
|
||||
for (const key in newEnvObject) {
|
||||
if (Object.prototype.hasOwnProperty.call(newEnvObject, key)) {
|
||||
if (
|
||||
Object.prototype.hasOwnProperty.call(newEnvObject, key) &&
|
||||
key !== 'NODE_PATH' &&
|
||||
key !== 'QL_NODE_GLOBAL_PATH'
|
||||
) {
|
||||
process.env[key] = newEnvObject[key];
|
||||
}
|
||||
}
|
||||
@@ -150,9 +159,13 @@ try {
|
||||
|
||||
run();
|
||||
|
||||
const { sendNotify } = require('./__ql_notify__.js');
|
||||
global.QLAPI = {
|
||||
notify: sendNotify,
|
||||
notify(...args) {
|
||||
// Keep notification dependencies out of ordinary script startup.
|
||||
// require caches the module after the first notification.
|
||||
const { sendNotify } = require('./__ql_notify__.js');
|
||||
return sendNotify.apply(this, args);
|
||||
},
|
||||
...client,
|
||||
};
|
||||
} catch (error) {
|
||||
|
||||
@@ -129,10 +129,12 @@ try:
|
||||
|
||||
run()
|
||||
|
||||
from __ql_notify__ import send
|
||||
|
||||
class BaseApi(Client):
|
||||
def notify(self, *args, **kwargs):
|
||||
# Most scripts never send a notification. Python caches this
|
||||
# module after the first call, including its channel dependencies.
|
||||
from __ql_notify__ import send
|
||||
|
||||
return send(*args, **kwargs)
|
||||
|
||||
QLAPI = BaseApi()
|
||||
|
||||
+8
-8
@@ -1,26 +1,26 @@
|
||||
#!/usr/bin/env bash
|
||||
echo -e "开始发布"
|
||||
|
||||
echo -e "切换 debian 分支"
|
||||
git branch -D debian
|
||||
git checkout -b debian
|
||||
git push --set-upstream origin debian -f
|
||||
echo -e "切换master分支"
|
||||
git branch -D master
|
||||
git checkout -b master
|
||||
git push --set-upstream origin master -f
|
||||
|
||||
echo -e "更新cdn文件"
|
||||
ts-node-transpile-only sample/tool.ts
|
||||
|
||||
string=$(cat version.yaml | grep "version" | egrep "[^ ]*" -o | egrep "\d\.*")
|
||||
version="v$string"
|
||||
echo -e "当前版本$version-debian"
|
||||
echo -e "当前版本$version"
|
||||
|
||||
echo -e "删除已经存在的本地tag"
|
||||
git tag -d "$version-debian" &>/dev/null
|
||||
git tag -d "$version" &>/dev/null
|
||||
|
||||
echo -e "删除已经存在的远程tag"
|
||||
git push origin :refs/tags/$version-debian &>/dev/null
|
||||
git push origin :refs/tags/$version &>/dev/null
|
||||
|
||||
echo -e "创建新tag"
|
||||
git tag -a "$version-debian" -m "release $version-debian"
|
||||
git tag -a "$version" -m "release $version"
|
||||
|
||||
echo -e "提交tag"
|
||||
git push --tags
|
||||
|
||||
+5
-5
@@ -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
|
||||
|
||||
+66
-27
@@ -78,6 +78,16 @@ load_ql_envs() {
|
||||
|
||||
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:-""}
|
||||
@@ -91,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
|
||||
@@ -225,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
|
||||
}
|
||||
@@ -244,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"
|
||||
|
||||
@@ -277,18 +299,23 @@ 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 &
|
||||
|
||||
# Start node directly in the background. In containers, keep application
|
||||
# output on the container streams instead of duplicating Winston logs on disk.
|
||||
if [[ "$QL_CONTAINER" == "true" ]]; then
|
||||
nohup node static/build/app.js >/proc/1/fd/1 2>/proc/1/fd/2 &
|
||||
else
|
||||
nohup node static/build/app.js >$dir_log/qinglong.log 2>&1 &
|
||||
fi
|
||||
local node_pid=$!
|
||||
|
||||
echo "已使用 Node.js 直接启动服务 (PID: $node_pid)"
|
||||
echo "注意: 使用此模式时,部分 PM2 管理功能将不可用"
|
||||
|
||||
t '已使用 Node.js 直接启动服务 (PID: %s)' "$node_pid"
|
||||
t '注意: 使用此模式时,部分 PM2 管理功能将不可用'
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
@@ -341,9 +368,16 @@ format_timestamp() {
|
||||
|
||||
get_env_array() {
|
||||
exported_variables=()
|
||||
while IFS= read -r line; do
|
||||
exported_variables+=("$line")
|
||||
done < <(grep '^export ' $file_env | awk '{print $2}' | cut -d= -f1)
|
||||
# Preserve the legacy export-line/second-field rules without evaluating values.
|
||||
local export_name_program='/^export / { name = $2; sub(/=.*/, "", name); print name }'
|
||||
if [[ ${BASH_VERSINFO[0]} -ge 4 ]]; then
|
||||
builtin mapfile -t exported_variables < <(awk "$export_name_program" "$file_env")
|
||||
else
|
||||
# macOS still ships Bash 3, which does not provide mapfile.
|
||||
while IFS= read -r line; do
|
||||
exported_variables+=("$line")
|
||||
done < <(awk "$export_name_program" "$file_env")
|
||||
fi
|
||||
}
|
||||
|
||||
clear_env() {
|
||||
@@ -360,16 +394,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
|
||||
}
|
||||
|
||||
@@ -377,22 +411,27 @@ 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
|
||||
}
|
||||
|
||||
handle_task_end() {
|
||||
local etime=$(date "+$time_format")
|
||||
local end_time=$(format_time "$time_format" "$etime")
|
||||
local end_timestamp=$(format_timestamp "$time_format" "$etime")
|
||||
local etime end_time end_timestamp
|
||||
if [[ $is_macos -ne 1 && $time_format == '%Y-%m-%d %H:%M:%S' ]]; then
|
||||
IFS='|' read -r end_time end_timestamp < <(date "+$time_format|%s")
|
||||
else
|
||||
etime=$(date "+$time_format")
|
||||
end_time=$(format_time "$time_format" "$etime")
|
||||
end_timestamp=$(format_timestamp "$time_format" "$etime")
|
||||
fi
|
||||
local diff_time=$(($end_timestamp - $begin_timestamp))
|
||||
local exit_code="${@: -1}"
|
||||
local exit_code="${_task_exit_code:-0}"
|
||||
[[ "$diff_time" == 0 ]] && diff_time=1
|
||||
|
||||
if [[ $ID ]]; then
|
||||
local error=$(update_cron "\"$ID\"" "1" "$$" "$log_path" "$begin_timestamp" "$diff_time")
|
||||
local error=$(update_cron "\"$ID\"" "1" "$$" "$log_path" "$begin_timestamp" "$diff_time" "$exit_code")
|
||||
if [[ $error ]]; then
|
||||
error_message=", 状态更新失败(${error})"
|
||||
fi
|
||||
@@ -401,11 +440,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
|
||||
}
|
||||
|
||||
|
||||
+29
-55
@@ -1,68 +1,42 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# 简化版启动脚本:跳过系统依赖和 npm 全局安装
|
||||
# 前置要求:已安装 nodejs、npm/pnpm、python3、nginx、jq
|
||||
set -e
|
||||
set -x
|
||||
|
||||
if [[ ! $QL_DIR ]]; then
|
||||
echo -e "请先设置 export QL_DIR=<qinglong目录>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! $QL_DATA_DIR ]]; then
|
||||
export QL_DATA_DIR="${QL_DIR}/data"
|
||||
fi
|
||||
|
||||
if [[ $QL_DATA_DIR != */data ]]; then
|
||||
echo -e "QL_DATA_DIR 必须以 /data 结尾,例如 /ql/data"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
: "${QL_DIR:?Please set QL_DIR}"
|
||||
: "${QL_DATA_DIR:?Please set QL_DATA_DIR}"
|
||||
[[ "$QL_DIR" = /* && "$QL_DATA_DIR" = /*/data ]] || { echo "QL_DIR and QL_DATA_DIR must be absolute paths (data directory ends in /data)" >&2; exit 1; }
|
||||
command="$1"
|
||||
|
||||
# 从 .env 文件读取 PYTHON_VENV_DIR
|
||||
if [[ -z "${PYTHON_VENV_DIR:-}" ]] && [[ -f "${QL_DIR}/.env" ]]; then
|
||||
env_venv_dir=$(grep -E '^PYTHON_VENV_DIR=' "${QL_DIR}/.env" | tail -1 | cut -d'=' -f2 | xargs)
|
||||
if [[ -n "$env_venv_dir" ]]; then
|
||||
if [[ "$env_venv_dir" != /* ]]; then
|
||||
env_venv_dir="${QL_DIR}/${env_venv_dir}"
|
||||
fi
|
||||
export PYTHON_VENV_DIR="$env_venv_dir"
|
||||
export PNPM_HOME="${PNPM_HOME:-${QL_DATA_DIR}/dep_cache/node}"
|
||||
if [[ -n "${PYTHON_VENV_DIR:-}" ]]; then
|
||||
[[ "$PYTHON_VENV_DIR" = /* ]] || PYTHON_VENV_DIR="${QL_DIR}/${PYTHON_VENV_DIR}"
|
||||
export PYTHON_VENV_DIR
|
||||
if [[ ! -x "$PYTHON_VENV_DIR/bin/python3" ]]; then
|
||||
python3 -m venv "$PYTHON_VENV_DIR"
|
||||
fi
|
||||
[[ -x "$PYTHON_VENV_DIR/bin/python3" ]] || { echo "Invalid Python venv" >&2; exit 1; }
|
||||
unset PYTHON_HOME PYTHONUSERBASE PYTHONPATH
|
||||
export PATH="${PYTHON_VENV_DIR}/bin:${PATH}:${PNPM_HOME}"
|
||||
export PIP_CACHE_DIR="${PYTHON_VENV_DIR}/pip"
|
||||
else
|
||||
export PYTHON_SHORT_VERSION=$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')
|
||||
export PYTHON_HOME="${PYTHON_HOME:-${QL_DATA_DIR}/dep_cache/python3}"
|
||||
export PYTHONUSERBASE="$PYTHON_HOME"
|
||||
export PATH="${PATH}:${PNPM_HOME}:${PYTHON_HOME}/bin"
|
||||
export PIP_CACHE_DIR="${PYTHON_HOME}/pip"
|
||||
export PYTHONPATH="${PYTHON_HOME}:${PYTHON_HOME}/lib/python${PYTHON_SHORT_VERSION}:${PYTHON_HOME}/lib/python${PYTHON_SHORT_VERSION}/site-packages"
|
||||
fi
|
||||
export NODE_PATH="${NODE_PATH:-/usr/local/bin:/usr/local/lib/node_modules:${PNPM_HOME}/global/5/node_modules}"
|
||||
mkdir -p "$PIP_CACHE_DIR"
|
||||
|
||||
export PNPM_HOME=${QL_DIR}/data/dep_cache/node
|
||||
|
||||
# ===== Python venv =====
|
||||
export PYTHON_VENV_DIR="${PYTHON_VENV_DIR:-${QL_DIR}/.venv}"
|
||||
if [[ ! -f "${PYTHON_VENV_DIR}/bin/python3" ]]; then
|
||||
echo "正在创建 Python venv: ${PYTHON_VENV_DIR}"
|
||||
python3 -m venv "${PYTHON_VENV_DIR}"
|
||||
cd "${QL_DIR}"
|
||||
if [[ ! -e .env ]]; then
|
||||
# Preserve user configuration, including a file created concurrently.
|
||||
(umask 077; set -C; cat .env.example > .env)
|
||||
fi
|
||||
chmod u+x "${QL_DIR}"/shell/*.sh
|
||||
|
||||
export PATH=${PYTHON_VENV_DIR}/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PNPM_HOME}
|
||||
export NODE_PATH=/usr/local/bin:/usr/local/lib/node_modules:${PNPM_HOME}/global/5/node_modules
|
||||
export PIP_CACHE_DIR=${PYTHON_VENV_DIR}/pip
|
||||
mkdir -p "${PIP_CACHE_DIR}"
|
||||
|
||||
if [[ $command != "reload" ]]; then
|
||||
${PYTHON_VENV_DIR}/bin/pip3 install requests
|
||||
fi
|
||||
|
||||
cd ${QL_DIR}
|
||||
# 仅在 .env 不存在时从 .env.example 复制
|
||||
if [[ ! -f .env ]] && [[ -f .env.example ]]; then
|
||||
cp -f .env.example .env
|
||||
fi
|
||||
chmod 777 ${QL_DIR}/shell/*.sh
|
||||
|
||||
# 确保 task/ql 命令指向当前部署目录(覆盖 npm 全局安装可能指向旧路径的软链接)
|
||||
ln -sf ${QL_DIR}/shell/task.sh /usr/local/bin/task 2>/dev/null || true
|
||||
ln -sf ${QL_DIR}/shell/update.sh /usr/local/bin/ql 2>/dev/null || true
|
||||
|
||||
. ${QL_DIR}/shell/share.sh
|
||||
. ${QL_DIR}/shell/env.sh
|
||||
. "${QL_DIR}/shell/share.sh"
|
||||
. "${QL_DIR}/shell/env.sh"
|
||||
|
||||
log_with_style() {
|
||||
local level="$1"
|
||||
|
||||
+14
-87
@@ -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,94 +69,21 @@ 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
|
||||
|
||||
npm install -g pnpm@8.3.1 pm2 ts-node
|
||||
npm install -g pnpm@8.3.1 pm2 ts-node typescript@5
|
||||
fi
|
||||
|
||||
# 从 .env 文件读取 PYTHON_VENV_DIR(.env 是给 Node.js 用的,bash 需要手动读取)
|
||||
if [[ -z "${PYTHON_VENV_DIR:-}" ]] && [[ -f "${QL_DIR}/.env" ]]; then
|
||||
env_venv_dir=$(grep -E '^PYTHON_VENV_DIR=' "${QL_DIR}/.env" | tail -1 | cut -d'=' -f2 | xargs)
|
||||
if [[ -n "$env_venv_dir" ]]; then
|
||||
# 处理相对路径
|
||||
if [[ "$env_venv_dir" != /* ]]; then
|
||||
env_venv_dir="${QL_DIR}/${env_venv_dir}"
|
||||
fi
|
||||
export PYTHON_VENV_DIR="$env_venv_dir"
|
||||
if [[ "$command" != "reload" ]]; then
|
||||
if [[ -n "${PYTHON_VENV_DIR:-}" ]]; then
|
||||
[[ "$PYTHON_VENV_DIR" = /* ]] || export PYTHON_VENV_DIR="${QL_DIR}/${PYTHON_VENV_DIR}"
|
||||
[[ -x "$PYTHON_VENV_DIR/bin/python3" ]] || python3 -m venv "$PYTHON_VENV_DIR"
|
||||
"$PYTHON_VENV_DIR/bin/python3" -m pip install requests
|
||||
else
|
||||
pip3 install --prefix "${PYTHON_HOME:-${QL_DATA_DIR}/dep_cache/python3}" requests
|
||||
fi
|
||||
fi
|
||||
|
||||
export PNPM_HOME=${QL_DIR}/data/dep_cache/node
|
||||
|
||||
# ===== Python venv =====
|
||||
export PYTHON_VENV_DIR="${PYTHON_VENV_DIR:-${QL_DIR}/.venv}"
|
||||
if [[ ! -f "${PYTHON_VENV_DIR}/bin/python3" ]]; then
|
||||
echo "正在创建 Python venv: ${PYTHON_VENV_DIR}"
|
||||
python3 -m venv "${PYTHON_VENV_DIR}"
|
||||
fi
|
||||
|
||||
# 仅将 venv 的 bin 加入 PATH,Python 的 venv 机制自动处理包路径
|
||||
export PATH=${PYTHON_VENV_DIR}/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PNPM_HOME}
|
||||
export NODE_PATH=/usr/local/bin:/usr/local/lib/node_modules:${PNPM_HOME}/global/5/node_modules
|
||||
export PIP_CACHE_DIR=${PYTHON_VENV_DIR}/pip
|
||||
mkdir -p "${PIP_CACHE_DIR}"
|
||||
|
||||
if [[ $command != "reload" ]]; then
|
||||
${PYTHON_VENV_DIR}/bin/pip3 install requests
|
||||
fi
|
||||
|
||||
cd ${QL_DIR}
|
||||
# 仅在 .env 不存在时从 .env.example 复制
|
||||
if [[ ! -f .env ]] && [[ -f .env.example ]]; then
|
||||
cp -f .env.example .env
|
||||
fi
|
||||
chmod 777 ${QL_DIR}/shell/*.sh
|
||||
|
||||
# 确保 task/ql 命令指向当前部署目录(覆盖 npm 全局安装可能指向旧路径的软链接)
|
||||
ln -sf ${QL_DIR}/shell/task.sh /usr/local/bin/task 2>/dev/null || true
|
||||
ln -sf ${QL_DIR}/shell/update.sh /usr/local/bin/ql 2>/dev/null || true
|
||||
|
||||
. ${QL_DIR}/shell/share.sh
|
||||
. ${QL_DIR}/shell/env.sh
|
||||
|
||||
log_with_style() {
|
||||
local level="$1"
|
||||
local message="$2"
|
||||
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
||||
|
||||
printf "\n[%s] [%7s] %s\n" "${timestamp}" "${level}" "${message}"
|
||||
}
|
||||
|
||||
log_with_style "INFO" "🚀 1. 检测配置文件..."
|
||||
import_config "$@"
|
||||
make_dir /etc/nginx/conf.d
|
||||
make_dir /run/nginx
|
||||
fix_config
|
||||
|
||||
pm2 l &>/dev/null
|
||||
|
||||
log_with_style "INFO" "🔄 2. 启动 nginx..."
|
||||
nginx -s reload 2>/dev/null || nginx -c /etc/nginx/nginx.conf
|
||||
|
||||
log_with_style "INFO" "⚙️ 3. 启动 pm2 服务..."
|
||||
reload_pm2
|
||||
|
||||
if [[ $command != "reload" ]]; then
|
||||
if [[ $AutoStartBot == true ]]; then
|
||||
log_with_style "INFO" "🤖 4. 启动 bot..."
|
||||
nohup ql bot >$dir_log/bot.log 2>&1 &
|
||||
fi
|
||||
|
||||
if [[ $EnableExtraShell == true ]]; then
|
||||
log_with_style "INFO" "🛠️ 5. 执行自定义脚本..."
|
||||
nohup ql extra >$dir_log/extra.log 2>&1 &
|
||||
fi
|
||||
|
||||
pm2 startup
|
||||
pm2 save
|
||||
fi
|
||||
|
||||
log_with_style "SUCCESS" "🎉 启动成功!"
|
||||
exec /bin/bash "${QL_DIR}/shell/start-simplify.sh" "$@"
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Fork from the current Bash state: an external `bash -c` would lose private
|
||||
# before-hook variables, arrays and functions used by sourced Shell scripts.
|
||||
run_task_command() {
|
||||
local timeout_seconds
|
||||
if [[ -z ${command_timeout_time:-} ]]; then
|
||||
"$@"
|
||||
return $?
|
||||
fi
|
||||
timeout_seconds=$(awk -v value="$command_timeout_time" 'BEGIN {
|
||||
if (value !~ /^([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[smhd]?$/) exit 1
|
||||
unit = substr(value, length(value), 1)
|
||||
factor = unit == "d" ? 86400 : unit == "h" ? 3600 : unit == "m" ? 60 : 1
|
||||
printf "%.9f", (value + 0) * factor
|
||||
}') || { printf 'Invalid task timeout: %s\n' "$command_timeout_time" >&2; return 125; }
|
||||
if [[ $timeout_seconds == '0.000000000' ]]; then
|
||||
"$@"
|
||||
return $?
|
||||
fi
|
||||
(
|
||||
timeout_dir=$(mktemp -d "${TMPDIR:-/tmp}/ql-task-timeout.XXXXXXXX") || exit 125
|
||||
# Both children get private process groups. The timer and the wrapper must
|
||||
# survive terminating the task, and descendants must not outlive a timeout.
|
||||
set -m
|
||||
( set +m; "$@" ) <&0 &
|
||||
timeout_worker=$!
|
||||
(
|
||||
set +m
|
||||
: > "$timeout_dir/ready"
|
||||
sleep "$timeout_seconds" || exit 125
|
||||
: > "$timeout_dir/expired"
|
||||
kill -TERM -- "-$timeout_worker" 2>/dev/null || :
|
||||
sleep 0.5
|
||||
kill -KILL -- "-$timeout_worker" 2>/dev/null || :
|
||||
) &
|
||||
timeout_watcher=$!
|
||||
set +m
|
||||
trap 'kill -KILL -- "-$timeout_watcher" 2>/dev/null || :
|
||||
if kill -TERM -- "-$timeout_worker" 2>/dev/null; then sleep 0.1; fi
|
||||
kill -KILL -- "-$timeout_worker" 2>/dev/null || :
|
||||
wait "$timeout_worker" 2>/dev/null || :
|
||||
wait "$timeout_watcher" 2>/dev/null || :
|
||||
rm -rf -- "$timeout_dir"' EXIT
|
||||
trap 'exit 130' INT
|
||||
trap 'exit 143' TERM
|
||||
trap 'exit 129' HUP
|
||||
trap 'exit 131' QUIT
|
||||
# Do not clean up a timer whose process group has not initialized yet.
|
||||
while [[ ! -f "$timeout_dir/ready" ]]; do
|
||||
kill -0 "$timeout_watcher" 2>/dev/null || exit 125
|
||||
sleep 0.001
|
||||
done
|
||||
wait "$timeout_worker"
|
||||
timeout_code=$?
|
||||
if [[ -f "$timeout_dir/expired" ]]; then
|
||||
# Let the timer finish escalation even if the task leader exited first.
|
||||
wait "$timeout_watcher" 2>/dev/null || :
|
||||
exit 124
|
||||
fi
|
||||
exit "$timeout_code"
|
||||
)
|
||||
}
|
||||
+31
-11
@@ -3,13 +3,28 @@
|
||||
dir_shell=$QL_DIR/shell
|
||||
. $dir_shell/share.sh
|
||||
. $dir_shell/api.sh
|
||||
. $dir_shell/task-timeout.sh
|
||||
|
||||
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
|
||||
|
||||
trap "single_hanle" 2 3 20 15 14 19 1
|
||||
single_hanle() {
|
||||
eval MANUAL=true handle_task_end "$@" "$cmd"
|
||||
_task_exit_code="${_task_exit_code:-$?}"
|
||||
[[ "$_task_exit_code" == "0" ]] && _task_exit_code="1"
|
||||
eval MANUAL=true handle_task_end "$@"
|
||||
exit 1
|
||||
}
|
||||
|
||||
run_shell_script() {
|
||||
# Preserve inherited variables/functions, but contain exit, exec and shell
|
||||
# options so the task wrapper can run after hooks and report completion.
|
||||
( . "$@" )
|
||||
}
|
||||
|
||||
define_program() {
|
||||
local file_param=$1
|
||||
if [[ $file_param == *.js ]] || [[ $file_param == *.mjs ]]; then
|
||||
@@ -17,7 +32,7 @@ define_program() {
|
||||
elif [[ $file_param == *.py ]] || [[ $file_param == *.pyc ]]; then
|
||||
which_program="python3"
|
||||
elif [[ $file_param == *.sh ]]; then
|
||||
which_program="."
|
||||
which_program="run_shell_script"
|
||||
elif [[ $file_param == *.ts ]]; then
|
||||
which_program="ts-node-transpile-only"
|
||||
else
|
||||
@@ -44,8 +59,14 @@ handle_log_path() {
|
||||
fi
|
||||
fi
|
||||
|
||||
time=$(date "+$mtime_format")
|
||||
log_time=$(format_log_time "$mtime_format" "$time")
|
||||
if [[ $is_macos -ne 1 && $mtime_format == '%Y-%m-%d %H:%M:%S.%3N' ]]; then
|
||||
# Render both representations from one clock snapshot, without parsing it
|
||||
# again in a second date process.
|
||||
IFS='|' read -r time log_time < <(date "+$mtime_format|%Y-%m-%d-%H-%M-%S-%3N")
|
||||
else
|
||||
time=$(date "+$mtime_format")
|
||||
log_time=$(format_log_time "$mtime_format" "$time")
|
||||
fi
|
||||
if [[ -z $log_name ]]; then
|
||||
log_dir_tmp="${file_param##*/}"
|
||||
if [[ $file_param =~ "/" ]]; then
|
||||
@@ -92,11 +113,6 @@ format_params() {
|
||||
mtime_format="%Y-%m-%d %H:%M:%S.%3N"
|
||||
fi
|
||||
timeoutCmd=""
|
||||
if [[ $command_timeout_time ]]; then
|
||||
if type timeout &>/dev/null; then
|
||||
timeoutCmd="timeout --foreground -s 2 -k 10s $command_timeout_time "
|
||||
fi
|
||||
fi
|
||||
# params=$(echo "$@" | sed -E 's/([^ ])&([^ ])/\1\\\&\2/g')
|
||||
|
||||
# 分割 task 内置参数和脚本参数
|
||||
@@ -116,7 +132,11 @@ format_params() {
|
||||
}
|
||||
|
||||
init_begin_time() {
|
||||
begin_time=$(format_time "$time_format" "$time")
|
||||
if [[ $is_macos -ne 1 && $mtime_format == '%Y-%m-%d %H:%M:%S.%3N' && $time_format == '%Y-%m-%d %H:%M:%S' ]]; then
|
||||
begin_time=${time%.*}
|
||||
else
|
||||
begin_time=$(format_time "$time_format" "$time")
|
||||
fi
|
||||
begin_timestamp=$(format_timestamp "$time_format" "$time")
|
||||
}
|
||||
|
||||
|
||||
+119
-113
@@ -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,67 @@ 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
|
||||
}
|
||||
|
||||
## 查看青龙服务日志
|
||||
show_service_logs() {
|
||||
local lines="15"
|
||||
local no_stream="false"
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--lines)
|
||||
if [[ ! "${2:-}" =~ ^[0-9]+$ ]]; then
|
||||
echo "ql log: --lines requires a non-negative integer" >&2
|
||||
return 2
|
||||
fi
|
||||
lines="$2"
|
||||
shift 2
|
||||
;;
|
||||
--lines=*)
|
||||
lines="${1#--lines=}"
|
||||
if [[ ! "$lines" =~ ^[0-9]+$ ]]; then
|
||||
echo "ql log: --lines requires a non-negative integer" >&2
|
||||
return 2
|
||||
fi
|
||||
shift
|
||||
;;
|
||||
--nostream)
|
||||
no_stream="true"
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
echo "ql log: unknown option: $1" >&2
|
||||
return 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
local system_log_dir="$dir_data/syslog"
|
||||
local latest_system_log
|
||||
latest_system_log=$(ls -1t "$system_log_dir"/*.log* 2>/dev/null | head -n 1)
|
||||
if [[ "$lines" != "0" ]] && [[ -f "$latest_system_log" ]]; then
|
||||
tail -n "$lines" "$latest_system_log"
|
||||
fi
|
||||
|
||||
if [[ "$no_stream" == "true" ]]; then
|
||||
if [[ -z "$latest_system_log" ]]; then
|
||||
echo "ql log: no system log found in $system_log_dir" >&2
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Winston writes the active runtime log by date. Follow the filename so
|
||||
# size-based rotations are handled without involving the PM2 log files.
|
||||
local current_system_log="$system_log_dir/$(date +%F).log"
|
||||
tail -n 0 --retry -F "$current_system_log"
|
||||
}
|
||||
|
||||
## 脚本用法
|
||||
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 <fileurl> # 更新单个脚本文件"
|
||||
@@ -221,105 +275,52 @@ usage() {
|
||||
echo -e "5. $cmd_update rmlog <days> # 删除旧日志"
|
||||
echo -e "6. $cmd_update bot # 启动tg-bot"
|
||||
echo -e "7. $cmd_update check # 检测青龙环境并修复"
|
||||
echo -e "8. $cmd_update resetlet # 重置登录错误次数"
|
||||
echo -e "9. $cmd_update resettfa # 禁用两步登录"
|
||||
echo -e "10. $cmd_update resetpwd # 修改登录密码"
|
||||
echo -e "11. $cmd_update resetname # 修改登录用户名"
|
||||
echo -e "8. $cmd_update log [--lines <n>] [--nostream] # 查看青龙运行日志"
|
||||
echo -e "9. $cmd_update resetlet # 重置登录错误次数"
|
||||
echo -e "10. $cmd_update resettfa # 禁用两步登录"
|
||||
echo -e "11. $cmd_update resetpwd # 修改登录密码"
|
||||
echo -e "12. $cmd_update resetname # 修改登录用户名"
|
||||
}
|
||||
|
||||
reload_qinglong() {
|
||||
echo -e "[reload_qinglong] deleting Triggered at $(date)" >>${dir_log}/reload.log
|
||||
local reload_target="${1}"
|
||||
if [[ "$reload_target" == 'system' ]]; then
|
||||
QlPort="${ql_port:-${QlPort:-5700}}" QlBaseUrl="${ql_base_url:-${QlBaseUrl:-/}}" node "$dir_shell/upgrade.cjs" reload
|
||||
return $?
|
||||
fi
|
||||
|
||||
# Data import and a plain service restart retain their existing behavior.
|
||||
echo -e "[reload_qinglong] deleting Triggered at $(date)" >>"${dir_log}/reload.log"
|
||||
sleep 3
|
||||
delete_pm2
|
||||
echo -e "[reload_qinglong] deleted Triggered at $(date)" >>${dir_log}/reload.log
|
||||
|
||||
local reload_target="${1}"
|
||||
local primary_branch="master"
|
||||
if [[ "${QL_BRANCH}" == "develop" ]] || [[ "${QL_BRANCH}" == "debian" ]] || [[ "${QL_BRANCH}" == "debian-dev" ]]; then
|
||||
primary_branch="${QL_BRANCH}"
|
||||
fi
|
||||
|
||||
if [[ "$reload_target" == 'system' ]]; then
|
||||
rm -rf ${dir_root}/back ${dir_root}/cli ${dir_root}/docker ${dir_root}/sample ${dir_root}/shell ${dir_root}/src
|
||||
mv -f ${dir_tmp}/qinglong-${primary_branch}/* ${dir_root}/
|
||||
rm -rf $dir_static/*
|
||||
mv -f ${dir_tmp}/qinglong-static-${primary_branch}/* ${dir_static}/
|
||||
cp -f $file_config_sample $dir_config/config.sample.sh
|
||||
fi
|
||||
|
||||
if [[ "$reload_target" == 'data' ]]; then
|
||||
rm -rf ${dir_data}/*
|
||||
mv -f ${dir_tmp}/data/* ${dir_data}/
|
||||
rm -rf "${dir_data:?}"/*
|
||||
mv -f "$dir_tmp"/data/* "$dir_data"/ || return $?
|
||||
fi
|
||||
echo -e "[reload_qinglong] starting Triggered at $(date)" >>${dir_log}/reload.log
|
||||
reload_pm2
|
||||
echo -e "[reload_qinglong] started Triggered at $(date)\n" >>${dir_log}/reload.log
|
||||
}
|
||||
|
||||
## 更新 qinglong
|
||||
update_qinglong() {
|
||||
rm -rf ${dir_tmp}/*
|
||||
local mirror="gitee"
|
||||
local downloadQLUrl="https://gitee.com/whyour/qinglong/repository/archive"
|
||||
local downloadStaticUrl="https://gitee.com/whyour/qinglong-static/repository/archive"
|
||||
local githubStatus=$(curl -s --noproxy "*" -m 2 -IL "https://google.com" | grep 200)
|
||||
if [[ ! -z $githubStatus ]]; then
|
||||
if curl --fail --silent --noproxy "*" --max-time 2 --head "https://github.com" >/dev/null; then
|
||||
mirror="github"
|
||||
downloadQLUrl="https://github.com/whyour/qinglong/archive/refs/heads"
|
||||
downloadStaticUrl="https://github.com/whyour/qinglong-static/archive/refs/heads"
|
||||
fi
|
||||
echo -e "使用 ${mirror} 源更新...\n"
|
||||
|
||||
local primary_branch="master"
|
||||
if [[ "${QL_BRANCH}" == "develop" ]] || [[ "${QL_BRANCH}" == "debian" ]] || [[ "${QL_BRANCH}" == "debian-dev" ]]; then
|
||||
primary_branch="${QL_BRANCH}"
|
||||
fi
|
||||
|
||||
wget -cqO "${dir_tmp}/ql.zip" "${downloadQLUrl}/${primary_branch}.zip"
|
||||
exit_status=$?
|
||||
|
||||
if [[ $exit_status -eq 0 ]]; then
|
||||
echo -e "更新青龙源文件成功...\n"
|
||||
|
||||
unzip -oq ${dir_tmp}/ql.zip -d ${dir_tmp}
|
||||
|
||||
update_qinglong_static
|
||||
else
|
||||
echo -e "更新青龙源文件失败,请检查网络...\n"
|
||||
fi
|
||||
t '使用 %s 源更新...\n' "$mirror"
|
||||
QlPort="${ql_port:-${QlPort:-5700}}" QlBaseUrl="${ql_base_url:-${QlBaseUrl:-/}}" node "$dir_shell/upgrade.cjs" update "$mirror" "$needRestart"
|
||||
}
|
||||
|
||||
update_qinglong_static() {
|
||||
wget -cqO "${dir_tmp}/static.zip" "${downloadStaticUrl}/${primary_branch}.zip"
|
||||
exit_status=$?
|
||||
|
||||
if [[ $exit_status -eq 0 ]]; then
|
||||
echo -e "更新青龙静态资源成功...\n"
|
||||
unzip -oq ${dir_tmp}/static.zip -d ${dir_tmp}
|
||||
|
||||
check_update_dep
|
||||
# Preserve the updater's status through both tee and the final timing output.
|
||||
run_update_logged() (
|
||||
set -o pipefail
|
||||
if [[ "$real_time" == 'true' ]]; then
|
||||
"$@"
|
||||
elif [[ "$no_tee" == 'true' ]]; then
|
||||
"$@" >>"$file_path" 2>&1
|
||||
else
|
||||
echo -e "更新青龙静态资源失败,请检查网络...\n"
|
||||
"$@" 2>&1 | tee -a "$file_path"
|
||||
fi
|
||||
}
|
||||
|
||||
check_update_dep() {
|
||||
echo -e "\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 "更新包下载成功..."
|
||||
|
||||
if [[ "$needRestart" == 'true' ]]; then
|
||||
reload_qinglong "system"
|
||||
fi
|
||||
else
|
||||
echo -e "\n依赖检测安装失败,请检查网络...\n"
|
||||
fi
|
||||
}
|
||||
)
|
||||
|
||||
## 对比脚本
|
||||
diff_scripts() {
|
||||
@@ -374,23 +375,19 @@ gen_list_repo() {
|
||||
|
||||
cd ${repo_path}
|
||||
|
||||
local cmd="find ."
|
||||
local index=0
|
||||
if [[ $6 ]]; then
|
||||
file_extensions="$6"
|
||||
if [[ $file_extensions =~ "|" ]]; then
|
||||
file_extensions=$(echo $file_extensions | sed 's/|/ /g')
|
||||
local extension_string="${6:-$file_extensions}"
|
||||
extension_string="${extension_string//|/ }"
|
||||
extension_string="${extension_string//$'\n'/ }"
|
||||
local -a extensions=() find_args=()
|
||||
IFS=$' \t\n' read -r -a extensions <<< "$extension_string"
|
||||
local extension
|
||||
for extension in "${extensions[@]}"; do
|
||||
if [[ ${#find_args[@]} -gt 0 ]]; then
|
||||
find_args+=(-o)
|
||||
fi
|
||||
fi
|
||||
for extension in $file_extensions; do
|
||||
if [[ $index -eq 0 ]]; then
|
||||
cmd="${cmd} -name \"*.${extension}\""
|
||||
else
|
||||
cmd="${cmd} -o -name \"*.${extension}\""
|
||||
fi
|
||||
let index+=1
|
||||
find_args+=(-name "*.${extension}")
|
||||
done
|
||||
files=$(eval $cmd | sed 's/^..//')
|
||||
files=$(find . "${find_args[@]}" | sed 's/^..//')
|
||||
if [[ $path ]]; then
|
||||
files=$(echo "$files" | egrep "$path")
|
||||
fi
|
||||
@@ -403,7 +400,7 @@ gen_list_repo() {
|
||||
|
||||
if [[ $dependence ]]; then
|
||||
cd ${repo_path}
|
||||
results=$(eval $cmd | sed 's/^..//' | egrep "$dependence")
|
||||
results=$(find . "${find_args[@]}" | sed 's/^..//' | egrep "$dependence")
|
||||
for _file in ${results}; do
|
||||
file_path=$(dirname $_file)
|
||||
make_dir "${dir_scripts}/${uniq_path}/${file_path}"
|
||||
@@ -471,6 +468,12 @@ main() {
|
||||
local p8="${8}"
|
||||
local p9="${9}"
|
||||
local p10="${10}"
|
||||
|
||||
if [[ "$p1" == "log" ]]; then
|
||||
show_service_logs "${@:2}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local log_dir="${p1}"
|
||||
make_dir "$dir_log/$log_dir"
|
||||
local log_time=$(date "+%Y-%m-%d-%H-%M-%S")
|
||||
@@ -497,14 +500,17 @@ main() {
|
||||
|
||||
[[ $ID ]] && update_cron "\"$ID\"" "0" "$$" "$log_path" "$begin_timestamp"
|
||||
|
||||
local command_status=0
|
||||
case $p1 in
|
||||
update)
|
||||
fix_config
|
||||
local needRestart=${p2:-"true"}
|
||||
eval update_qinglong $cmd
|
||||
run_update_logged update_qinglong
|
||||
command_status=$?
|
||||
;;
|
||||
reload)
|
||||
eval reload_qinglong "$p2" $cmd
|
||||
run_update_logged reload_qinglong "$p2"
|
||||
command_status=$?
|
||||
;;
|
||||
extra)
|
||||
eval run_extra_shell $cmd
|
||||
@@ -514,7 +520,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 +529,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 +555,7 @@ main() {
|
||||
eval update_auth_config "\\\"username\\\":\\\"$p2\\\"" "重置用户名" $cmd
|
||||
;;
|
||||
*)
|
||||
eval echo -e "命令输入错误...\\\n" $cmd
|
||||
t '命令输入错误...\n'
|
||||
eval usage $cmd
|
||||
;;
|
||||
esac
|
||||
@@ -563,9 +569,9 @@ main() {
|
||||
if [[ "$p1" != "repo" ]] && [[ "$p1" != "raw" ]]; then
|
||||
eval echo -e "\\\n\#\# 执行结束... $end_time 耗时 $diff_time 秒 " $cmd
|
||||
fi
|
||||
return "$command_status"
|
||||
}
|
||||
|
||||
import_config "$@"
|
||||
main "$@"
|
||||
|
||||
exit 0
|
||||
exit $?
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
// The image updater and local CLI share one implementation and build contract.
|
||||
const upgrade = require('../cli/src/internal/maintenance/upgradeArtifacts.cjs');
|
||||
module.exports = upgrade;
|
||||
if (require.main === module)
|
||||
upgrade.main().catch((error) => {
|
||||
console.error(`更新失败: ${error.message}`);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
Reference in New Issue
Block a user