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 <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-17 13:12:32 +08:00
parent 5bd3f3250a
commit af7fb44650
4 changed files with 108 additions and 4 deletions
+92
View File
@@ -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"
+4
View File
@@ -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 \
@@ -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");
+6 -2
View File
@@ -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");