fix deploy env bom handling
This commit is contained in:
31
docs/technical/JENKINS_DEPLOY_ENV_BOM_FIX_2026-04-25.md
Normal file
31
docs/technical/JENKINS_DEPLOY_ENV_BOM_FIX_2026-04-25.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# 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`。
|
||||
@@ -99,7 +99,7 @@ scripts/jenkins-deploy-release.sh \
|
||||
|
||||
如果 `RUN_DEPLOY_HOOKS_WITH_SUDO=true`,第 1 步和第 4 步会改为 `sudo -n` 调用;这要求 Jenkins 运行用户提前配置免密 sudo,否则部署会直接失败,不会进入交互式密码提示。
|
||||
|
||||
这样可以满足“发布文件直接覆盖”的要求,同时保留部署目录里像 `spacetimedb-data/`、`logs/`、`run/` 这类运行态目录,不会因为部署被整体删除。发布白名单内的 `.env`、`.env.local` 仍会以构建产物中的文件为准。
|
||||
这样可以满足“发布文件直接覆盖”的要求,同时保留部署目录里像 `spacetimedb-data/`、`logs/`、`run/` 这类运行态目录,不会因为部署被整体删除。发布白名单内的 `.env`、`.env.local` 仍会以构建产物中的文件为准;部署脚本会在启动 hook 前移除这些环境文件中的 UTF-8 BOM 与 CRLF,避免 `start.sh` 在 Bash 下把首行变量名误解析成命令。
|
||||
|
||||
### 4.3 构建并部署
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
- [CREATION_HUB_CARD_ACTIONS_2026-04-22.md](./CREATION_HUB_CARD_ACTIONS_2026-04-22.md):冻结创作中心作品卡“体验 / 删除”入口的最小落地语义,明确 RPG 已发布作品软删除、卡片直达运行时,以及暂不扩草稿 / 拼图删除契约。
|
||||
- [CREATION_CATEGORY_OPENING_TIMEOUT_GUARD_FIX_2026-04-22.md](./CREATION_CATEGORY_OPENING_TIMEOUT_GUARD_FIX_2026-04-22.md):记录创作中心点击类别后长时间停留在“正在开启”的根因与修复口径,收口前端创建会话启动超时、中文错误提示以及 Big Fish / 拼图代理上游超时兜底。
|
||||
- [JENKINS_RUST_BUILD_DEPLOY_PIPELINES_2026-04-23.md](./JENKINS_RUST_BUILD_DEPLOY_PIPELINES_2026-04-23.md):冻结 Jenkins `构建 / 部署 / 构建并部署` 三条流水线的职责、版本号传递、上游触发门禁、本地目录部署脚本与 `/home/ubuntu/Genarrative-deploy/` 覆盖策略。
|
||||
- [JENKINS_DEPLOY_ENV_BOM_FIX_2026-04-25.md](./JENKINS_DEPLOY_ENV_BOM_FIX_2026-04-25.md):记录 Jenkins 部署时 `.env.local` 首行 UTF-8 BOM 导致 `start.sh` 加载失败的根因,并冻结发布包构建、部署脚本和启动脚本的环境文件净化规则。
|
||||
- [RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md](./RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md):冻结 Rust 本地一键联调脚本与 Ubuntu 发布包构建脚本的执行口径,覆盖 `npm run dev:rust`、`npm run build:rust:ubuntu`、Vite release、Linux `api-server`、SpacetimeDB wasm、启动停止脚本、默认 scp 上传和安全清库开关。
|
||||
- [RUST_API_SERVER_ROUTE_INDEX_2026-04-22.md](./RUST_API_SERVER_ROUTE_INDEX_2026-04-22.md):记录当前 Rust `api-server` 已挂载的 101 条 Axum 路由,并补充管理后台入口与管理接口索引,按 auth、assets、runtime、custom world、story、generated path 等挂载面归类,用于对照 Node 能力基线与切流 smoke 清单。
|
||||
- [BACKEND_REWRITE_CROSS_CUTTING_GOVERNANCE_2026-04-22.md](./BACKEND_REWRITE_CROSS_CUTTING_GOVERNANCE_2026-04-22.md):冻结后端重写收口阶段的横向治理规则,覆盖 TypeScript contract 到 Rust DTO 映射、SpacetimeDB schema 演进、大对象 / workflow cache 存储边界和文档维护门禁。
|
||||
|
||||
@@ -116,9 +116,9 @@ npm run deploy:rust:remote
|
||||
3. 使用 Vite 构建前端 release 到目标目录的 `web/`。
|
||||
4. 执行 `cargo build -p api-server --release --target x86_64-unknown-linux-gnu --manifest-path server-rs/Cargo.toml`,并把 `api-server` 复制到目标目录。
|
||||
5. 执行 `cargo build -p spacetime-module --release --target wasm32-unknown-unknown --manifest-path server-rs/Cargo.toml`,并把 `spacetime_module.wasm` 复制到目标目录。
|
||||
6. 把仓库根目录的 `.env` 与 `.env.local` 分别复制到目标目录根部和目标目录的 `web/` 下。
|
||||
6. 把仓库根目录的 `.env` 与 `.env.local` 分别复制到目标目录根部和目标目录的 `web/` 下;复制后统一移除 UTF-8 BOM 与 CRLF,避免目标服务器 Bash 加载环境文件失败。
|
||||
7. 在目标目录写入 `web-server.mjs`,用于托管 `web/` 并把 `/api/*`、`/generated-*`、`/healthz` 反代到本包内的 `api-server`。
|
||||
8. 在目标目录写入 `start.sh` 与 `stop.sh`;`start.sh` 会先加载发布目录根部的 `.env`、`.env.local`,再回退到构建时通过 `--database`、`--api-port`、`--web-port`、`--spacetime-host`、`--spacetime-port` 写入的默认值,并默认导出 `NO_COLOR=1` 与 `CARGO_TERM_COLOR=never`,避免 ANSI 控制码写入日志文件;如果以 `--clear-database` 启动,则内部 `spacetime publish` 会追加 `-c=on-conflict`,仅在 schema 冲突时删除旧模块数据。
|
||||
8. 在目标目录写入 `start.sh` 与 `stop.sh`;`start.sh` 会先按 `KEY=value` 子集加载发布目录根部的 `.env`、`.env.local`,兼容 UTF-8 BOM 与 CRLF,再回退到构建时通过 `--database`、`--api-port`、`--web-port`、`--spacetime-host`、`--spacetime-port` 写入的默认值,并默认导出 `NO_COLOR=1` 与 `CARGO_TERM_COLOR=never`,避免 ANSI 控制码写入日志文件;如果以 `--clear-database` 启动,则内部 `spacetime publish` 会追加 `-c=on-conflict`,仅在 schema 冲突时删除旧模块数据。
|
||||
9. 默认执行 `scp -r -i ~\.ssh\dsk.pem build/<timestamp> ubuntu@82.157.175.59:/home/ubuntu/genarrative/` 上传发布包。
|
||||
|
||||
发布包结构:
|
||||
@@ -160,10 +160,11 @@ cd build/<timestamp>
|
||||
|
||||
1. 构建脚本会把仓库根目录已有的 `.env`、`.env.local` 一并复制进发布包,因此运行前必须确认这些文件内容适合被带入目标环境。
|
||||
2. 如果仓库根目录不存在 `.env` 或 `.env.local`,脚本会打印跳过日志,但不会因此失败;此时 `start.sh` 仅使用构建时写入的默认值与运行时显式传入的环境变量。
|
||||
3. `start.sh` 默认不追加清理参数;只有显式执行 `./start.sh --clear-database` 才追加 `-c=on-conflict`,在 schema 冲突时清理旧模块数据后重发。
|
||||
4. `start.sh` 使用 `spacetime publish --bin-path spacetime_module.wasm --yes` 发布当前包内 wasm;清库模式下会追加 `-c=on-conflict`,仅在 schema 冲突时删除旧模块数据。
|
||||
5. 当前脚本是单目录进程启动方案,不替代生产 systemd、Nginx、TLS、日志轮转与守护进程配置。
|
||||
6. 如只需要本地生成发布包,可传 `--skip-upload` 跳过默认 scp 上传。
|
||||
3. `start.sh` 只解析合法 `KEY=value` 环境行,支持不加引号、双引号和单引号;不执行复杂 shell 表达式,避免把环境文件变成脚本入口。
|
||||
4. `start.sh` 默认不追加清理参数;只有显式执行 `./start.sh --clear-database` 才追加 `-c=on-conflict`,在 schema 冲突时清理旧模块数据后重发。
|
||||
5. `start.sh` 使用 `spacetime publish --bin-path spacetime_module.wasm --yes` 发布当前包内 wasm;清库模式下会追加 `-c=on-conflict`,仅在 schema 冲突时删除旧模块数据。
|
||||
6. 当前脚本是单目录进程启动方案,不替代生产 systemd、Nginx、TLS、日志轮转与守护进程配置。
|
||||
7. 如只需要本地生成发布包,可传 `--skip-upload` 跳过默认 scp 上传。
|
||||
|
||||
目标服务器最小要求:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user