diff --git a/Dockerfile b/Dockerfile index 835a942..cb98b26 100644 --- a/Dockerfile +++ b/Dockerfile @@ -47,6 +47,7 @@ COPY --from=builder /app/.next/static ./.next/static COPY --from=builder /app/config.json ./ COPY --from=builder /app/.version ./ COPY --from=builder /app/.env ./ +COPY --from=builder /app/docker-compose.yml ./ # 给 nextjs 用户授权整个目录 RUN chown -R nextjs:nodejs /app diff --git a/lib/update.ts b/lib/update.ts index b18f6a8..674d9a3 100644 --- a/lib/update.ts +++ b/lib/update.ts @@ -333,26 +333,25 @@ async function checkDockerAvailable(): Promise<{ ok: boolean; message: string }> * 执行 Docker 容器更新 * @param onLog 日志回调 */ +const REGISTRY_IMAGE = "git.hanhan.ltd/recipe_tool/recipe_tool:latest"; + export async function executeUpdate( onLog: LogCallback = noopLog ): Promise<{ success: boolean; message: string }> { - const tmpDir = path.join( - process.env.TEMP || "/tmp", - `recipe_tool_update_${Date.now()}` - ); + const cwd = process.cwd(); // /app(容器内工作目录,含 docker-compose.yml) + const token = process.env.ONEDEV_ACCESS_TOKEN || ""; consoleLog("执行", "=== 开始执行更新 ==="); - consoleLog("执行", `临时目录: ${tmpDir}`); + consoleLog("执行", `工作目录: ${cwd}, 镜像: ${REGISTRY_IMAGE}`); - // 包装 onLog,将输出行实时推送到 SSE const logLine = (line: string) => onLog(line); try { onLog("开始更新流程"); // 0. 检查 Docker 是否可用 - onLog("[0/5] 检查 Docker 环境"); - consoleLog("执行", "步骤 0/5 — 检查 Docker 环境"); + onLog("[0/4] 检查 Docker 环境"); + consoleLog("执行", "步骤 0/4 — 检查 Docker 环境"); const dockerCheck = await checkDockerAvailable(); if (!dockerCheck.ok) { onLog(`✗ ${dockerCheck.message}`); @@ -361,19 +360,32 @@ export async function executeUpdate( } onLog("✓ Docker 环境正常"); - // 1. 拉取最新代码 - const url = getAuthenticatedRemoteUrl(); - onLog("[1/5] 克隆最新代码"); - consoleLog("执行", "步骤 1/5 — 克隆代码"); - await execAsync("git", ["clone", "--depth", "1", url, tmpDir], "git-clone", { - timeout: 120000, + // 1. 登录 OneDev 镜像仓库并拉取最新镜像 + onLog("[1/4] 登录镜像仓库"); + consoleLog("执行", "步骤 1/4 — 登录 OneDev 镜像仓库"); + if (token) { + await execAsync( + "sh", + ["-c", `echo "${token}" | docker login git.hanhan.ltd -u access-token --password-stdin`], + "docker-login", + { timeout: 15000, onLine: logLine } + ); + } else { + onLog("⚠ 未配置 ONEDEV_ACCESS_TOKEN,尝试匿名拉取"); + consoleLog("执行", "未配置 access token,尝试匿名拉取"); + } + + onLog("[1/4] 拉取新镜像"); + consoleLog("执行", "步骤 1/4 — 拉取镜像"); + await execAsync("docker", ["pull", REGISTRY_IMAGE], "docker-pull", { + timeout: 300000, onLine: logLine, }); - onLog("✓ 代码拉取完成"); + onLog("✓ 镜像拉取完成"); - // 2. 生成 .env 文件 - onLog("[2/5] 准备环境变量"); - consoleLog("执行", "步骤 2/5 — 准备环境变量"); + // 2. 生成 .env 配置文件(供 docker-compose 解析 ${VAR}) + onLog("[2/4] 准备环境变量"); + consoleLog("执行", "步骤 2/4 — 准备环境变量"); const envContent = [ `DB_HOST=${process.env.DB_HOST || "127.0.0.1"}`, `DB_PORT=${process.env.DB_PORT || "3306"}`, @@ -383,28 +395,19 @@ export async function executeUpdate( `AI_API_KEY=${process.env.AI_API_KEY || ""}`, `AI_BASE_URL=${process.env.AI_BASE_URL || "https://api.openai.com/v1"}`, `AI_MODEL=${process.env.AI_MODEL || "gpt-3.5-turbo"}`, - `ONEDEV_ACCESS_TOKEN=${process.env.ONEDEV_ACCESS_TOKEN || ""}`, + `ONEDEV_ACCESS_TOKEN=${token}`, + `GIT_REMOTE_URL=${process.env.GIT_REMOTE_URL || "https://git.hanhan.ltd/recipe_tool.git"}`, ].join("\n"); - fs.writeFileSync(path.join(tmpDir, ".env"), envContent, "utf-8"); + fs.writeFileSync(path.join(cwd, ".env"), envContent, "utf-8"); consoleLog("执行", `.env 已写入 ${envContent.split("\n").length} 行`); onLog("✓ 环境变量已准备"); - // 3. 构建新镜像(实时输出构建日志) - onLog("[3/5] 构建新 Docker 镜像"); - consoleLog("执行", "步骤 3/5 — 构建镜像(可能耗时较长)"); - await execAsync("docker-compose", ["-p", "recipe_tool", "build"], "docker-build", { - cwd: tmpDir, - timeout: 600000, - onLine: logLine, - }); - onLog("✓ 镜像构建完成"); - - // 4. 停止旧容器(释放端口),忽略失败 - onLog("[4/5] 停止旧容器"); - consoleLog("执行", "步骤 4/5 — 停止旧容器"); + // 3. 停止旧容器(释放端口),忽略失败 + onLog("[3/4] 停止旧容器"); + consoleLog("执行", "步骤 3/4 — 停止旧容器"); try { await execAsync("docker-compose", ["-p", "recipe_tool", "down", "--remove-orphans"], "docker-down", { - cwd: tmpDir, + cwd, timeout: 30000, onLine: logLine, }); @@ -413,11 +416,11 @@ export async function executeUpdate( } onLog("✓ 旧容器已停止"); - // 5. 启动新容器 - onLog("[5/5] 启动新容器"); - consoleLog("执行", "步骤 5/5 — 启动新容器"); + // 4. 启动新容器(使用拉取的镜像) + onLog("[4/4] 启动新容器"); + consoleLog("执行", "步骤 4/4 — 启动新容器"); await execAsync("docker-compose", ["-p", "recipe_tool", "up", "-d"], "docker-up", { - cwd: tmpDir, + cwd, timeout: 60000, onLine: logLine, }); @@ -430,12 +433,5 @@ export async function executeUpdate( consoleErr("执行", `=== 更新失败: ${errMsg} ===`); onLog(`✗ 更新失败: ${errMsg}`); return { success: false, message: errMsg }; - } finally { - try { - fs.rmSync(tmpDir, { recursive: true, force: true }); - consoleLog("执行", `临时目录已清理: ${tmpDir}`); - } catch { - // ignore cleanup errors - } } }