# Jenkins 部署环境文件 BOM 修复 日期:`2026-04-25` ## 1. 问题 Jenkins 部署阶段执行固定目录内的 `start.sh` 时失败: ```text /var/lib/jenkins/deploy/Genarrative/.env.local: line 1: VITE_LLM_BASE_URL=...: No such file or directory ``` 根因是 `.env.local` 第一行包含 UTF-8 BOM。旧版 `start.sh` 直接 `source .env.local`,BOM 会成为变量名前缀,Bash 无法按赋值语句解析,进而把整行当作命令执行。日志末尾的 sudo 提示只是 hook 执行失败后的兜底提示,不是本次失败的真实根因。 ## 2. 修复口径 1. 发布包构建脚本复制 `.env`、`.env.local` 到发布目录和 `web/` 目录后,统一移除 UTF-8 BOM 与 CRLF。 2. Jenkins 部署脚本在移动发布产物前后,再次净化发布目录和固定部署目录中的 `.env`、`.env.local`,兼容已经构建出来但尚未部署成功的旧发布包。 3. 新生成的 `start.sh` 不再直接 `source` 环境文件,而是按 `KEY=value` 子集解析、导出合法变量,并跳过空行、注释和不合法行。 4. `start.sh` 仍保留 `.env` 先于 `.env.local` 的加载顺序,后加载的 `.env.local` 可以覆盖默认配置。 ## 3. 运行边界 1. 环境文件应保持 UTF-8 文本,允许 UTF-8 BOM 和 CRLF,但部署脚本会在发布目录中消除它们。 2. 环境变量名必须符合 `[A-Za-z_][A-Za-z0-9_]*`。 3. 值支持不加引号、双引号和单引号;复杂 shell 表达式不会执行,避免把环境文件变成脚本入口。 4. 业务密钥仍通过目标服务器环境变量或发布目录 `.env.local` 管理,不写入 Jenkinsfile。 ## 4. 失败现场恢复 如果 Jenkins 已经生成了失败版本,可以在拉取本次脚本修复后直接重跑部署流水线。`scripts/jenkins-deploy-release.sh` 会在执行新版本 `start.sh` 前净化已有发布目录,因此不要求手工编辑服务器上的 `.env.local`。