feat: 更新机制改为 docker pull 镜像仓库方案,移除 git clone + build
This commit is contained in:
@@ -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
|
||||
|
||||
+41
-45
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user