feat: 更新机制改为 docker pull 镜像仓库方案,移除 git clone + build

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