From 87a1ded2d2ec91a2b911e349329bb8050f606598 Mon Sep 17 00:00:00 2001 From: zijunchiang Date: Tue, 15 Sep 2026 01:00:58 +0800 Subject: [PATCH] =?UTF-8?q?fix(deploy):=20install.sh=20=E5=A4=8D=E5=88=B6?= =?UTF-8?q?=E6=97=B6=E5=90=8C=E6=A0=B7=E8=AF=AF=E6=8E=92=E9=99=A4=E4=BA=86?= =?UTF-8?q?=20storage=20=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户重装后仍报同一错误,且日志显示「复制 web-server … 174 个文件」。 经复现:发布包内 web-server 有 180 个文件(含 src/storage 2 个、 dist/storage 4 个),但 install.sh 复制后只剩 174 个——storage 被剔除了。 根因:上一轮只修了 package-release.sh,install.sh 里**还有一份独立的 copy_tree() 排除规则**,其中 'storage' 同样未锚定。install.sh 的 COPY_EXCLUDES 与 tar 回退分支都把 'storage' 当作任意层级模式, 于是 src/storage 与 dist/storage 在复制进 /opt/opendance 时被剔除。 这是同一类错误在本项目的第三次(.gitignore → package-release.sh → install.sh)。因此这次不再只改模式,而是把语义显式化并加硬校验: - 排除规则拆成三类常量,语义明确: TOP_DIRS 仅顶层:storage data tmp dist-release → 必须锚定 ANY_DIRS 任意层级:node_modules .git coverage .gradle TOP_FILES 仅顶层文件:.env .env.local - rsync 用前导 '/' 锚定;tar 对 TOP_* 只给 './x'(绝不给 '*/storage'), 对 ANY_* 才给 './x' + '*/x' - 复制后硬校验关键产物(dist/main.js、dist/storage/storage.module.js、 dist/app.module.js、admin/index.html),缺失即 die 并指出是排除规则问题 实测: - rsync 路径:180 文件,src/storage 与 dist/storage 均在, 运行时 storage 与 .env 未带入,.env.example 保留 - tar 回退路径(隔离 PATH 屏蔽 rsync 后真跑):同样 180 文件,存储目录完整 - git 检出布局:214 文件,嵌套 admin/node_modules 未带入,本地 .env 未带入 - 对照实验:把锚定改回未锚定 → 复制剩 174 文件 → 守卫立刻报 「缺少关键文件 dist/storage/storage.module.js」并终止,不再拖到启动才炸 --- deploy/install.sh | 55 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 42 insertions(+), 13 deletions(-) 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"