mirror of
https://github.com/Awuqing/BackupX.git
synced 2026-08-29 03:57:39 +08:00
feat: improve local demo and documentation
This commit is contained in:
+1
-1
@@ -12,6 +12,7 @@ require (
|
||||
github.com/prometheus/client_golang v1.23.2
|
||||
github.com/rclone/rclone v1.73.5
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/shirou/gopsutil/v4 v4.25.10
|
||||
github.com/spf13/viper v1.20.0
|
||||
go.uber.org/zap v1.27.0
|
||||
golang.org/x/crypto v0.50.0
|
||||
@@ -195,7 +196,6 @@ require (
|
||||
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 // indirect
|
||||
github.com/sagikazarmark/locafero v0.7.0 // indirect
|
||||
github.com/samber/lo v1.52.0 // indirect
|
||||
github.com/shirou/gopsutil/v4 v4.25.10 // indirect
|
||||
github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af // indirect
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect
|
||||
github.com/sony/gobreaker v1.0.0 // indirect
|
||||
|
||||
@@ -59,7 +59,7 @@ func NewBackupRecordRepository(db *gorm.DB) *GormBackupRecordRepository {
|
||||
|
||||
func (r *GormBackupRecordRepository) List(ctx context.Context, options BackupRecordListOptions) ([]model.BackupRecord, error) {
|
||||
// Omit("Manifest"):列表不需要可能很大的清单 JSON,避免每行拖出该 TEXT 列。
|
||||
query := r.db.WithContext(ctx).Model(&model.BackupRecord{}).Omit("Manifest").Preload("Task").Preload("Task.StorageTarget").Order("started_at desc")
|
||||
query := r.db.WithContext(ctx).Model(&model.BackupRecord{}).Omit("Manifest").Preload("Task").Preload("StorageTarget").Preload("Task.StorageTarget").Order("started_at desc")
|
||||
if options.TaskID != nil {
|
||||
query = query.Where("task_id = ?", *options.TaskID)
|
||||
}
|
||||
@@ -87,7 +87,7 @@ func (r *GormBackupRecordRepository) List(ctx context.Context, options BackupRec
|
||||
|
||||
func (r *GormBackupRecordRepository) FindByID(ctx context.Context, id uint) (*model.BackupRecord, error) {
|
||||
var item model.BackupRecord
|
||||
if err := r.db.WithContext(ctx).Preload("Task").Preload("Task.StorageTarget").First(&item, id).Error; err != nil {
|
||||
if err := r.db.WithContext(ctx).Preload("Task").Preload("StorageTarget").Preload("Task.StorageTarget").First(&item, id).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -127,7 +127,7 @@ func (r *GormBackupRecordRepository) ListRecent(ctx context.Context, limit int)
|
||||
limit = 10
|
||||
}
|
||||
var items []model.BackupRecord
|
||||
if err := r.db.WithContext(ctx).Omit("Manifest").Preload("Task").Preload("Task.StorageTarget").Order("started_at desc").Limit(limit).Find(&items).Error; err != nil {
|
||||
if err := r.db.WithContext(ctx).Omit("Manifest").Preload("Task").Preload("StorageTarget").Preload("Task.StorageTarget").Order("started_at desc").Limit(limit).Find(&items).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
|
||||
@@ -22,6 +22,11 @@ func newBackupRecordTestRepository(t *testing.T) *GormBackupRecordRepository {
|
||||
if err != nil {
|
||||
t.Fatalf("database.Open returned error: %v", err)
|
||||
}
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
t.Fatalf("db.DB returned error: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { _ = sqlDB.Close() })
|
||||
storageTarget := &model.StorageTarget{Name: "local", Type: "local_disk", Enabled: true, ConfigCiphertext: "{}", ConfigVersion: 1, LastTestStatus: "unknown"}
|
||||
if err := db.Create(storageTarget).Error; err != nil {
|
||||
t.Fatalf("seed storage target error: %v", err)
|
||||
@@ -60,6 +65,9 @@ func TestBackupRecordRepositoryQueries(t *testing.T) {
|
||||
if stored == nil || stored.FileName != "website.tar.gz" {
|
||||
t.Fatalf("unexpected stored record: %#v", stored)
|
||||
}
|
||||
if stored.StorageTarget.Name != "local" {
|
||||
t.Fatalf("expected record storage target to be preloaded, got %#v", stored.StorageTarget)
|
||||
}
|
||||
listed, err := repo.List(ctx, BackupRecordListOptions{TaskID: &record.TaskID, Status: "success"})
|
||||
if err != nil {
|
||||
t.Fatalf("List returned error: %v", err)
|
||||
@@ -67,6 +75,9 @@ func TestBackupRecordRepositoryQueries(t *testing.T) {
|
||||
if len(listed) != 1 {
|
||||
t.Fatalf("expected one listed record, got %d", len(listed))
|
||||
}
|
||||
if listed[0].StorageTarget.Name != "local" {
|
||||
t.Fatalf("expected listed storage target to be preloaded, got %#v", listed[0].StorageTarget)
|
||||
}
|
||||
recent, err := repo.ListRecent(ctx, 5)
|
||||
if err != nil {
|
||||
t.Fatalf("ListRecent returned error: %v", err)
|
||||
@@ -74,6 +85,9 @@ func TestBackupRecordRepositoryQueries(t *testing.T) {
|
||||
if len(recent) != 1 {
|
||||
t.Fatalf("expected one recent record, got %d", len(recent))
|
||||
}
|
||||
if recent[0].StorageTarget.Name != "local" {
|
||||
t.Fatalf("expected recent storage target to be preloaded, got %#v", recent[0].StorageTarget)
|
||||
}
|
||||
total, err := repo.Count(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Count returned error: %v", err)
|
||||
|
||||
@@ -721,7 +721,8 @@ func (s *BackupExecutionService) executeTask(ctx context.Context, task *model.Ba
|
||||
return
|
||||
}
|
||||
defer os.RemoveAll(result.TempDir)
|
||||
// 依据运行器产出判定实际类型:产出清单 → 全量(记录清单供后续差异比对);否则为差异。
|
||||
// 依据运行器产出判定实际类型:文件全量会产出清单;只有明确启用差异模式且
|
||||
// 未产出清单时才是差异备份。数据库运行器本身不产出文件清单,但仍属于全量。
|
||||
if result.Manifest != nil {
|
||||
backupKind = model.BackupKindFull
|
||||
if data, encErr := backup.EncodeManifest(*result.Manifest); encErr == nil {
|
||||
@@ -729,8 +730,10 @@ func (s *BackupExecutionService) executeTask(ctx context.Context, task *model.Ba
|
||||
} else {
|
||||
logger.Warnf("备份清单序列化失败(不影响本次备份,但将禁用后续差异):%v", encErr)
|
||||
}
|
||||
} else {
|
||||
} else if spec.Differential {
|
||||
backupKind = model.BackupKindDifferential
|
||||
} else {
|
||||
backupKind = model.BackupKindFull
|
||||
}
|
||||
finalPath := result.ArtifactPath
|
||||
if strings.EqualFold(task.Compression, "gzip") && !strings.HasSuffix(strings.ToLower(finalPath), ".gz") {
|
||||
|
||||
@@ -107,6 +107,11 @@ func newExecutionTestServices(t *testing.T) (*BackupExecutionService, *BackupRec
|
||||
if err != nil {
|
||||
t.Fatalf("database.Open returned error: %v", err)
|
||||
}
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
t.Fatalf("db.DB returned error: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { _ = sqlDB.Close() })
|
||||
cipher := codec.NewConfigCipher("execution-secret")
|
||||
tasks := repository.NewBackupTaskRepository(db)
|
||||
targets := repository.NewStorageTargetRepository(db)
|
||||
@@ -155,6 +160,34 @@ func TestBackupExecutionServiceRunTaskByIDSync(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBackupExecutionServiceSQLiteBackupRemainsFull(t *testing.T) {
|
||||
executionService, _, tasks, _, records, sourceDir, _ := newExecutionTestServices(t)
|
||||
dbPath := filepath.Join(sourceDir, "finance.db")
|
||||
if err := os.WriteFile(dbPath, []byte("sqlite-demo"), 0o644); err != nil {
|
||||
t.Fatalf("WriteFile sqlite fixture returned error: %v", err)
|
||||
}
|
||||
task := &model.BackupTask{
|
||||
Name: "finance-db", Type: model.BackupTaskTypeSQLite, Enabled: true,
|
||||
DBPath: dbPath, StorageTargetID: 1, RetentionDays: 30,
|
||||
Compression: "none", MaxBackups: 10, LastStatus: "idle",
|
||||
}
|
||||
if err := tasks.Create(context.Background(), task); err != nil {
|
||||
t.Fatalf("Create sqlite task returned error: %v", err)
|
||||
}
|
||||
|
||||
detail, err := executionService.RunTaskByIDSync(context.Background(), task.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("RunTaskByIDSync sqlite returned error: %v", err)
|
||||
}
|
||||
stored, err := records.FindByID(context.Background(), detail.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("FindByID sqlite record returned error: %v", err)
|
||||
}
|
||||
if stored == nil || stored.BackupKind != model.BackupKindFull {
|
||||
t.Fatalf("expected sqlite backup kind full, got %#v", stored)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBackupExecutionServiceNodePoolSelectionDoesNotPersistTaskNodeID(t *testing.T) {
|
||||
executionService, _, tasks, _, records, _, _ := newExecutionTestServices(t)
|
||||
ctx := context.Background()
|
||||
|
||||
@@ -8,10 +8,11 @@ import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"backupx/server/internal/config"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/disk"
|
||||
)
|
||||
|
||||
type SystemInfo struct {
|
||||
@@ -74,11 +75,11 @@ func (s *SystemService) CheckUpdate(ctx context.Context) (*UpdateCheckResult, er
|
||||
}
|
||||
|
||||
var release struct {
|
||||
TagName string `json:"tag_name"`
|
||||
HTMLURL string `json:"html_url"`
|
||||
Body string `json:"body"`
|
||||
Published string `json:"published_at"`
|
||||
Assets []struct {
|
||||
TagName string `json:"tag_name"`
|
||||
HTMLURL string `json:"html_url"`
|
||||
Body string `json:"body"`
|
||||
Published string `json:"published_at"`
|
||||
Assets []struct {
|
||||
Name string `json:"name"`
|
||||
BrowserDownloadURL string `json:"browser_download_url"`
|
||||
} `json:"assets"`
|
||||
@@ -124,12 +125,10 @@ func (s *SystemService) GetInfo(_ context.Context) *SystemInfo {
|
||||
if dir == "" {
|
||||
dir = "."
|
||||
}
|
||||
var stat syscall.Statfs_t
|
||||
if err := syscall.Statfs(dir, &stat); err == nil {
|
||||
info.DiskTotal = int64(stat.Blocks) * int64(stat.Bsize)
|
||||
info.DiskFree = int64(stat.Bavail) * int64(stat.Bsize)
|
||||
info.DiskUsed = info.DiskTotal - info.DiskFree
|
||||
if stat, err := disk.Usage(dir); err == nil {
|
||||
info.DiskTotal = int64(stat.Total)
|
||||
info.DiskFree = int64(stat.Free)
|
||||
info.DiskUsed = int64(stat.Used)
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user