diff --git a/deploy/ctl.sh b/deploy/ctl.sh index bb2f72b..9d9fa03 100755 --- a/deploy/ctl.sh +++ b/deploy/ctl.sh @@ -20,8 +20,9 @@ RUN_DIR="${RUN_DIR:-/run}" PID_FILE="${PID_FILE:-$RUN_DIR/opendance-api.pid}" MAIN_JS="$APP_DIR/web-server/dist/main.js" -die() { printf '\033[1;31m[ctl]\033[0m %s\n' "$*" >&2; exit 1; } -log() { printf '\033[1;34m[ctl]\033[0m %s\n' "$*"; } +die() { printf '\033[1;31m[ctl]\033[0m %s\n' "$*" >&2; exit 1; } +log() { printf '\033[1;34m[ctl]\033[0m %s\n' "$*"; } +warn() { printf '\033[1;33m[ctl]\033[0m %s\n' "$*"; } [ "$(id -u)" -eq 0 ] || die "需要 root 权限" diff --git a/scripts/package-release.sh b/scripts/package-release.sh index 5067f5a..303cb8c 100755 --- a/scripts/package-release.sh +++ b/scripts/package-release.sh @@ -47,13 +47,19 @@ 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 '.env' \ - --exclude '.env.local' \ - --exclude 'admin' \ + --exclude '/storage/' \ + --exclude '/data/' \ + --exclude '/tmp/' \ + --exclude '/.env' \ + --exclude '/.env.local' \ + --exclude '/admin/' \ web-server/ "$STAGE/web-server/" # 管理端构建产物(文档要求 admin/ 与 web-server/ 平级) @@ -77,6 +83,67 @@ cp -r tasks "$STAGE/tasks" 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/nginx/opendance.conf" \ + "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" ; 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 "产物内容:"