fix(deploy): 安装脚本按真实 HOST 报告监听地址,并主动提示外部不可达

背景:旧版 .env.example 的 HOST=127.0.0.1 会让服务只监听回环,
本机 curl 一切正常、安装日志毫无异常,但外部永远连不上。

install.sh 修复:
- 不再写死 API_HOST=127.0.0.1:从 deploy/.env 读 HOST 得到 BIND_HOST,
  绑定具体网卡时用该地址做健康检查(原来用回环会误判启动失败)
- 汇总输出改为真实监听地址 + 按三种绑定模式给出正确 URL
  (回环写 127.0.0.1、具体网卡写该地址、通配写探测到的对外 IP)
- 新增安装期外部可达性自测:用对外 IP 请求 /health,
  只监听回环 → 打印改 HOST 与重启命令;
  监听正常但自测失败 → 打印防火墙 / 云安全组放行步骤
- 反代提示里回显当前 HOST 实际值,避免照抄"建议 127.0.0.1"改坏配置

README.txt 同步说明。验证:用真实脚本片段 + 打桩跑四种场景
(回环 / 通配可达 / 通配被防火墙拦 / 指定网卡),输出逐一核对。
This commit is contained in:
2026-09-15 01:48:04 +08:00
parent 34c5040e6b
commit 44451028dd
2 changed files with 116 additions and 13 deletions
+114 -12
View File
@@ -28,8 +28,9 @@ cd "$SRC_DIR"
APP_DIR="${APP_DIR:-/opt/opendance}"
SERVICE_USER="${SERVICE_USER:-opendance}"
STORAGE_DIR="${STORAGE_DIR:-/var/lib/opendance/storage}"
API_HOST="127.0.0.1"
API_PORT="${PORT:-3000}"
# 注意:健康检查与汇总输出用的地址不在这里写死——真实监听地址来自
# deploy/.env 的 HOST,脚本在源入 .env 后计算 BIND_HOST / HEALTH_HOST / LAN_IP。
log() { printf '\033[1;34m[install]\033[0m %s\n' "$*"; }
warn() { printf '\033[1;33m[warn]\033[0m %s\n' "$*"; }
@@ -119,6 +120,20 @@ API_PORT="${PORT:-3000}"
[ -n "${DB_PASS:-}" ] || die "deploy/.env 中 DB_PASS 未设置"
[ -n "${DB_NAME:-}" ] || die "deploy/.env 中 DB_NAME 未设置"
# 实际生效的监听地址(deploy/.env 的 HOST,默认 0.0.0.0)
BIND_HOST="${HOST:-0.0.0.0}"
case "$BIND_HOST" in
0.0.0.0|'*'|''|127.0.0.1|localhost|::1) HEALTH_HOST="127.0.0.1" ;;
*) HEALTH_HOST="$BIND_HOST" ;; # 绑定具体网卡时,回环地址是连不上的
esac
# 本机对外 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}')"
command -v node >/dev/null 2>&1 || die "未找到 node。请安装 Node.js 22 或更高版本。"
NODE_BIN="$(command -v node)"
NODE_MAJOR="$(node -p 'process.versions.node.split(".")[0]')"
@@ -373,7 +388,7 @@ esac
log "等待 API 就绪…"
READY=0
for _ in $(seq 1 30); do
if http_ok "http://${API_HOST}:${API_PORT}/health"; then READY=1; break; fi
if http_ok "http://${HEALTH_HOST}:${API_PORT}/health"; then READY=1; break; fi
sleep 1
done
if [ "$READY" -ne 1 ]; then
@@ -381,7 +396,25 @@ if [ "$READY" -ne 1 ]; then
journalctl -u opendance-api -n 40 --no-pager || true
die "API 启动失败"
fi
log "API 已就绪:$(http_get "http://${API_HOST}:${API_PORT}/health" | head -c 200)"
log "API 已就绪:$(http_get "http://${HEALTH_HOST}:${API_PORT}/health" | head -c 200)"
# ---------- 5. 边界:确认"外部到底能不能访问" ----------
# 绑定层写错(HOST=127.0.0.1)时,本机 curl 一切正常,但外部永远连不上,
# 而安装日志里看不出任何异常——必须在安装时就点出来。
EXTERNAL_OK=""
case "$BIND_HOST" in
127.0.0.1|localhost|::1)
ACCESS_MODE="loopback"
;;
*)
ACCESS_MODE="external"
if [ -n "$LAN_IP" ] && http_ok "http://${LAN_IP}:${API_PORT}/health"; then
EXTERNAL_OK="yes"
else
EXTERNAL_OK="no"
fi
;;
esac
# ---------- 5. 反向代理(本脚本**不部署**,仅给出说明) ----------
# 需求变更:Web 端只负责业务服务,反向代理由另外的服务/团队提供。
@@ -403,14 +436,38 @@ cat <<EOF
deploy/proxy/web.config.example IIS(ARR + URL Rewrite)配置
deploy/proxy/README.md IIS / nginx 反代说明文档
注意:若反代与后端不在同一台机器,需保持 HOST=0.0.0.0 并用防火墙限制来源;
注意:若反代与后端不在同一台机器,需保持 HOST=0.0.0.0(当前值:${BIND_HOST})并用防火墙限制来源;
若在同一台机器,建议把 HOST 改为 127.0.0.1。
------------------------------------------------------------
EOF
# ---------- 6. 完成 ----------
HOST_HINT="${DEPLOY_HOST:-<服务器IP>}"
# 汇总里展示的地址必须与真实绑定一致:
# 回环 → 只能写 127.0.0.1(写对外 IP 会让人以为外部能访问)
# 具体网卡 → 只能写该网卡地址(对外 IP 未必等于它)
# 通配 → 用探测到的对外 IP
HOST_HINT="${DEPLOY_HOST:-}"
if [ -z "$HOST_HINT" ]; then
case "$BIND_HOST" in
127.0.0.1|localhost|::1) HOST_HINT="127.0.0.1" ;;
0.0.0.0|'*'|'') HOST_HINT="${LAN_IP:-<服务器IP>}" ;;
*) HOST_HINT="$BIND_HOST" ;;
esac
fi
case "$BIND_HOST" in
127.0.0.1|localhost|::1)
LISTEN_DESC="仅回环 ${BIND_HOST}:${API_PORT}(外部无法访问)" ;;
0.0.0.0|'*'|'')
if [ -n "$LAN_IP" ]; then
LISTEN_DESC="${BIND_HOST}:${API_PORT}(可直接访问:http://${LAN_IP}:${API_PORT})"
else
LISTEN_DESC="${BIND_HOST}:${API_PORT}"
fi ;;
*)
LISTEN_DESC="${BIND_HOST}:${API_PORT}(仅该网卡地址,请确认客户端可达)" ;;
esac
# 按实际使用的初始化系统给出对应的运维命令(用变量拼装,避免嵌套 heredoc 出错)
case "$INIT_SYSTEM" in
@@ -440,19 +497,64 @@ cat <<EOF
============================================================
OpenDance Studio Web 服务端已部署(业务服务,无 Docker、不含反向代理)
服务监听: http://${API_HOST}:${API_PORT}/
管理端首页: http://${API_HOST}:${API_PORT}/ (由本服务直接提供)
API 前缀: http://${API_HOST}:${API_PORT}/api
健康检查: http://${API_HOST}:${API_PORT}/health
契约快照: http://${API_HOST}:${API_PORT}/contracts/
对外访问: 由另外的反向代理服务转发到上述地址(见 deploy/proxy/)
监听地址: ${LISTEN_DESC}
管理端首页: http://${HOST_HINT}:${API_PORT}/ (由本服务直接提供)
API 前缀: http://${HOST_HINT}:${API_PORT}/api
健康检查: http://${HOST_HINT}:${API_PORT}/health
契约快照: http://${HOST_HINT}:${API_PORT}/contracts/
数据库: 使用现有实例 ${DB_HOST}:${DB_PORT:-3306}/${DB_NAME}
上传目录: ${STORAGE_DIR}
程序目录: ${APP_DIR}
Node 进程: 仅监听 ${API_HOST}:${API_PORT}
进程管理: ${INIT_SYSTEM}
HOST 配置: ${BIND_HOST}(部署配置文件 ${APP_DIR}/deploy/.env)
常用命令:
${CMD_HINT}
============================================================
EOF
if [ "$ACCESS_MODE" = "loopback" ]; then
cat <<EOF
============================================================
⚠ 外部访问不了?现在是只监听回环(HOST=${BIND_HOST})
============================================================
服务本身正常,但只有本机能访问 127.0.0.1:${API_PORT},
其它机器(包括浏览器、反向代理)都连不上。
要允许外部访问,改这一行后重启:
sed -i 's/^HOST=.*/HOST=0.0.0.0/' ${APP_DIR}/deploy/.env
EOF
case "$INIT_SYSTEM" in
systemd) echo " systemctl restart opendance-api" ;;
openrc) echo " rc-service opendance-api restart" ;;
sysvinit) echo " /etc/init.d/opendance-api restart" ;;
*) echo " bash ${APP_DIR}/deploy/ctl.sh restart" ;;
esac
cat <<EOF
别忘了在防火墙放行 ${API_PORT}/tcp:
firewall-cmd --permanent --add-port=${API_PORT}/tcp && firewall-cmd --reload
# 或:ufw allow ${API_PORT}/tcp
诊断脚本(只读):bash ${APP_DIR}/deploy/check-access.sh
============================================================
EOF
elif [ "$EXTERNAL_OK" = "no" ]; then
cat <<EOF
============================================================
⚠ 已监听 ${BIND_HOST}:${API_PORT},但用本机对外 IP 自测失败
============================================================
绑定层没问题,外部却连不上,通常是防火墙或云安全组拦截:
1) 放行端口:firewall-cmd --permanent --add-port=${API_PORT}/tcp && firewall-cmd --reload
或 ufw allow ${API_PORT}/tcp
2) 云服务器还要在控制台「安全组 / 网络 ACL」放行 ${API_PORT}/tcp
3) 确认客户端与服务器网络互通
诊断脚本(只读):bash ${APP_DIR}/deploy/check-access.sh
============================================================
EOF
fi