diff --git a/deploy/install.sh b/deploy/install.sh index ac8de4c..41115ce 100755 --- a/deploy/install.sh +++ b/deploy/install.sh @@ -151,26 +151,41 @@ esac # 说明:在 git 检出目录直接执行时,web-server/ 里可能已有 200 MB+ 的 # node_modules(数万个小文件)。原实现用 cp -r 全量复制,会长时间无输出, # 看起来像卡死。这里统一排除,运行期依赖稍后由 npm ci 重新安装。 -# .env / .env.local 一并排除:deploy/.env 才是权威配置,源码树里的本地 .env -# 若被复制进来,dotenv 会把它当成第二配置源,造成排障困惑。 -COPY_EXCLUDES=(--exclude=node_modules --exclude=.git --exclude=storage - --exclude=dist-release --exclude=coverage --exclude=.gradle - --exclude=.env --exclude=.env.local) +# 复制排除规则 —— 必须区分「仅顶层」与「任意层级」: +# +# 仅顶层 (TOP_*):运行时产物与本地配置,只会出现在 web-server 根下。 +# 必须锚定!未锚定的 'storage' 会匹配任意层级,把源码目录 +# src/storage/ 与编译产物 dist/storage/ 一起排除掉,导致安装后 +# 启动即 MODULE_NOT_FOUND。本项目已在 .gitignore 与 package-release.sh +# 上各踩过一次,这里是第三处。 +# 任意层级 (ANY_*):依赖与缓存目录,任何位置都不应该被复制。 +# +TOP_DIRS="storage data tmp dist-release" +ANY_DIRS="node_modules .git coverage .gradle" +TOP_FILES=".env .env.local" copy_tree() { local src="$1" dst="$2" mkdir -p "$dst" if command -v rsync >/dev/null 2>&1; then - rsync -a --delete "${COPY_EXCLUDES[@]}" "$src/" "$dst/" + # rsync 模式前导 '/' 表示锚定到传输根目录 + local args=(-a --delete) + local d + for d in $TOP_DIRS; do args+=(--exclude="/$d/"); done + for d in $ANY_DIRS; do args+=(--exclude="$d/"); done + local f + for f in $TOP_FILES; do args+=(--exclude="/$f"); done + rsync "${args[@]}" "$src/" "$dst/" else - # 无 rsync 时用 tar 管道,同样支持排除。 - # 注意:tar 的 --exclude 按完整成员名匹配,只写 "./node_modules" 无法匹配 - # 嵌套的 "./admin/node_modules"(实测会漏掉 admin 下的 122MB 依赖)。 - # 因此每个名字都要同时给出顶层与任意层级的模式。 + # tar 的 --exclude 按完整成员名匹配: + # './storage' 只匹配顶层 + # './x' + '*/x' 才覆盖任意层级(如 ./admin/node_modules) + # 因此 ANY_DIRS 给两个模式,TOP_* 只给 './' 一个模式——绝不能给 '*/storage'。 local tar_excludes=() - for pattern in node_modules .git storage dist-release coverage .gradle .env .env.local; do - tar_excludes+=(--exclude="./$pattern" --exclude="*/$pattern") - done + local d f + for d in $TOP_DIRS; do tar_excludes+=(--exclude="./$d"); done + for d in $ANY_DIRS; do tar_excludes+=(--exclude="./$d" --exclude="*/$d"); done + for f in $TOP_FILES; do tar_excludes+=(--exclude="./$f"); done ( cd "$src" && tar "${tar_excludes[@]}" -cf - . ) | ( cd "$dst" && tar -xf - ) fi } @@ -218,6 +233,20 @@ cp -r "$SRC_DIR/deploy/." "$APP_DIR/deploy/" if [ -f "$SRC_DIR/THIRD_PARTY.md" ]; then cp "$SRC_DIR/THIRD_PARTY.md" "$APP_DIR/"; fi if [ -f "$SRC_DIR/README.txt" ]; then cp "$SRC_DIR/README.txt" "$APP_DIR/"; fi +# ---- 复制结果硬校验 ---- +# 排除规则一旦写错,会在启动时才以 MODULE_NOT_FOUND 的形式暴露, +# 极难定位。这里就地检查关键的编译产物,把问题拦在部署阶段。 +for required in \ + "$APP_DIR/web-server/dist/main.js" \ + "$APP_DIR/web-server/dist/storage/storage.module.js" \ + "$APP_DIR/web-server/dist/app.module.js" \ + "$APP_DIR/admin/index.html" ; do + [ -f "$required" ] || die "复制结果缺少关键文件:$required + 这通常意味着复制时的排除规则误伤了源码目录,请检查 deploy/install.sh 中的 + TOP_DIRS / ANY_DIRS 设置(注意 'storage' 必须锚定到顶层)。" +done +log "复制结果校验通过" + mkdir -p "$STORAGE_DIR" log "设置目录属主($SERVICE_USER)" chown -R "$SERVICE_USER:$SERVICE_USER" "$APP_DIR" "$STORAGE_DIR"