现象:服务器未安装 Assimp,上传 PMX 压缩包只得到 GLB 直出:导出失败(ENOENT):spawn assimp ENOENT glTF 直出:导出失败(ENOENT):spawn assimp ENOENT 用户看不出缺什么、怎么装,也不知道只传 glb/vrm 其实不需要它。 修复: 1. 新增 src/common/assimp.ts:按 ASSIMP_BIN -> PATH -> /usr/bin、/usr/local/bin 等常见目录定位可执行文件(只认文件,不认同名目录),转换前先预检; 找不到就一次性给出各发行版安装命令、查过的路径与替代办法, 不再打两条 ENOENT。动作预处理(FBX/glTF -> BVH)复用同一文案。 2. 新增 deploy/assimp-lib.sh:install.sh 按发行版自动安装 (apt-get / dnf / yum / zypper / apk / pacman),装在非常规目录时把绝对 路径写入 deploy/.env 的 ASSIMP_BIN;装不上只告警不中止,可用 SKIP_ASSIMP_INSTALL=1 跳过。check-access.sh 自检增加 Assimp 一行。 3. /health 增加 assimp.path;管理端「概览 -> 服务健康」显示 Assimp 状态, 不可用时给出安装提示。 4. 新增 test/assimp-locate.spec.ts 13 项;强化「assimp 不存在」用例断言安装命令。 测试规模 12 文件 / 193 项。 5. 版本 0.2.2,README(.md/.txt)/DSH.md 同步(前置要求、FAQ、环境变量说明)。
267 lines
10 KiB
Bash
Executable File
267 lines
10 KiB
Bash
Executable File
#!/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
|
||
ASSIMP_MISSING=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
|
||
|
||
# ---------- 6.5 可选依赖 Assimp ----------
|
||
title "可选依赖 Assimp(.pmx/.fbx 模型转换、FBX/glTF 动作预处理)"
|
||
if [ -f "$(dirname "$0")/assimp-lib.sh" ]; then
|
||
# shellcheck source=deploy/assimp-lib.sh
|
||
. "$(dirname "$0")/assimp-lib.sh"
|
||
ASSIMP_PATH="$(opendance_assimp_resolve || true)"
|
||
if [ -n "$ASSIMP_PATH" ]; then
|
||
ok "Assimp 可用:$ASSIMP_PATH($("$ASSIMP_PATH" version 2>/dev/null | head -1))"
|
||
else
|
||
warn "未安装 Assimp:上传 .pmx / .fbx(或含它们的压缩包)会报转换失败"
|
||
info "安装:$(opendance_assimp_install_cmd)"
|
||
info "只上传 .glb / .vrm 模型与 .bvh / .vmd 动作则不受影响"
|
||
ASSIMP_MISSING=1
|
||
fi
|
||
else
|
||
warn "未找到 deploy/assimp-lib.sh,跳过 Assimp 检查"
|
||
fi
|
||
|
||
# ---------- 7. 结论 ----------
|
||
title "结论"
|
||
|
||
if [ "${ASSIMP_MISSING:-0}" = "1" ]; then
|
||
c_yel " 提示:未安装 Assimp,.pmx / .fbx 模型转换与 FBX / glTF 动作预处理不可用(与访问无关)。"
|
||
echo " 安装:$(opendance_assimp_install_cmd 2>/dev/null || echo '用发行版包管理器安装 assimp')"
|
||
echo
|
||
fi
|
||
|
||
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
|