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/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
@@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user