diff --git a/README.txt b/README.txt index e551bc3..4e28cc9 100644 --- a/README.txt +++ b/README.txt @@ -68,6 +68,11 @@ OpenDance Studio —— Web 管理服务端 v0.1.0 使用说明 deploy/proxy/web.config.example IIS(ARR + URL Rewrite)配置 deploy/proxy/README.md 完整说明、自检清单与常见故障 + 外部访问不了?先在服务器上跑一次自检脚本(只读,不改配置): + bash deploy/check-access.sh # 或 bash deploy/check-access.sh /opt/opendance + 它会打印 .env 里的 HOST/PORT、实际监听地址、回环与对外 IP 自测结果、 + 80/443 是否有反代、防火墙放行情况,并直接给出修复命令。 + 可覆盖的安装参数 APP_DIR=/opt/opendance 程序目录 SERVICE_USER=opendance 运行服务的系统用户 diff --git a/deploy/check-access.sh b/deploy/check-access.sh new file mode 100755 index 0000000..186925b --- /dev/null +++ b/deploy/check-access.sh @@ -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 diff --git a/deploy/proxy/README.md b/deploy/proxy/README.md index 8c3002a..5f9b7be 100644 --- a/deploy/proxy/README.md +++ b/deploy/proxy/README.md @@ -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 的放行情况,并直接给出修复命令。 diff --git a/scripts/package-release.sh b/scripts/package-release.sh index c3ee899..85ba1ca 100755 --- a/scripts/package-release.sh +++ b/scripts/package-release.sh @@ -112,6 +112,7 @@ for required in \ "opendance-web-v${VERSION}/admin/index.html" \ "opendance-web-v${VERSION}/deploy/install.sh" \ "opendance-web-v${VERSION}/deploy/ctl.sh" \ + "opendance-web-v${VERSION}/deploy/check-access.sh" \ "opendance-web-v${VERSION}/deploy/proxy/README.md" \ "opendance-web-v${VERSION}/deploy/proxy/nginx.conf.example" \ "opendance-web-v${VERSION}/deploy/proxy/web.config.example" \