feat(deploy): 新增外部访问诊断脚本 check-access.sh

"服务已启动但外部访问不了"时一次性定位原因,只读诊断、不改配置:
- 读出 .env 的 HOST/PORT,并对 127.0.0.1 这种只监听回环的配置直接报错
- 检查实际监听地址(ss/netstat),区分通配与仅回环
- 回环 + 对外 IP 两次 /health 自测(用 ip route get 取真实出口 IP)
- 检查 80/443 上有没有反向代理,并说明本项目不部署反代
- 检查 firewalld / ufw / iptables 对端口的放行情况,给出修复命令
- 结尾按"直连后端"与"经反代"两条路径给出结论

同时更新 README.txt、deploy/proxy/README.md(新增第八节:还没有反代时
如何直连后端)与打包校验清单。
This commit is contained in:
2026-09-15 01:43:35 +08:00
parent 321ab1f584
commit 34c5040e6b
4 changed files with 269 additions and 0 deletions
+241
View File
@@ -0,0 +1,241 @@
#!/bin/bash
# ============================================================
# OpenDance Studio Web 服务端 —— 外部访问诊断
#
# 用途:服务"本机能 curl 通、外部却访问不了"时,一次性定位原因。
#
# 用法(在装有本服务的服务器上执行,无需 root):
# bash deploy/check-access.sh # 自动寻找 .env
# bash deploy/check-access.sh /opt/opendance
#
# 只读诊断,不修改任何配置、不重启任何服务。
# ============================================================
set -uo pipefail
APP_DIR="${1:-/opt/opendance}"
PORT=""
HOST_CFG=""
PROBLEMS=0
c_red() { printf '\033[31m%s\033[0m\n' "$*"; }
c_grn() { printf '\033[32m%s\033[0m\n' "$*"; }
c_yel() { printf '\033[33m%s\033[0m\n' "$*"; }
ok() { c_grn " ✅ $*"; }
bad() { c_red " ❌ $*"; PROBLEMS=$((PROBLEMS + 1)); }
warn() { c_yel " ⚠ $*"; }
info() { printf ' %s\n' "$*"; }
title() { printf '\n\033[1m%s\033[0m\n' "$*"; }
# ---------- 1. 定位并读取 .env ----------
title "1. 定位配置(.env)"
ENV_FILE=""
for cand in "$APP_DIR/deploy/.env" "./deploy/.env" "./.env"; do
if [ -f "$cand" ]; then ENV_FILE="$cand"; break; fi
done
if [ -z "$ENV_FILE" ]; then
warn "未找到 .env(已尝试 $APP_DIR/deploy/.env、./deploy/.env、./.env)"
info "将按默认值 PORT=3000、HOST=0.0.0.0 继续诊断"
else
ok "配置文件:$ENV_FILE"
# 只提取需要的两个键,避免把口令等敏感值读进环境
PORT="$(grep -E '^[[:space:]]*PORT=' "$ENV_FILE" | tail -1 | cut -d= -f2- | tr -d '"'"'"' \r')"
HOST_CFG="$(grep -E '^[[:space:]]*HOST=' "$ENV_FILE" | tail -1 | cut -d= -f2- | tr -d '"'"'"' \r')"
fi
PORT="${PORT:-3000}"
HOST_CFG="${HOST_CFG:-0.0.0.0}"
info "PORT = $PORT"
info "HOST = $HOST_CFG"
case "$HOST_CFG" in
127.0.0.1|localhost|::1)
bad "HOST=$HOST_CFG 表示只监听本机回环,外部一定访问不到"
info "修复:把 $ENV_FILE 里的 HOST 改为 0.0.0.0,然后重启服务"
info " bash deploy/ctl.sh restart (或 systemctl restart opendance-api)"
;;
0.0.0.0|'*'|'')
ok "HOST=$HOST_CFG 允许外部连接(是否真能访问还要看下面的监听与防火墙)"
;;
*)
warn "HOST=$HOST_CFG 只绑定某个具体网卡地址"
info "若客户端不在该网段,请改为 0.0.0.0"
;;
esac
# ---------- 2. 实际监听端口 ----------
title "2. 实际监听状态"
LISTEN_LINE=""
if command -v ss >/dev/null 2>&1; then
LISTEN_LINE="$(ss -tlnH 2>/dev/null | awk -v p=":$PORT" '$4 ~ p"$"')"
elif command -v netstat >/dev/null 2>&1; then
LISTEN_LINE="$(netstat -tln 2>/dev/null | awk -v p=":$PORT" '$4 ~ p"$"')"
fi
if [ -z "$LISTEN_LINE" ]; then
if [ -z "$(command -v ss || command -v netstat)" ]; then
warn "系统里没有 ss / netstat,跳过监听检查"
else
bad "没有任何进程监听 $PORT 端口 —— 服务可能没起来,或 PORT 与 .env 不一致"
info "检查:bash deploy/ctl.sh status / bash deploy/ctl.sh logs"
fi
else
printf '%s\n' "$LISTEN_LINE" | while IFS= read -r l; do info "$l"; done
if printf '%s' "$LISTEN_LINE" | grep -qE '(^|[^0-9])127\.0\.0\.1:'"$PORT"; then
if printf '%s' "$LISTEN_LINE" | grep -qE '(0\.0\.0\.0|\[::\]|\*):'"$PORT"; then
ok "同时监听通配地址,外部可连"
else
bad "只监听 127.0.0.1:$PORT —— 这是「外部访问不到」最常见的原因"
info "修复:$ENV_FILE 中 HOST=0.0.0.0,然后重启服务"
fi
else
ok "监听了通配地址(0.0.0.0 / [::]),绑定层没问题"
fi
fi
# ---------- 3. 本机回环自测 ----------
title "3. 本机自测(回环)"
probe() { # $1=url
if command -v curl >/dev/null 2>&1; then
curl -sS -m 5 -o /dev/null -w '%{http_code}' "$1" 2>/dev/null
elif command -v wget >/dev/null 2>&1; then
wget -q -T 5 -O /dev/null -S "$1" 2>&1 | awk '/HTTP\//{code=$2} END{print code}'
else
printf ''
fi
}
LOOP_CODE="$(probe "http://127.0.0.1:$PORT/health")"
if [ "$LOOP_CODE" = "200" ]; then
ok "http://127.0.0.1:$PORT/health → 200,服务本身正常"
elif [ -z "$LOOP_CODE" ]; then
warn "系统里没有 curl / wget,跳过 HTTP 自测"
else
bad "http://127.0.0.1:$PORT/health → $LOOP_CODE(期望 200)"
info "503 通常表示数据库连不上;000/空表示端口没人应答"
info "看日志:bash deploy/ctl.sh logs"
fi
# ---------- 4. 用对外 IP 自测 ----------
title "4. 本机自测(对外 IP)"
LAN_IP=""
if command -v ip >/dev/null 2>&1; then
LAN_IP="$(ip route get 1.1.1.1 2>/dev/null | awk '/src/{for(i=1;i<=NF;i++) if($i=="src") print $(i+1)}' | head -1)"
fi
[ -z "$LAN_IP" ] && LAN_IP="$(hostname -I 2>/dev/null | awk '{print $1}')"
if [ -z "$LAN_IP" ]; then
warn "拿不到本机对外 IP,跳过"
else
info "本机对外 IP:$LAN_IP"
EXT_CODE="$(probe "http://$LAN_IP:$PORT/health")"
if [ "$EXT_CODE" = "200" ]; then
ok "http://$LAN_IP:$PORT/health → 200,走网卡这一层是通的"
info "那么外部访问不了多半是:防火墙 / 云安全组 / 客户端与服务器不在同一网段,"
info "或者你访问的是 80 / 443 端口(本项目不部署反向代理,见第 5 节)"
elif [ -n "$EXT_CODE" ]; then
bad "http://$LAN_IP:$PORT/health → $EXT_CODE"
else
warn "无法自测(缺 curl / wget)"
fi
fi
# ---------- 5. 是否有 80/443 监听(反代才需要) ----------
title "5. 是否有人在监听 80 / 443(反向代理)"
WEB_LISTEN=""
if command -v ss >/dev/null 2>&1; then
WEB_LISTEN="$(ss -tlnH 2>/dev/null | awk '$4 ~ /:(80|443)$/')"
fi
if [ -z "$WEB_LISTEN" ]; then
warn "本机没有监听 80 / 443 的服务 —— 说明这台机器上没有反向代理"
info "本项目按需求不部署反向代理:直接用 http://<服务器IP>:$PORT 访问后端业务服务"
info "若要通过域名 / 80 / 443 访问,需由运维侧按 deploy/proxy/ 配置 IIS 或 nginx"
else
ok "检测到 80/443 监听(可能是运维侧的反向代理)"
printf '%s\n' "$WEB_LISTEN" | while IFS= read -r l; do info "$l"; done
info "请确认它的转发目标写的是本机 $PORT 端口,见 deploy/proxy/README.md"
fi
# ---------- 6. 防火墙 ----------
title "6. 防火墙"
FW_FOUND=0
if command -v firewall-cmd >/dev/null 2>&1 && firewall-cmd --state >/dev/null 2>&1; then
FW_FOUND=1
info "firewalld 运行中"
ZONES="$(firewall-cmd --get-active-zones 2>/dev/null | awk '/^[a-z]/{print $1}')"
for z in $ZONES; do
PORTS="$(firewall-cmd --zone="$z" --list-ports 2>/dev/null)"
SVC="$(firewall-cmd --zone="$z" --list-services 2>/dev/null)"
info " zone=$z ports=[$PORTS] services=[$SVC]"
if printf '%s' "$PORTS" | grep -qE "(^|,| )$PORT/tcp(,| |$)"; then
ok " $PORT/tcp 已在 zone=$z 放行"
else
bad " $PORT/tcp 未在 zone=$z 放行"
info " 修复:firewall-cmd --permanent --add-port=$PORT/tcp && firewall-cmd --reload"
fi
done
fi
if command -v ufw >/dev/null 2>&1 && ufw status 2>/dev/null | grep -qi '^Status: active'; then
FW_FOUND=1
info "ufw 运行中"
ufw status 2>/dev/null | sed 's/^/ /'
if ufw status 2>/dev/null | grep -qE "(^| )$PORT(/tcp)?( |$)"; then
ok " $PORT 已在 ufw 规则中出现"
else
bad " ufw 中未见 $PORT 放行规则"
info " 修复:ufw allow $PORT/tcp"
fi
fi
if [ "$FW_FOUND" -eq 0 ]; then
if command -v iptables >/dev/null 2>&1; then
if iptables -S 2>/dev/null | grep -qE '(-P INPUT (DROP|REJECT)|--dport '"$PORT"')'; then
info "iptables 规则(节选):"
iptables -S 2>/dev/null | grep -E -- "--dport ($PORT|80|443)|-P INPUT" | sed 's/^/ /'
if iptables -S 2>/dev/null | grep -qE -- "--dport $PORT .*-j ACCEPT"; then
ok " iptables 中有 $PORT 的 ACCEPT 规则"
elif iptables -S 2>/dev/null | grep -qE -- "--dport $PORT .*-j (DROP|REJECT)"; then
bad " iptables 明确拒绝了 $PORT"
else
warn " iptables 中未见 $PORT 的放行规则(默认策略若为 DROP 则外部连不上)"
info " 查询默认策略:iptables -S | grep '^-P INPUT'"
fi
else
info "iptables 无相关丢弃规则(未检测到防火墙拦截迹象)"
fi
else
info "未检测到 firewalld / ufw"
fi
info "注意:云服务器还需检查控制台的「安全组 / 网络 ACL」是否放行 $PORT"
fi
# ---------- 7. 结论 ----------
title "结论"
if [ "$PROBLEMS" -eq 0 ]; then
c_grn " 未发现本机侧问题。"
echo " 若仍访问不了,按顺序排查:"
echo " 1) 客户端与服务器是否互通:ping <服务器IP>"
echo " 2) 云控制台安全组 / 公司网络策略是否放行 $PORT"
echo " 3) 你访问的端口是否写错(本项目服务在 $PORT,不在 80/443)"
echo " 4) 若要通过域名访问,需运维侧按 deploy/proxy/ 配置反向代理"
else
c_red " 发现 $PROBLEMS 个问题,按上面的 ❌ 逐条修复后重试。"
echo
echo " 两种修复路径:"
echo " A. 直连后端(本项目开箱即用,无需反代):"
echo " 1) $ENV_FILE 里 HOST=0.0.0.0"
echo " 2) 防火墙放行 $PORT/tcp"
echo " 3) 重启服务后访问 http://<服务器IP>:$PORT"
echo " B. 通过域名 / 80 / 443 访问(需运维侧配合):"
echo " 按 deploy/proxy/README.md 配置 IIS 或 nginx,整体转发到 $PORT"
fi
echo
exit 0
+22
View File
@@ -180,3 +180,25 @@ curl -i http://<反代地址>/api/catalog/version
| 上传大文件 413 | 代理层上传上限没放开(见第三、四、五节) |
| 上传中途断开 | 代理超时太短 |
| `/songs` 直接访问 404 | SPA 回退被代理拦截;本服务已处理,代理**不要**自己加 `try_files`,全匹配转发即可 |
## 八、还没有反代时:直连后端怎么访问
在运维侧配好反代之前,可以直接访问后端业务服务(本项目开箱即用,不依赖反代):
1. `deploy/.env` 中 `HOST=0.0.0.0`(若只想让某个网段访问,也可填具体网卡地址)
2. 防火墙放行后端端口(默认 `3000/tcp`)
3. 浏览器访问 `http://<服务器IP>:3000`
> 注意:服务默认在 **3000** 端口,不在 80/443。直接访问 `http://<服务器IP>/`
> 是打不开的——那需要反代把 80/443 转到 3000,属于本节的配置范围。
一条命令定位「服务起来了但外部访问不了」:
```bash
bash deploy/check-access.sh # 只读诊断,不改配置
bash deploy/check-access.sh /opt/opendance # 指定安装目录
```
脚本会打印 `.env` 中的 `HOST`/`PORT`、实际监听地址(重点看是否只监听
`127.0.0.1`)、回环与对外 IP 两次自测结果、80/443 上有没有反代、
firewalld / ufw / iptables 的放行情况,并直接给出修复命令。