From 4122907936d3dd5780fbcf44558810c30613911c Mon Sep 17 00:00:00 2001 From: wangbo Date: Sun, 18 Oct 2026 00:21:54 +0800 Subject: [PATCH] =?UTF-8?q?fix(deploy):=20=E7=AD=89=E5=BE=85=20Nginx=20?= =?UTF-8?q?=E6=96=B0=E9=85=8D=E7=BD=AE=E7=94=9F=E6=95=88=E5=90=8E=E5=86=8D?= =?UTF-8?q?=E9=AA=8C=E6=94=B6=E5=85=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- scripts/test-proxy-entry.py | 9 ++++++--- start.sh | 36 ++++++++++++++++++++++++++++-------- 3 files changed, 35 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 55adfa1..0fcd5ad 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ DEPLOY_ACCESS=domain DEPLOY_DOMAIN=demo.example.com DEPLOY_HTTPS=false ./start.s DEPLOY_ACCESS=domain DEPLOY_DOMAIN=demo.example.com DEPLOY_HTTPS=true CERTBOT_EMAIL=ops@example.com ./start.sh ``` -Linux IP 模式会自动安装并配置 Nginx,浏览器统一访问 `http://IP`。公网只需放行 TCP 80;前端、API、WebSocket 和 ASG 分别通过 `/`、`/api`、`/socket.io`、`/asg-api` 反向代理,容器上游端口默认仅绑定 `127.0.0.1`。首次启动会初始化数据库和服务,核心容器默认有 180 秒启动宽限期,检查成功会立即通过;脚本等待所选服务恢复可用后才报告完成。可通过 `DEPLOY_HEALTH_TIMEOUT_SECONDS` / `UPDATE_HEALTH_TIMEOUT_SECONDS` 调整等待时限(默认 600 秒)。 +Linux IP 模式会自动安装并配置 Nginx,浏览器统一访问 `http://IP`。公网只需放行 TCP 80;前端、API、WebSocket 和 ASG 分别通过 `/`、`/api`、`/socket.io`、`/asg-api` 反向代理,容器上游端口默认仅绑定 `127.0.0.1`。首次启动会初始化数据库和服务,核心容器默认有 180 秒启动宽限期,检查成功会立即通过;脚本等待所选服务恢复可用后,还会等待 Nginx 配置生效并核验入口,之后才报告完成。可通过 `DEPLOY_HEALTH_TIMEOUT_SECONDS` / `UPDATE_HEALTH_TIMEOUT_SECONDS` 调整等待时限(默认 600 秒)。 常用变量: diff --git a/scripts/test-proxy-entry.py b/scripts/test-proxy-entry.py index 4b8b800..c9ae80d 100644 --- a/scripts/test-proxy-entry.py +++ b/scripts/test-proxy-entry.py @@ -16,13 +16,16 @@ import json,os,sys with open(os.environ['CURL_CALLS'],'a') as out:out.write(json.dumps(sys.argv[1:])+'\\n') if os.environ['HTTP_CASE']=='home-error':sys.exit(22) if any('/api/health/update-ready' in arg for arg in sys.argv): - print(json.dumps({'status':'ok','service':'wrong-upstream' if os.environ['HTTP_CASE']=='wrong-upstream' else 'easyai-server-main'})) + if os.environ['HTTP_CASE']=='delayed-reload' and open(os.environ['CURL_CALLS']).read().count('/api/health/update-ready')==1: + print('redirect\\n301');sys.exit(0) + print(json.dumps({'status':'ok','service':'wrong-upstream' if os.environ['HTTP_CASE']=='wrong-upstream' else 'easyai-server-main'})+'\\n200') +else: print('200',end='') ''') curl.chmod(0o755) - for name, mode, succeeds in [('home-error', 'ip', False), ('wrong-upstream', 'domain', False), ('healthy', 'ip', True), ('healthy', 'https', True)]: + for name, mode, succeeds in [('home-error', 'ip', False), ('wrong-upstream', 'domain', False), ('healthy', 'ip', True), ('healthy', 'https', True), ('delayed-reload', 'domain', True)]: log = root / 'curl.jsonl' log.write_text('') - env = {**os.environ, 'PATH': str(root) + os.pathsep + os.environ['PATH'], 'HTTP_CASE': name, 'CURL_CALLS': str(log)} + env = {**os.environ, 'PATH': str(root) + os.pathsep + os.environ['PATH'], 'HTTP_CASE': name, 'CURL_CALLS': str(log), 'DEPLOY_PROXY_TIMEOUT_SECONDS':'8' if succeeds else '1'} run = '. ./functions.sh; DEPLOY_MODE="$1"; DEPLOY_IP=192.0.2.10; DEPLOY_DOMAIN=deployment.example; DEPLOY_HTTPS=false; if [ "$1" = https ]; then DEPLOY_MODE=domain; DEPLOY_HTTPS=true; fi; verify_proxy_entry' result = subprocess.run(['bash', '-c', run, 'fixture', mode], cwd=root, env=env, capture_output=True, text=True, timeout=15) assert (result.returncode == 0) == succeeds, (name, mode, result.stdout, result.stderr) diff --git a/start.sh b/start.sh index 29e534b..220b969 100755 --- a/start.sh +++ b/start.sh @@ -574,7 +574,7 @@ verify_proxy_entry() { case "$DEPLOY_MODE" in ip|domain) ;; *) return 0 ;; esac local public_host response if [ "$DEPLOY_MODE" = ip ]; then public_host="$DEPLOY_IP"; else public_host="$DEPLOY_DOMAIN"; fi - local curl_options=(--fail --silent --show-error --max-time 30) + local curl_options=(--fail --silent --show-error --max-time 10) local origin='http://127.0.0.1' if [ "$DEPLOY_HTTPS" = true ]; then curl_options+=(--resolve "${public_host}:443:127.0.0.1") @@ -582,13 +582,33 @@ verify_proxy_entry() { else curl_options+=(-H "Host: ${public_host}") fi - curl "${curl_options[@]}" "${origin}/" -o /dev/null || { echo '❌ 首页访问失败,部署未完成' >&2; return 1; } - response="$(curl "${curl_options[@]}" "${origin}/api/health/update-ready")" || return 1 - if ! printf '%s' "$response" | python3 -c 'import json,sys; value=json.load(sys.stdin); sys.exit(0 if value.get("status")=="ok" and value.get("service")=="easyai-server-main" else 1)'; then - echo '❌ 后端公开入口尚未就绪,部署未完成' >&2 - return 1 - fi - echo '✅ 同机 Nginx 首页和后端入口检查通过' + local timeout="${DEPLOY_PROXY_TIMEOUT_SECONDS:-60}" deadline homepage_status api_status + [[ "$timeout" =~ ^[1-9][0-9]*$ ]] || { echo '❌ 入口检查等待秒数必须为正整数' >&2; return 1; } + deadline=$((SECONDS + timeout)) + # nginx -s reload returns before its new workers have accepted requests. + # A previous vhost can briefly answer with a redirect during replacement. + while (( SECONDS < deadline )); do + homepage_status="$(curl "${curl_options[@]}" -w '%{http_code}' "${origin}/" -o /dev/null 2>/dev/null || true)" + if [ "$homepage_status" = 200 ]; then + response="$(curl "${curl_options[@]}" -w '\n%{http_code}' "${origin}/api/health/update-ready" 2>/dev/null || true)" + api_status="${response##*$'\n'}" + response="${response%$'\n'*}" + if [ "$api_status" = 200 ] && printf '%s' "$response" | python3 -c ' +import json,sys +try: + value=json.load(sys.stdin) + sys.exit(0 if value.get("status")=="ok" and value.get("service")=="easyai-server-main" else 1) +except (ValueError, AttributeError): + sys.exit(1) +'; then + echo '✅ 同机 Nginx 首页和后端入口检查通过' + return 0 + fi + fi + sleep 2 + done + echo '❌ Nginx 首页或后端公开入口未在规定时间内就绪,部署未完成' >&2 + return 1 } # ==================== 执行 HTTPS 配置 ====================