# deploy-ai.sh — tlyq.ai 站点部署脚本 ## 概述 `deploy-ai.sh` 将本地代码部署到 tlyq.ai 云服务器(txjp,IP: 43.133.38.210)。采用"源码上传 → 服务器构建"模式,本地不做生产构建。 **核心流程**:打包源码 → 上传至 txjp → 服务器 `npm install` + `npm run build` → 容器重启生效 ## 用法 ```bash bash scripts/deploy-ai.sh [选项] ``` | 选项 | 说明 | |------|------| | 无参数 | 自动检测:初次构建 or 增量更新(源码快照比对) | | `--force` | 强制完整构建(跳过快照比对) | | `--restart` | 仅重启容器(跳过构建) | | `--init` | 强制初次完整构建 | ## 站点选择 脚本启动后显示交互式菜单: | 编号 | 站点 | 容器 | 构建方式 | |------|------|------|---------| | 1 | www.tlyq.ai | — | Next.js `output: 'export'` 静态站点 | | 2 | cloud.tlyq.ai | — | 纯静态 HTML | | 3 | token.tlyq.ai | — | 纯静态 HTML | | 4 | issue.tlyq.ai | issue-ai | Next.js standalone(Docker) | | 5 | assets.tlyq.ai | assets-ai | Next.js standalone(Docker) | | 6 | oa.tlyq.ai | oa-ai | Next.js standalone(Docker) | ## 部署流程 ### 通用步骤 1. **链接替换**:`localhost` → 正式域名(www/cloud/token 站点;OA 站点额外替换内部 API 地址) 2. **快照比对**:计算本地源码 MD5,与服务器上次快照对比,无变化则跳过构建 3. **打包上传**:tar 打包(排除 `node_modules`、`.next`、`.env` 等),scp 上传至 txjp 4. **解压同步**:rsync 同步源码到目标目录 5. **依赖安装**:服务器 `npm install --prefer-offline` 6. **环境变量**:写入生产环境所需的环境变量到 `.env` 7. **构建**:服务器 `npm run build` 8. **清理**:删除 Alpine musl 残留(sharp、swc) 9. **重启容器**:`docker compose up -d` + `docker compose restart` 10. **快照保存**:保存本次源码 MD5 用于下次比对 ### issue-ai 专属 - 构建前写入 `ASSETS_API_URL=http://assets-ai:3000/api` - 构建前写入 `NEXT_PUBLIC_ASSETS_URL=https://assets.tlyq.ai` - **部署后自动验证 issue→assets API 连通性**(见下方) ### assets-ai 专属 - 构建前写入 `NEXT_PUBLIC_ISSUE_URL=https://issue.tlyq.ai/tickets` - 构建前写入 `ISSUE_API_URL=http://issue-ai:3000/api` ### OA 专属 - 替换三类 URL:前端导航卡片(`localhost` → 生产域名)、服务端内部 API(`localhost` → 容器名)、退出登录重定向 ## 部署后验证 ### 部署后 API 连通性检查 部署 issue-ai 和 assets-ai 后自动执行双向连通性检查,均带 **重试机制**(3 次、间隔 10s),应对同时部署两个站点时目标容器暂不可达的情况。 | 部署站点 | 检查方向 | 使用 Key | 目标 URL | |---------|---------|---------|----------| | issue-ai | issue → assets | `ASSETS_API_KEY` | `ASSETS_API_URL/assets?pageSize=1` | | assets-ai | assets → issue | `ISSUE_API_KEY` | `ISSUE_API_URL/tickets?pageSize=1` | 流程: 1. 将连通性检查脚本发送到 txjp 宿主机 2. `docker cp` 到对应容器内 3. 容器内 Node.js 发起 HTTP 请求,失败自动重试(最多 3 次,间隔 10s) 4. 返回 200 → 通过;全部重试仍失败 → **部署失败退出** **错误输出示例**: ``` [✗] issue→assets API 连通性检查失败!请检查 ASSETS_API_KEY 是否在 assets-ai 中注册 连接失败: HTTP 401 (第1次) 10s 后重试... 连通失败: HTTP 401 (第2次) 已达最大重试次数 ``` ## 排除文件 打包时排除以下目录和文件,不上传到服务器: | 类别 | 排除项 | |------|--------| | 依赖 | `node_modules` | | 构建产物 | `.next`、`out`、`data` | | 环境配置 | `.env`、`.env.local`、`.env.development`、`.env.production` | | 版本控制 | `.git` | | 文档 | `docs` | | 报告 | `reports/*.docx` | | macOS | `._*` | 部署后服务器上的 `.env.local` / `.env.development` / `.env.production` 会被自动删除,防止覆盖 `.env` 中的生产配置。 ## 源码快照机制 脚本计算本地源码文件的组合 MD5,保存到服务器的 `/tmp/.snapshot.{site}.md5`。下次部署时比对,若无变化则跳过构建仅重启容器。`--force` 可绕过此机制。 快照包含:所有源码文件(排除 `node_modules`、`.next`、`out`、`data`、`reports`、`docs`、`.env*`、`.git`)。 ## 故障排查 ### 构建失败 ```bash # 查看服务器构建日志 ssh txjp "tail -100 /tmp/build_output.txt" ``` ### 容器启动失败 ```bash ssh txjp "docker logs {container}" ``` ### 连通性检查失败 ```bash # 手动验证 issue→assets 连通性 ssh txjp "docker exec issue-ai sh -c 'cd /app && NODE_PATH=/app/node_modules node -e \" const http = require(\\\"http\\\"); const url = process.env.ASSETS_API_URL + \\\"/assets?pageSize=1\\\"; const key = process.env.ASSETS_API_KEY || \\\"\\\"; http.get(url, {headers:{\\\"Authorization\\\":\\\"Bearer \\\"+key}}, (res) => { console.log(\\\"status:\\\", res.statusCode); process.exit(res.statusCode === 200 ? 0 : 1); }).on(\\\"error\\\", (e) => { console.error(e.message); process.exit(1); }); \"'" # 确认 assets-ai 的 api_keys 表中是否有对应的 key ssh txjp "docker exec assets-ai node -e \" const db = require('better-sqlite3')('/app/data/assets.db'); const rows = db.prepare('SELECT id, name, is_active FROM api_keys').all(); rows.forEach(r => console.log(JSON.stringify(r))); \"" ``` ## 注意事项 - **跨容器凭据**:禁止在代码或配置中硬编码另一服务的密码,通过运行时读取源容器环境变量获取 - **共享 JWT 密钥**:所有 `.tlyq.ai` 子站点的 `JWT_SECRET` 和 `COOKIE_DOMAIN` 必须一致 - **日期时区**:系统统一 UTC+8,禁止使用 `toISOString()` 和 `datetime('now')` - **nginx 重载**:容器重启后需 `docker restart nginx-ai` 清除 DNS 缓存 - **新增 npm 依赖**:需重建 Docker 镜像(`docker compose build --no-cache`),因为容器内 `/app/node_modules/` 来自镜像构建时