This commit is contained in:
zlwu committed 2026-08-11 11:13:01 +08:00
1 parent 80138c6388
commit 693e8a678e
682 files changed
+211375 -5306

No files matched your search

+24 -7
View File
@@ -11,11 +11,12 @@
| 角色 | 地址 |
|------|------|
| 入口 | `http://101.35.245.122:10001` |
| 入口 | `https://www.chengyantek.cn`(兼容 `http://101.35.245.122:10001`) |
| Nginx 静态 | `/root/zkxw/web-admin/dist` |
| 后端 | `127.0.0.1:10002`(仅本机) |
| 中间件 | 与本机 `backend/.env` 相同(`47.99.164.66`) |
| MinIO 浏览器 | `http://47.99.164.66:20004/zkxw`(直连,Nginx 不反代) |
| MinIO | 同域 `/minio/zkxw` → `47.99.164.66:20004/zkxw` |
| SSL 证书 | 服务器 `/root/zkxw/cert/www.chengyantek.cn.{pem,key}` |
流量路径:
@@ -123,10 +124,13 @@ sudo nginx -t && sudo systemctl reload nginx
Nginx 关键配置摘要:
- `listen 10001`
- `root /root/zkxw/web-admin/dist`
- `location ^~ /api/` → `http://127.0.0.1:10002`
- **不**反代 MinIO(前端直连 `http://47.99.164.66:20004/zkxw`)
- `https://www.chengyantek.cn`(443 SSL)+ `http` 80/apex → 301 到 https://www
- 兼容 `http://IP:10001`
- `root /root/zkxw/web-admin/dist`;`/api`、`/health` → `127.0.0.1:10002`
- `/minio/zkxw/` → `47.99.164.66:20004/zkxw/`(HTTPS 同域,避免混合内容)
- 证书:`/root/zkxw/cert/www.chengyantek.cn.{pem,key}`(来源仓库旁证书包,私钥勿提交 git;`*.pem`/`*.key` 已在 `.gitignore`)
**云安全组须放行 TCP 80、443**(10001 已通不代表 443 已开)。
静态目录在 `/root/...` 下时,Nginx 工作用户(如 `www-data`)须能穿越 `/root`,否则首页 500 / Permission denied:
@@ -137,6 +141,16 @@ chmod -R a+rX /root/zkxw/web-admin
(或将静态目录放到 `/var/www/zkxw` 等非 `/root` 路径。)
### 4.5 更新 SSL 证书
```bash
# 本机
scp 26548929_www.chengyantek.cn_nginx/www.chengyantek.cn.pem \
26548929_www.chengyantek.cn_nginx/www.chengyantek.cn.key \
root@101.35.245.122:/root/zkxw/cert/
ssh root@101.35.245.122 'chmod 600 /root/zkxw/cert/* && nginx -t && systemctl reload nginx'
```
---
## 5. 脚本与环境对照
@@ -167,10 +181,12 @@ python3 测试环境部署/deploy_test_env.py --backend-only # 仅后端并
python3 测试环境部署/deploy_test_env.py --verify # 仅验收
python3 测试环境部署/deploy_test_env.py --upload-env # 额外上传本机 backend/.env → 远端 .env.test
python3 测试环境部署/deploy_test_env.py --pip # 远端重新 pip install -r requirements.txt
python3 测试环境部署/deploy_test_env.py --health-timeout 90 # 重启后等待健康检查最长时间(默认 60s)
```
依赖本机:`python3`、`pnpm`、`rsync`、`ssh`/`scp`;密码登录需 `sshpass`。
SSH 密码默认读仓库根 `测试环境机器的账号密码`,或环境变量 `ZKXW_TEST_SSH_PASS`(**勿提交密码到 git**)。
SSH 密码默认读仓库根 `测试环境机器的账号密码`,或环境变量 `ZKXW_TEST_SSH_PASS`(**勿提交密码到 git**)。
发版若重启了后端,会先轮询 `127.0.0.1:10002/health`(再确认 Nginx `/health`)通过后再跑验收,避免瞬时 502。
### 6.2 手工命令(等价)
@@ -282,3 +298,4 @@ python3.12 -m venv .venv
| 2026-06-01 | 补充:`web-admin` 测试打包(`pnpm build:test`)、Nginx 8112、`/root/zkxw/dist`、API 反代 `47.99.164.66:8112` |
| 2026-08-06 | 裸机部署 10001/10002 于 101.35.245.122 |
| 2026-08-06 | 新增 `deploy_test_env.py` 日常发版脚本 |
| 2026-08-10 | 启用 `www.chengyantek.cn` HTTPS(443)+ HTTP→HTTPS;MinIO 同域 `/minio/zkxw` |
+99 -1
View File
@@ -26,6 +26,7 @@ import shutil
import subprocess
import sys
import tempfile
import time
from pathlib import Path
from typing import List, Optional, Sequence
@@ -40,6 +41,8 @@ REMOTE_DIST = f"{REMOTE_ROOT}/web-admin/dist"
BACKEND_HOST = "127.0.0.1"
BACKEND_PORT = "10002"
PUBLIC_HTTP_PORT = "10001"
DEFAULT_HEALTH_TIMEOUT_SEC = 60
DEFAULT_HEALTH_INTERVAL_SEC = 2
BACKEND_EXCLUDES = (
".venv",
@@ -253,6 +256,82 @@ def restart_backend(args: argparse.Namespace, password: Optional[str]) -> None:
)
def _health_ok_via_ssh(args: argparse.Namespace, password: Optional[str]) -> bool:
"""本机回环健康检查(不依赖公网 / Nginx)。"""
remote = (
f"curl -sf --connect-timeout 2 --max-time 5 "
f"http://{BACKEND_HOST}:{BACKEND_PORT}/health"
)
cmd = [*ssh_argv(password), remote_target(args), remote]
cp = subprocess.run(
cmd,
env=with_sshpass_env(password),
capture_output=True,
text=True,
)
return cp.returncode == 0 and "healthy" in (cp.stdout or "")
def _health_ok_via_nginx(args: argparse.Namespace) -> bool:
"""经 Nginx 公开入口检查(与验收路径一致)。"""
url = f"http://{args.host}:{PUBLIC_HTTP_PORT}/health"
cp = subprocess.run(
["curl", "-sf", "--connect-timeout", "3", "--max-time", "8", url],
capture_output=True,
text=True,
)
return cp.returncode == 0 and "healthy" in (cp.stdout or "")
def wait_for_health(
args: argparse.Namespace,
password: Optional[str],
*,
prefer_ssh: bool,
) -> None:
"""
重启后轮询健康检查,成功后再验收。
prefer_ssh=True:优先 SSH 打 127.0.0.1:10002(刚重启时更准)。
否则优先 Nginx :10001/health。
"""
require_bin("curl")
timeout = max(1, int(args.health_timeout))
interval = max(0.5, float(args.health_interval))
deadline = time.monotonic() + timeout
attempt = 0
info(f"等待后端健康(最多 {timeout}s,间隔 {interval}s)…")
while True:
attempt += 1
ssh_ok = _health_ok_via_ssh(args, password)
nginx_ok = _health_ok_via_nginx(args)
ready = ssh_ok if prefer_ssh else (nginx_ok or ssh_ok)
detail = f"ssh={'ok' if ssh_ok else 'fail'} nginx={'ok' if nginx_ok else 'fail'}"
if ready:
info(f"健康检查通过(第 {attempt} 次,{detail})")
# 本机已通但 Nginx 仍 502 时,再短等公开入口
if prefer_ssh and not nginx_ok:
info("本机 health 已通,等待 Nginx /health …")
nginx_deadline = time.monotonic() + min(15.0, float(timeout))
while time.monotonic() < nginx_deadline:
if _health_ok_via_nginx(args):
info("Nginx /health 已就绪")
break
time.sleep(interval)
else:
info("Nginx /health 仍未就绪,将继续验收(可能失败)")
return
if time.monotonic() >= deadline:
die(
f"等待健康检查超时({timeout}s,共 {attempt} 次,{detail})。"
f"请 SSH 查看: cd {REMOTE_BACKEND} && ./start-test.sh logs",
code=3,
)
print(f"[WAIT] 未就绪(第 {attempt} 次,{detail}),{interval}s 后重试…")
time.sleep(interval)
def verify(args: argparse.Namespace) -> None:
require_bin("curl")
base = f"http://{args.host}:{PUBLIC_HTTP_PORT}"
@@ -281,7 +360,7 @@ def verify(args: argparse.Namespace) -> None:
)
body = (cp.stdout or "").strip()
print(f"[VERIFY] {name}: {body[:120]} ({url})")
if "healthy" not in body and cp.returncode != 0:
if "healthy" not in body:
ok = False
# 10002 应对外不可达
@@ -333,6 +412,18 @@ def parse_args(argv: Optional[Sequence[str]] = None) -> argparse.Namespace:
p.add_argument("--upload-env", action="store_true", help="上传本机 backend/.env 为远端 .env.test")
p.add_argument("--pip", action="store_true", help="远端执行 pip install -r requirements.txt")
p.add_argument("--no-verify", action="store_true", help="发版后不自动验收")
p.add_argument(
"--health-timeout",
type=int,
default=DEFAULT_HEALTH_TIMEOUT_SEC,
help=f"重启后等待健康检查的最长时间(秒,默认 {DEFAULT_HEALTH_TIMEOUT_SEC})",
)
p.add_argument(
"--health-interval",
type=float,
default=DEFAULT_HEALTH_INTERVAL_SEC,
help=f"健康检查轮询间隔(秒,默认 {DEFAULT_HEALTH_INTERVAL_SEC})",
)
return p.parse_args(argv)
@@ -354,6 +445,7 @@ def main(argv: Optional[Sequence[str]] = None) -> None:
do_fe = not args.backend_only
do_be = not args.frontend_only
did_restart = False
if do_fe:
if not args.skip_build:
@@ -375,10 +467,16 @@ def main(argv: Optional[Sequence[str]] = None) -> None:
if not args.skip_restart:
info("重启后端 …")
restart_backend(args, password)
did_restart = True
else:
info("已跳过后端重启")
if not args.no_verify:
if did_restart:
wait_for_health(args, password, prefer_ssh=True)
else:
# 仅前端发版:仍确认当前后端/Nginx 可用再验收
wait_for_health(args, password, prefer_ssh=False)
info("发版后验收 …")
verify(args)
+132 -5
View File
@@ -1,10 +1,122 @@
# ZKXW 测试环境(裸机)— 部署到 101.35.245.122
# 用法(按发行版二选一):
# Ubuntu/Debian: 复制到 /etc/nginx/sites-available/zkxw-test-10001.conf
# ln -sf ... /etc/nginx/sites-enabled/
# RHEL/CentOS: 复制到 /etc/nginx/conf.d/zkxw-test-10001.conf
# 支持:
# - https://www.chengyantek.cn (443,SSL)
# - http://www.chengyantek.cn / http://chengyantek.cn → 301 到 https://www
# - http://IP:10001 (兼容旧入口,无强制跳转)
#
# 证书部署路径(服务器):
# /root/zkxw/cert/www.chengyantek.cn.pem
# /root/zkxw/cert/www.chengyantek.cn.key
#
# 用法(Ubuntu/Debian):
# cp 到 /etc/nginx/sites-available/zkxw-test-10001.conf
# ln -sfn ... /etc/nginx/sites-enabled/
# nginx -t && systemctl reload nginx
# ---------- HTTP → HTTPS(域名)----------
server {
listen 80;
listen [::]:80;
server_name www.chengyantek.cn chengyantek.cn;
return 301 https://www.chengyantek.cn$request_uri;
}
# ---------- apex HTTPS → www ----------
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name chengyantek.cn;
ssl_certificate /root/zkxw/cert/www.chengyantek.cn.pem;
ssl_certificate_key /root/zkxw/cert/www.chengyantek.cn.key;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1h;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
return 301 https://www.chengyantek.cn$request_uri;
}
# ---------- 主站 HTTPS ----------
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name www.chengyantek.cn;
ssl_certificate /root/zkxw/cert/www.chengyantek.cn.pem;
ssl_certificate_key /root/zkxw/cert/www.chengyantek.cn.key;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1h;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
root /root/zkxw/web-admin/dist;
index index.html;
client_max_body_size 100M;
access_log /var/log/nginx/zkxw_admin_https_access.log;
error_log /var/log/nginx/zkxw_admin_https_error.log;
location = /health {
proxy_pass http://127.0.0.1:10002/health;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_connect_timeout 10s;
proxy_read_timeout 30s;
}
location ^~ /docs {
proxy_pass http://127.0.0.1:10002;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location = /openapi.json {
proxy_pass http://127.0.0.1:10002/openapi.json;
proxy_http_version 1.1;
proxy_set_header Host $host;
}
location ^~ /api/ {
proxy_pass http://127.0.0.1:10002;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 10s;
proxy_send_timeout 120s;
proxy_read_timeout 120s;
}
# MinIO 同域反代(避免 HTTPS 页面对 http://47.99.164.66:20004 混合内容)
location ^~ /minio/zkxw/ {
proxy_pass http://47.99.164.66:20004/zkxw/;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_request_buffering off;
proxy_buffering off;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
proxy_connect_timeout 10s;
add_header Access-Control-Allow-Origin * always;
add_header Access-Control-Allow-Methods "GET, HEAD, OPTIONS" always;
}
location / {
try_files $uri $uri/ /index.html;
}
}
# ---------- 兼容旧入口:IP:10001(HTTP)----------
server {
listen 10001;
listen [::]:10001;
@@ -12,7 +124,6 @@ server {
root /root/zkxw/web-admin/dist;
index index.html;
client_max_body_size 100M;
access_log /var/log/nginx/zkxw_admin_10001_access.log;
@@ -53,6 +164,22 @@ server {
proxy_read_timeout 120s;
}
location ^~ /minio/zkxw/ {
proxy_pass http://47.99.164.66:20004/zkxw/;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_request_buffering off;
proxy_buffering off;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
proxy_connect_timeout 10s;
add_header Access-Control-Allow-Origin * always;
add_header Access-Control-Allow-Methods "GET, HEAD, OPTIONS" always;
}
location / {
try_files $uri $uri/ /index.html;
}