mirror of
https://github.com/komari-monitor/komari.git
synced 2025-11-15 03:11:26 +00:00
174 lines
4.5 KiB
Go
174 lines
4.5 KiB
Go
package admin
|
|
|
|
import (
|
|
"archive/zip"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/komari-monitor/komari/api"
|
|
"github.com/komari-monitor/komari/cmd/flags"
|
|
)
|
|
|
|
// 只有一个备份恢复操作在进行
|
|
var restoreMutex sync.Mutex
|
|
|
|
// UploadBackup 用于接收上传的备份文件并将其内容恢复到原始位置
|
|
func UploadBackup(c *gin.Context) {
|
|
// 尝试获取锁,如果已有恢复操作在进行,则立即返回错误
|
|
if !restoreMutex.TryLock() {
|
|
api.RespondError(c, http.StatusConflict, "Another restore operation is already in progress")
|
|
return
|
|
}
|
|
defer restoreMutex.Unlock()
|
|
|
|
// 获取上传的文件
|
|
file, header, err := c.Request.FormFile("backup")
|
|
if err != nil {
|
|
api.RespondError(c, http.StatusBadRequest, fmt.Sprintf("Error getting uploaded file: %v", err))
|
|
return
|
|
}
|
|
defer file.Close()
|
|
|
|
// 检查文件是否为zip格式
|
|
if !strings.HasSuffix(strings.ToLower(header.Filename), ".zip") {
|
|
api.RespondError(c, http.StatusBadRequest, "Uploaded file must be a ZIP archive")
|
|
return
|
|
}
|
|
|
|
// 创建临时文件保存上传的zip
|
|
tempFile, err := os.CreateTemp("", "backup-*.zip")
|
|
if err != nil {
|
|
api.RespondError(c, http.StatusInternalServerError, fmt.Sprintf("Error creating temporary file: %v", err))
|
|
return
|
|
}
|
|
tempFilePath := tempFile.Name()
|
|
defer os.Remove(tempFilePath) // 确保临时文件最终被删除
|
|
|
|
// 将上传的文件内容复制到临时文件
|
|
_, err = io.Copy(tempFile, file)
|
|
if err != nil {
|
|
tempFile.Close()
|
|
api.RespondError(c, http.StatusInternalServerError, fmt.Sprintf("Error saving uploaded file: %v", err))
|
|
return
|
|
}
|
|
tempFile.Close() // 关闭文件以便后续操作
|
|
|
|
// 打开zip文件准备解压
|
|
zipReader, err := zip.OpenReader(tempFilePath)
|
|
if err != nil {
|
|
api.RespondError(c, http.StatusInternalServerError, fmt.Sprintf("Error opening zip file: %v", err))
|
|
return
|
|
}
|
|
defer zipReader.Close()
|
|
|
|
// 检查是否包含备份标记文件
|
|
hasMarkupFile := false
|
|
for _, zipFile := range zipReader.File {
|
|
if zipFile.Name == "komari-backup-markup" {
|
|
hasMarkupFile = true
|
|
break
|
|
}
|
|
}
|
|
if !hasMarkupFile {
|
|
api.RespondError(c, http.StatusBadRequest, "Invalid backup file: missing komari-backup-markup file")
|
|
return
|
|
}
|
|
|
|
// 确保data目录存在
|
|
if err := os.MkdirAll("./data", 0755); err != nil {
|
|
api.RespondError(c, http.StatusInternalServerError, fmt.Sprintf("Error creating data directory: %v", err))
|
|
return
|
|
}
|
|
|
|
// 获取数据库文件名
|
|
dbFileName := filepath.Base(flags.DatabaseFile)
|
|
|
|
// 解压文件
|
|
for _, zipFile := range zipReader.File {
|
|
// 检查文件路径是否安全(防止路径遍历攻击)
|
|
filePath := zipFile.Name
|
|
if strings.Contains(filePath, "..") {
|
|
log.Printf("Potentially unsafe path in zip: %s, skipping", filePath)
|
|
continue
|
|
}
|
|
|
|
// 跳过备份标记文件
|
|
if filePath == "komari-backup-markup" {
|
|
continue
|
|
}
|
|
|
|
// 确定目标路径
|
|
var destPath string
|
|
if filePath == dbFileName {
|
|
// 如果是数据库文件,恢复到数据库文件路径
|
|
destPath = flags.DatabaseFile
|
|
} else {
|
|
// 其他文件恢复到data目录
|
|
destPath = filepath.Join("./data", filePath)
|
|
}
|
|
|
|
// 如果是目录,创建目录
|
|
if zipFile.FileInfo().IsDir() {
|
|
if err := os.MkdirAll(destPath, 0755); err != nil {
|
|
log.Printf("Error creating directory %s: %v", destPath, err)
|
|
}
|
|
continue
|
|
}
|
|
|
|
// 确保目标文件的目录存在
|
|
destDir := filepath.Dir(destPath)
|
|
if err := os.MkdirAll(destDir, 0755); err != nil {
|
|
log.Printf("Error creating directory %s: %v", destDir, err)
|
|
continue
|
|
}
|
|
|
|
// 打开zip中的文件
|
|
srcFile, err := zipFile.Open()
|
|
if err != nil {
|
|
log.Printf("Error opening file from zip %s: %v", filePath, err)
|
|
continue
|
|
}
|
|
|
|
// 创建目标文件
|
|
destFile, err := os.Create(destPath)
|
|
if err != nil {
|
|
srcFile.Close()
|
|
log.Printf("Error creating file %s: %v", destPath, err)
|
|
continue
|
|
}
|
|
|
|
// 复制内容
|
|
_, err = io.Copy(destFile, srcFile)
|
|
srcFile.Close()
|
|
destFile.Close()
|
|
if err != nil {
|
|
log.Printf("Error extracting file %s: %v", destPath, err)
|
|
continue
|
|
}
|
|
|
|
// 保持原始文件的修改时间
|
|
if err := os.Chtimes(destPath, zipFile.Modified, zipFile.Modified); err != nil {
|
|
log.Printf("Error setting file time for %s: %v", destPath, err)
|
|
}
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"status": "success",
|
|
"message": "Backup restored successfully. The service will restart shortly.",
|
|
})
|
|
|
|
go func() {
|
|
log.Println("Backup restored, restarting service in 2 seconds...")
|
|
time.Sleep(2 * time.Second)
|
|
os.Exit(0)
|
|
}()
|
|
}
|