fix(deploy): install.sh 复制时同样误排除了 storage 目录
用户重装后仍报同一错误,且日志显示「复制 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」并终止,不再拖到启动才炸
This commit is contained in:
+42
-13
@@ -151,26 +151,41 @@ esac
|
|||||||
# 说明:在 git 检出目录直接执行时,web-server/ 里可能已有 200 MB+ 的
|
# 说明:在 git 检出目录直接执行时,web-server/ 里可能已有 200 MB+ 的
|
||||||
# node_modules(数万个小文件)。原实现用 cp -r 全量复制,会长时间无输出,
|
# node_modules(数万个小文件)。原实现用 cp -r 全量复制,会长时间无输出,
|
||||||
# 看起来像卡死。这里统一排除,运行期依赖稍后由 npm ci 重新安装。
|
# 看起来像卡死。这里统一排除,运行期依赖稍后由 npm ci 重新安装。
|
||||||
# .env / .env.local 一并排除:deploy/.env 才是权威配置,源码树里的本地 .env
|
# 复制排除规则 —— 必须区分「仅顶层」与「任意层级」:
|
||||||
# 若被复制进来,dotenv 会把它当成第二配置源,造成排障困惑。
|
#
|
||||||
COPY_EXCLUDES=(--exclude=node_modules --exclude=.git --exclude=storage
|
# 仅顶层 (TOP_*):运行时产物与本地配置,只会出现在 web-server 根下。
|
||||||
--exclude=dist-release --exclude=coverage --exclude=.gradle
|
# 必须锚定!未锚定的 'storage' 会匹配任意层级,把源码目录
|
||||||
--exclude=.env --exclude=.env.local)
|
# 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() {
|
copy_tree() {
|
||||||
local src="$1" dst="$2"
|
local src="$1" dst="$2"
|
||||||
mkdir -p "$dst"
|
mkdir -p "$dst"
|
||||||
if command -v rsync >/dev/null 2>&1; then
|
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
|
else
|
||||||
# 无 rsync 时用 tar 管道,同样支持排除。
|
# tar 的 --exclude 按完整成员名匹配:
|
||||||
# 注意:tar 的 --exclude 按完整成员名匹配,只写 "./node_modules" 无法匹配
|
# './storage' 只匹配顶层
|
||||||
# 嵌套的 "./admin/node_modules"(实测会漏掉 admin 下的 122MB 依赖)。
|
# './x' + '*/x' 才覆盖任意层级(如 ./admin/node_modules)
|
||||||
# 因此每个名字都要同时给出顶层与任意层级的模式。
|
# 因此 ANY_DIRS 给两个模式,TOP_* 只给 './' 一个模式——绝不能给 '*/storage'。
|
||||||
local tar_excludes=()
|
local tar_excludes=()
|
||||||
for pattern in node_modules .git storage dist-release coverage .gradle .env .env.local; do
|
local d f
|
||||||
tar_excludes+=(--exclude="./$pattern" --exclude="*/$pattern")
|
for d in $TOP_DIRS; do tar_excludes+=(--exclude="./$d"); done
|
||||||
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 - )
|
( cd "$src" && tar "${tar_excludes[@]}" -cf - . ) | ( cd "$dst" && tar -xf - )
|
||||||
fi
|
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/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
|
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"
|
mkdir -p "$STORAGE_DIR"
|
||||||
log "设置目录属主($SERVICE_USER)"
|
log "设置目录属主($SERVICE_USER)"
|
||||||
chown -R "$SERVICE_USER:$SERVICE_USER" "$APP_DIR" "$STORAGE_DIR"
|
chown -R "$SERVICE_USER:$SERVICE_USER" "$APP_DIR" "$STORAGE_DIR"
|
||||||
|
|||||||
Reference in New Issue
Block a user