mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-06 13:14:11 +00:00
feat: add BackupJob and wire up routes and scheduling
This commit is contained in:
parent
3181d5805d
commit
50d3b2cd7e
4 changed files with 104 additions and 0 deletions
28
docs/Tasktracking/2026-04-26-backup-job-scheduling.md
Normal file
28
docs/Tasktracking/2026-04-26-backup-job-scheduling.md
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
# Task Record
|
||||||
|
|
||||||
|
Date: 2026-04-26
|
||||||
|
Related Module: web/job, web/controller, web
|
||||||
|
Change Type: Add
|
||||||
|
|
||||||
|
## Background
|
||||||
|
Database backups were only possible manually via API. There was no scheduled backup mechanism, so users had to remember to create backups or rely on external cron.
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
- Created `web/job/backup_job.go`: BackupJob struct with Run() and shouldRun() methods that check backup settings and execute CreateSnapshot when the configured schedule matches the current time
|
||||||
|
- Registered BackupController routes in `web/controller/server.go` initRouter() to expose backup API endpoints under `/panel/api/server/`
|
||||||
|
- Scheduled BackupJob via cron in `web/web.go` startTask() at `@every 1m` interval (the job internally checks whether it should actually run)
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
- New file: `web/job/backup_job.go`
|
||||||
|
- Modified: `web/controller/server.go` (3 lines added in initRouter)
|
||||||
|
- Modified: `web/web.go` (2 lines added in startTask)
|
||||||
|
- No database schema changes, no API breaking changes, no config changes
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
- `go build ./...` passed with no errors
|
||||||
|
- `gofmt -l -w .` produced no changes (files already properly formatted)
|
||||||
|
|
||||||
|
## Risks And Follow-Up
|
||||||
|
- BackupJob has zero-value service fields; methods rely on DB queries at runtime, same pattern as other jobs (CheckXrayRunningJob, LdapSyncJob)
|
||||||
|
- The job uses `logger.Warning` (not `logger.Warn`) — verify this matches the logger API
|
||||||
|
- Need to verify that Backups setting page in UI has the scheduling options (BackupEnabled, BackupFrequency, BackupHour) to make the job functional
|
||||||
|
|
@ -60,6 +60,10 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) {
|
||||||
g.POST("/xraylogs/:count", a.getXrayLogs)
|
g.POST("/xraylogs/:count", a.getXrayLogs)
|
||||||
g.POST("/importDB", a.importDB)
|
g.POST("/importDB", a.importDB)
|
||||||
g.POST("/getNewEchCert", a.getNewEchCert)
|
g.POST("/getNewEchCert", a.getNewEchCert)
|
||||||
|
|
||||||
|
// Backup routes
|
||||||
|
backupCtrl := BackupController{}
|
||||||
|
backupCtrl.initRouter(g)
|
||||||
}
|
}
|
||||||
|
|
||||||
// refreshStatus updates the cached server status and collects CPU history.
|
// refreshStatus updates the cached server status and collects CPU history.
|
||||||
|
|
|
||||||
69
web/job/backup_job.go
Normal file
69
web/job/backup_job.go
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
package job
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mhsanaei/3x-ui/v2/logger"
|
||||||
|
"github.com/mhsanaei/3x-ui/v2/web/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BackupJob handles scheduled database backups.
|
||||||
|
type BackupJob struct {
|
||||||
|
settingService service.SettingService
|
||||||
|
backupService service.BackupService
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBackupJob creates a new BackupJob instance.
|
||||||
|
func NewBackupJob() *BackupJob {
|
||||||
|
return &BackupJob{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run executes the scheduled backup if enabled and time matches the configured frequency.
|
||||||
|
func (j *BackupJob) Run() {
|
||||||
|
enabled, err := j.settingService.GetBackupEnabled()
|
||||||
|
if err != nil || !enabled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
frequency, err := j.settingService.GetBackupFrequency()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !j.shouldRun(frequency) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := j.backupService.CreateSnapshot(j.settingService); err != nil {
|
||||||
|
logger.Warning("scheduled backup failed:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// shouldRun checks if the backup should run based on the configured frequency.
|
||||||
|
func (j *BackupJob) shouldRun(frequency string) bool {
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
switch frequency {
|
||||||
|
case "hourly":
|
||||||
|
return now.Minute() == 0
|
||||||
|
case "every12h":
|
||||||
|
return (now.Hour() == 0 || now.Hour() == 12) && now.Minute() == 0
|
||||||
|
case "daily":
|
||||||
|
hour, err := j.settingService.GetBackupHour()
|
||||||
|
if err != nil {
|
||||||
|
hour = 3
|
||||||
|
}
|
||||||
|
return now.Hour() == hour && now.Minute() == 0
|
||||||
|
case "weekly":
|
||||||
|
if now.Weekday() != time.Sunday {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
hour, err := j.settingService.GetBackupHour()
|
||||||
|
if err != nil {
|
||||||
|
hour = 3
|
||||||
|
}
|
||||||
|
return now.Hour() == hour && now.Minute() == 0
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -410,6 +410,9 @@ func (s *Server) startTask() {
|
||||||
} else {
|
} else {
|
||||||
s.cron.Remove(entry)
|
s.cron.Remove(entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Schedule database backup job (runs every minute, checks schedule internally)
|
||||||
|
s.cron.AddJob("@every 1m", job.NewBackupJob())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) startNodeLoops() {
|
func (s *Server) startNodeLoops() {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue