Files
opendance-web/scripts/package-release.sh
T
opendance da03777995 fix(models): Assimp 缺失时给出可操作报错,部署脚本自动安装
现象:服务器未安装 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、环境变量说明)。
2026-09-16 02:55:32 +08:00

181 lines
7.8 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# ============================================================
# 打包发布产物:opendance-web-v0.2.2.tar.gz
#
# 产物结构(Linux 业务服务部署,不使用 Docker、不部署反向代理):
# web-server/ NestJS 源码 + 构建产物
# admin/ React 管理端构建产物
# deploy/ install.sh / ctl.sh / uninstall.sh / .env.example
# env-lib.sh (.env 语法预检) / assimp-lib.sh (Assimp 定位安装)
# init-env.sh (.env 引导)
# systemd|openrc|initd / proxy(反代说明文档,仅文档不部署)
# contracts/ 当前版本契约快照
# THIRD_PARTY.md 开源项目声明
# README.md 使用说明(Markdown, 仓库首页展示的就是它)
# README.txt 使用说明(纯文本同内容版本)
#
# 用法:./scripts/package-release.sh [版本号]
# ============================================================
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$REPO_ROOT"
VERSION="${1:-0.2.2}"
NAME="opendance-web-v${VERSION}"
OUT_DIR="$REPO_ROOT/dist-release"
STAGE="$OUT_DIR/$NAME"
log() { printf '\033[1;34m[package]\033[0m %s\n' "$*"; }
die() { printf '\033[1;31m[error]\033[0m %s\n' "$*" >&2; exit 1; }
# ---------- 1. 契约一致性 ----------
log "校验契约版本一致性..."
( cd web-server && node scripts/check-contracts.mjs )
# ---------- 2. 后端构建 ----------
log "构建后端..."
( cd web-server && npm run build )
# ---------- 3. 管理端构建 ----------
if [ -d web-server/admin/node_modules ]; then
log "构建管理端..."
( cd web-server/admin && npm run build )
else
log "警告:web-server/admin/node_modules 不存在,跳过管理端构建(产物将缺少 admin/)"
fi
# ---------- 4. 组织产物目录 ----------
log "整理产物目录 $STAGE ..."
rm -rf "$STAGE"
mkdir -p "$STAGE"
# 后端源码 + 构建产物(排除依赖、测试、存储、密钥)
# 排除模式**必须锚定到 web-server 根目录**(前导 /)。
# 否则 rsync 的 'storage' 会匹配任意层级的同名目录,把源码目录
# src/storage/ 与编译产物 dist/storage/ 一并删掉——发布包会缺少整个
# storage 模块,服务启动即 MODULE_NOT_FOUND。这是本项目第二次踩到
# 「storage 误伤源码目录」这个坑(第一次是 .gitignore)。
rsync -a --delete \
--exclude 'node_modules' \
--exclude '/storage/' \
--exclude '/data/' \
--exclude '/tmp/' \
--exclude '/.env' \
--exclude '/.env.local' \
--exclude '/admin/' \
web-server/ "$STAGE/web-server/"
# 管理端构建产物(文档要求 admin/ 与 web-server/ 平级)
if [ -d web-server/admin/dist ]; then
mkdir -p "$STAGE/admin"
cp -r web-server/admin/dist/. "$STAGE/admin/"
else
log "警告:未找到 web-server/admin/dist,产物不含 admin/"
fi
cp -r deploy "$STAGE/deploy"
# 部署配置里绝不能带真实 .env(含数据库口令)
rm -f "$STAGE/deploy/.env"
rm -rf "$STAGE/deploy"/systemd/*.bak "$STAGE/deploy"/openrc/*.bak "$STAGE/deploy"/initd/*.bak
cp -r contracts "$STAGE/contracts"
cp THIRD_PARTY.md README.txt README.md project.yaml DSH.md "$STAGE/"
cp -r tasks "$STAGE/tasks"
# ---------- 5. 打包 ----------
log "生成压缩包..."
( cd "$OUT_DIR" && tar -czf "$NAME.tar.gz" "$NAME" )
# ---------- 6. 产物完整性校验 ----------
# 打包脚本本身的错误(如排除模式写错)会静默产出残缺的发布包,
# 直到目标机启动时才暴露。这里做一次硬校验:src 下每个 .ts 都必须在
# 包内有对应的 dist/*.js,且关键文件齐全。
log "校验产物完整性…"
MANIFEST_TMP=$(tar -tzf "$OUT_DIR/$NAME.tar.gz")
fail=0
# 6.1 src → dist 一一对应(能抓到整目录被误排除这类问题)
# 注意 nest build 的 sourceRoot 是 src,产物会**去掉 src/ 前缀**:
# web-server/src/storage/storage.service.ts → web-server/dist/storage/storage.service.js
while IFS= read -r tsfile; do
rel="${tsfile#src/}"
jsfile="opendance-web-v${VERSION}/web-server/dist/${rel%.ts}.js"
if ! grep -qx "$jsfile" <<< "$MANIFEST_TMP"; then
echo " ✗ 缺少编译产物:$jsfile(源:$tsfile)" >&2
fail=1
fi
done < <(cd web-server && find src -name '*.ts' ! -name '*.spec.ts')
[ "$fail" -eq 0 ] && echo " ✓ src 下所有 .ts 均有对应 dist/*.js"
# 6.2 关键文件
for required in \
"opendance-web-v${VERSION}/web-server/dist/main.js" \
"opendance-web-v${VERSION}/web-server/src/storage/storage.service.ts" \
"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}/web-server/dist/preprocess/vmd-parser.js" \
"opendance-web-v${VERSION}/web-server/dist/preprocess/mmd-pose.js" \
"opendance-web-v${VERSION}/web-server/dist/preprocess/mmd-skeleton.js" \
"opendance-web-v${VERSION}/web-server/scripts/inspect-vmd.mjs" \
"opendance-web-v${VERSION}/web-server/scripts/import-default-model.mjs" \
"opendance-web-v${VERSION}/web-server/scripts/reanalyze-models.mjs" \
"opendance-web-v${VERSION}/web-server/dist/common/zip-reader.js" \
"opendance-web-v${VERSION}/web-server/dist/models/model-convert.js" \
"opendance-web-v${VERSION}/web-server/dist/models/glb-textures.js" \
"opendance-web-v${VERSION}/web-server/dist/migrations/1789450000000-AddModelConversionFields.js" \
"opendance-web-v${VERSION}/web-server/dist/migrations/1789460000000-AddModelStats.js" \
"opendance-web-v${VERSION}/web-server/dist/models/model-inspector.js" \
"opendance-web-v${VERSION}/web-server/dist/models/model-thumbnail.js" \
"opendance-web-v${VERSION}/web-server/dist/models/vrm-writer.js" \
"opendance-web-v${VERSION}/web-server/dist/models/gltf-packer.js" \
"opendance-web-v${VERSION}/deploy/init-env.sh" \
"opendance-web-v${VERSION}/deploy/env-lib.sh" \
"opendance-web-v${VERSION}/deploy/assimp-lib.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" \
"opendance-web-v${VERSION}/deploy/systemd/opendance-api.service" \
"opendance-web-v${VERSION}/contracts/VERSION" \
"opendance-web-v${VERSION}/THIRD_PARTY.md" \
"opendance-web-v${VERSION}/README.txt" \
"opendance-web-v${VERSION}/README.md" ; do
if ! grep -qx "$required" <<< "$MANIFEST_TMP"; then
echo " ✗ 缺少关键文件:$required" >&2
fail=1
fi
done
# 6.3 不得包含的东西(模式必须精确,否则会误伤 src/storage、.env.example)
PREFIX="opendance-web-v${VERSION}"
check_forbidden() {
local label="$1" pattern="$2"
if grep -qE "$pattern" <<< "$MANIFEST_TMP"; then
echo " ✗ 产物中混入了不应包含的内容($label)" >&2
grep -E "$pattern" <<< "$MANIFEST_TMP" | head -3 | sed 's/^/ /' >&2
fail=1
fi
}
check_forbidden "node_modules" "node_modules"
check_forbidden "真实 .env" "^${PREFIX}/(deploy|web-server)/\.env$"
check_forbidden "运行时存储目录" "^${PREFIX}/web-server/storage/"
check_forbidden "keystore" "\.jks$|keystore\.properties$"
[ "$fail" -eq 0 ] && echo " ✓ 无 node_modules / 真实 .env / 运行时存储 / keystore"
if [ "$fail" -ne 0 ]; then
rm -f "$OUT_DIR/$NAME.tar.gz"
die "产物完整性校验失败,已删除有问题的压缩包"
fi
log "完成:$OUT_DIR/$NAME.tar.gz"
echo
echo "产物内容:"
# 先把清单读入变量,避免 tar 因管道提前关闭(SIGPIPE)而误报 "write error"
MANIFEST=$(tar -tzf "$OUT_DIR/$NAME.tar.gz")
echo "$MANIFEST" | sed -n '1,20p'
echo " ...(共 $(echo "$MANIFEST" | wc -l) 项,$(du -h "$OUT_DIR/$NAME.tar.gz" | cut -f1))"
echo
echo "部署方式:解压后进入 deploy/,复制 .env.example 为 .env 并填写数据库信息,执行 ./install.sh"