From af7fb446506846d2f191e95ae37b342bdcd15fa7 Mon Sep 17 00:00:00 2001 From: dekun Date: Fri, 17 Jul 2026 13:12:32 +0800 Subject: [PATCH] Raise Nginx proxy timeouts so AI daily/weekly review is not cut at 60s. POST /ai_daily_review was returning 504 while the model was still running; patch BT reverse-proxy read/send timeouts to 360s on deploy. Co-authored-by: Cursor --- deploy/nginx_ai_review_timeouts.sh | 92 +++++++++++++++++++ deploy/pull_and_restart.sh | 4 + .../templates/embed_boot_scripts.html | 8 +- lib/instance/templates/index.html | 8 +- 4 files changed, 108 insertions(+), 4 deletions(-) create mode 100644 deploy/nginx_ai_review_timeouts.sh diff --git a/deploy/nginx_ai_review_timeouts.sh b/deploy/nginx_ai_review_timeouts.sh new file mode 100644 index 0000000..adf517d --- /dev/null +++ b/deploy/nginx_ai_review_timeouts.sh @@ -0,0 +1,92 @@ +#!/usr/bin/env bash +# 拉长宝塔 Nginx 反代读/写超时,避免 AI 复盘(1~3 分钟)被 60s 默认切断返回 504. +# 用法: bash deploy/nginx_ai_review_timeouts.sh +# 可选: TIMEOUT_SEC=360 bash deploy/nginx_ai_review_timeouts.sh +set -euo pipefail + +TIMEOUT_SEC="${TIMEOUT_SEC:-360}" +PROXY_ROOT="${PROXY_ROOT:-/www/server/panel/vhost/nginx/proxy}" +SITES=(okx.hyf2.cc bn.hyf2.cc gt1.hyf2.cc zk.hyf2.cc) + +if [[ ! -d "$PROXY_ROOT" ]]; then + echo "skip: no BT nginx proxy dir at $PROXY_ROOT" + exit 0 +fi + +patched=0 +for site in "${SITES[@]}"; do + dir="$PROXY_ROOT/$site" + [[ -d "$dir" ]] || continue + shopt -s nullglob + for conf in "$dir"/*.conf; do + # 已有同秒数则跳过 + if grep -qE "proxy_read_timeout[[:space:]]+${TIMEOUT_SEC}" "$conf"; then + echo "ok (unchanged): $conf" + continue + fi + # 去掉旧的超时行,再在 proxy_http_version 后插入 + tmp="$(mktemp)" + grep -vE 'proxy_(connect|send|read)_timeout' "$conf" >"$tmp" || true + if grep -q 'proxy_http_version' "$tmp"; then + awk -v t="$TIMEOUT_SEC" ' + {print} + /proxy_http_version/ && !done { + print " proxy_connect_timeout " t "s;" + print " proxy_send_timeout " t "s;" + print " proxy_read_timeout " t "s;" + done=1 + } + ' "$tmp" >"$conf" + else + # 兜底:插到 proxy_pass 后 + awk -v t="$TIMEOUT_SEC" ' + {print} + /proxy_pass/ && !done { + print " proxy_connect_timeout " t "s;" + print " proxy_send_timeout " t "s;" + print " proxy_read_timeout " t "s;" + done=1 + } + ' "$tmp" >"$conf" + fi + rm -f "$tmp" + echo "patched: $conf -> ${TIMEOUT_SEC}s" + patched=$((patched + 1)) + done +done + +# 同步抬高全局默认,防止其它 location 仍用 60s +GLOBAL_PROXY="${GLOBAL_PROXY:-/www/server/nginx/conf/proxy.conf}" +if [[ -f "$GLOBAL_PROXY" ]]; then + if grep -qE "proxy_read_timeout[[:space:]]+${TIMEOUT_SEC}" "$GLOBAL_PROXY"; then + echo "ok (unchanged): $GLOBAL_PROXY" + else + cp -a "$GLOBAL_PROXY" "${GLOBAL_PROXY}.bak.ai_timeout" + sed -i -E \ + -e "s/proxy_connect_timeout[[:space:]]+[0-9]+;/proxy_connect_timeout ${TIMEOUT_SEC};/" \ + -e "s/proxy_read_timeout[[:space:]]+[0-9]+;/proxy_read_timeout ${TIMEOUT_SEC};/" \ + -e "s/proxy_send_timeout[[:space:]]+[0-9]+;/proxy_send_timeout ${TIMEOUT_SEC};/" \ + "$GLOBAL_PROXY" + echo "patched: $GLOBAL_PROXY -> ${TIMEOUT_SEC}s" + patched=$((patched + 1)) + fi +fi + +if [[ "$patched" -eq 0 ]]; then + echo "done (nothing to patch)" + exit 0 +fi + +if command -v nginx >/dev/null 2>&1; then + nginx -t + # 宝塔常用 reload + if [[ -x /etc/init.d/nginx ]]; then + /etc/init.d/nginx reload + else + nginx -s reload + fi + echo "nginx reloaded" +else + echo "warn: nginx binary not found; configs patched but not reloaded" +fi +echo "done" diff --git a/deploy/pull_and_restart.sh b/deploy/pull_and_restart.sh index f43c588..77a7124 100644 --- a/deploy/pull_and_restart.sh +++ b/deploy/pull_and_restart.sh @@ -31,6 +31,10 @@ echo ">>> pm2 log policy (silence leftover + 3-day retain)" sed -i 's/\r$//' deploy/pm2_log_policy.sh 2>/dev/null || true bash deploy/pm2_log_policy.sh +echo ">>> nginx AI review proxy timeouts (avoid 504 on /ai_*_review)" +sed -i 's/\r$//' deploy/nginx_ai_review_timeouts.sh 2>/dev/null || true +bash deploy/nginx_ai_review_timeouts.sh || echo "warn: nginx timeout patch skipped" + echo ">>> pm2 restart --update-env" # --update-env:避免 PM2 dump 里残留的跨实例环境变量(如 EXCHANGE_DISPLAY_NAME)继续污染 pm2 restart crypto_gate crypto_binance crypto_okx manual-trading-hub manual-agent-gate --update-env 2>/dev/null \ diff --git a/lib/instance/templates/embed_boot_scripts.html b/lib/instance/templates/embed_boot_scripts.html index bad048d..ecbc41f 100644 --- a/lib/instance/templates/embed_boot_scripts.html +++ b/lib/instance/templates/embed_boot_scripts.html @@ -336,7 +336,9 @@ function genDaily(){ const ac = new AbortController(); const timer = setTimeout(()=>ac.abort(), 360000); fetch("/ai_daily_review",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`date=${encodeURIComponent(d)}`,signal:ac.signal}) - .then(r=>{ if(!r.ok) throw new Error("HTTP "+r.status); return r.json(); }) + .then(r=>{ if(!r.ok) throw new Error(r.status === 504 + ? "HTTP 504(网关超时:Nginx 等反代切断,通常 AI 仍在生成;已加长超时后请重试)" + : ("HTTP "+r.status)); return r.json(); }) .then(data=>{ if(!data || data.result == null) throw new Error("返回数据为空"); const el=document.getElementById("daily_result"); @@ -379,7 +381,9 @@ function genWeekly(){ const ac = new AbortController(); const timer = setTimeout(()=>ac.abort(), 360000); fetch("/ai_weekly_review",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`start_date=${encodeURIComponent(s)}&end_date=${encodeURIComponent(e)}`,signal:ac.signal}) - .then(r=>{ if(!r.ok) throw new Error("HTTP "+r.status); return r.json(); }) + .then(r=>{ if(!r.ok) throw new Error(r.status === 504 + ? "HTTP 504(网关超时:Nginx 等反代切断,通常 AI 仍在生成;已加长超时后请重试)" + : ("HTTP "+r.status)); return r.json(); }) .then(data=>{ if(!data || data.result == null) throw new Error("返回数据为空"); const el=document.getElementById("weekly_result"); diff --git a/lib/instance/templates/index.html b/lib/instance/templates/index.html index 7892546..549d7fa 100644 --- a/lib/instance/templates/index.html +++ b/lib/instance/templates/index.html @@ -796,7 +796,9 @@ function genDaily(){ const ac = new AbortController(); const timer = setTimeout(()=>ac.abort(), 360000); fetch("/ai_daily_review",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`date=${encodeURIComponent(d)}`,signal:ac.signal}) - .then(r=>{ if(!r.ok) throw new Error("HTTP "+r.status); return r.json(); }) + .then(r=>{ if(!r.ok) throw new Error(r.status === 504 + ? "HTTP 504(网关超时:Nginx 等反代切断,通常 AI 仍在生成;已加长超时后请重试)" + : ("HTTP "+r.status)); return r.json(); }) .then(data=>{ if(!data || data.result == null) throw new Error("返回数据为空"); const el=document.getElementById("daily_result"); @@ -839,7 +841,9 @@ function genWeekly(){ const ac = new AbortController(); const timer = setTimeout(()=>ac.abort(), 360000); fetch("/ai_weekly_review",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`start_date=${encodeURIComponent(s)}&end_date=${encodeURIComponent(e)}`,signal:ac.signal}) - .then(r=>{ if(!r.ok) throw new Error("HTTP "+r.status); return r.json(); }) + .then(r=>{ if(!r.ok) throw new Error(r.status === 504 + ? "HTTP 504(网关超时:Nginx 等反代切断,通常 AI 仍在生成;已加长超时后请重试)" + : ("HTTP "+r.status)); return r.json(); }) .then(data=>{ if(!data || data.result == null) throw new Error("返回数据为空"); const el=document.getElementById("weekly_result");