2024-11-25 13:07:29 +08:00

240 lines
8.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { exec } from 'child_process';
import { promisify } from 'util';
import fs from 'fs/promises';
import path from 'path';
import { redis } from "../../../../utils/redis.js";
const execAsync = promisify(exec);
// Git错误处理函数
function handleGitError(error, stderr) {
if (error.message.includes('RPC failed')) {
return '网络连接失败,请检查网络后重试';
}
if (error.message.includes('early EOF')) {
return '数据传输中断,请重试';
}
if (error.message.includes('fetch-pack: invalid index-pack output')) {
return '数据包错误,请重试';
}
if (error.message.includes('Timed out')) {
return '连接超时,请检查网络后重试';
}
if (error.message.includes('Could not resolve host')) {
return '无法解析主机地址,请检查网络';
}
if (error.message.includes('Permission denied')) {
return '权限被拒绝请检查git权限配置';
}
if (error.message.includes('be overwritten by merge')) {
return '存在冲突,请使用强制更新';
}
// 如果是其他错误,返回具体错误信息
return stderr || error.message || '未知错误';
}
async function ensureDirectory(dir) {
try {
await fs.access(dir);
} catch {
await fs.mkdir(dir, { recursive: true });
}
}
async function copyConfig(src, dest) {
try {
await ensureDirectory(path.dirname(dest));
await fs.cp(src, dest, { recursive: true });
console.log(`成功复制配置文件从 ${src}${dest}`);
return true;
} catch (error) {
console.error(`复制配置文件失败: ${error.message}`);
return false;
}
}
// 清理更新状态和临时文件
async function cleanupUpdate(tempDir) {
try {
// 清理临时文件
await fs.rm(tempDir, { recursive: true, force: true });
// 清理Redis中的更新状态
await redis.del('rconsole:update:status');
await redis.del('rconsole:update:paths');
console.log('清理完成');
} catch (error) {
console.error('清理失败:', error);
}
}
export async function GET(req) {
try {
const { searchParams } = new URL(req.url);
const isCheck = searchParams.get('check') === 'true';
const isRestore = searchParams.get('restore') === 'true';
const isForce = searchParams.get('force') === 'true';
// 如果是检查请求
if (isCheck) {
const updateStatus = await redis.get('rconsole:update:status');
return new Response(JSON.stringify({
needsRestore: updateStatus === 'restoring'
}), {
headers: { 'Content-Type': 'application/json' },
});
}
// 如果是恢复请求
if (isRestore) {
const updateStatus = await redis.get('rconsole:update:status');
const paths = JSON.parse(await redis.get('rconsole:update:paths') || '{}');
if (updateStatus === 'restoring' && paths.tempDir && paths.configDir) {
try {
await copyConfig(paths.tempDir, paths.configDir);
await cleanupUpdate(paths.tempDir);
return new Response(JSON.stringify({
success: true,
message: '配置恢复完成'
}), {
headers: { 'Content-Type': 'application/json' },
});
} catch (error) {
return new Response(JSON.stringify({
success: false,
message: '配置恢复失败:' + error.message
}), {
headers: { 'Content-Type': 'application/json' },
});
}
}
return new Response(JSON.stringify({
success: true,
message: '无需恢复'
}), {
headers: { 'Content-Type': 'application/json' },
});
}
const projectRoot = path.join(process.cwd(), '..');
const tempDir = path.join(projectRoot, 'temp', 'update-tmp');
const configDir = path.join(projectRoot, 'config');
// 检查是否有未完成的更新
const updateStatus = await redis.get('rconsole:update:status');
if (updateStatus === 'restoring') {
// 如果有未完成的更新,尝试恢复
const paths = JSON.parse(await redis.get('rconsole:update:paths') || '{}');
if (paths.tempDir && paths.configDir) {
try {
await copyConfig(paths.tempDir, paths.configDir);
console.log('恢复了之前未完成的配置文件更新');
} finally {
await cleanupUpdate(paths.tempDir);
}
}
}
console.log('开始新的更新流程');
// 保存路径信息到Redis
await redis.set('rconsole:update:paths', JSON.stringify({
tempDir,
configDir
}));
await redis.set('rconsole:update:status', 'started');
// 确保临时目录存在
await ensureDirectory(tempDir);
// 备份配置文件
let configBackedUp = false;
try {
await fs.access(configDir);
await copyConfig(configDir, tempDir);
configBackedUp = true;
console.log('配置文件备份成功');
await redis.set('rconsole:update:status', 'backed_up');
} catch (error) {
console.log('无配置文件需要备份或备份失败:', error.message);
}
try {
// 执行git操作
if (isForce) {
console.log('执行强制更新...');
await execAsync(`git -C "${projectRoot}" checkout .`);
}
// 标记状态为需要恢复
await redis.set('rconsole:update:status', 'restoring');
console.log('执行git pull...');
const { stdout } = await execAsync(`git -C "${projectRoot}" pull --no-rebase`);
// 恢复配置文件
if (configBackedUp) {
console.log('开始恢复配置文件...');
await copyConfig(tempDir, configDir);
}
// 清理所有临时文件和状态
await cleanupUpdate(tempDir);
if (stdout.includes('Already up to date') || stdout.includes('已经是最新')) {
return new Response(JSON.stringify({
success: true,
message: '已经是最新版本'
}), {
headers: { 'Content-Type': 'application/json' },
});
}
return new Response(JSON.stringify({
success: true,
message: '更新成功'
}), {
headers: { 'Content-Type': 'application/json' },
});
} catch (error) {
// 如果git操作失败也尝试恢复配置文件
if (configBackedUp) {
try {
await copyConfig(tempDir, configDir);
console.log('git操作失败但配置文件已恢复');
} catch (restoreError) {
console.error('恢复配置文件失败:', restoreError.message);
}
}
await cleanupUpdate(tempDir);
const errorMessage = handleGitError(error, error.stderr);
return new Response(JSON.stringify({
success: false,
message: errorMessage
}), {
headers: { 'Content-Type': 'application/json' },
});
}
} catch (error) {
console.error('更新过程出错:', error);
// 确保清理所有临时状态
const paths = JSON.parse(await redis.get('rconsole:update:paths') || '{}');
if (paths.tempDir) {
await cleanupUpdate(paths.tempDir);
}
return new Response(JSON.stringify({
success: false,
message: '更新过程出错:' + (error.message || '未知错误')
}), {
headers: { 'Content-Type': 'application/json' },
});
}
}