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
+41 -45
View File
@@ -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
}
}
}