Add scheduled rsync copy jobs
This commit is contained in:
@@ -18,6 +18,7 @@ import (
|
||||
"git.casaderoll.de/michael/urbm/internal/platform"
|
||||
"git.casaderoll.de/michael/urbm/internal/queue"
|
||||
"git.casaderoll.de/michael/urbm/internal/restic"
|
||||
"git.casaderoll.de/michael/urbm/internal/rsync"
|
||||
"git.casaderoll.de/michael/urbm/internal/scheduler"
|
||||
"git.casaderoll.de/michael/urbm/internal/store"
|
||||
)
|
||||
@@ -32,6 +33,7 @@ type Service struct {
|
||||
store *store.Store
|
||||
secrets SecretStore
|
||||
restic *restic.Runner
|
||||
rsync *rsync.Runner
|
||||
mounts *platform.MountManager
|
||||
workloads *platform.WorkloadManager
|
||||
notifier *notify.Sender
|
||||
@@ -41,12 +43,12 @@ type Service struct {
|
||||
config model.Config
|
||||
}
|
||||
|
||||
func New(st *store.Store, secrets SecretStore, rr *restic.Runner, mounts *platform.MountManager, workloads *platform.WorkloadManager, notifier *notify.Sender, log *slog.Logger) (*Service, error) {
|
||||
func New(st *store.Store, secrets SecretStore, rr *restic.Runner, rs *rsync.Runner, mounts *platform.MountManager, workloads *platform.WorkloadManager, notifier *notify.Sender, log *slog.Logger) (*Service, error) {
|
||||
config, err := st.LoadConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s := &Service{store: st, secrets: secrets, restic: rr, mounts: mounts, workloads: workloads, notifier: notifier, log: log, config: config}
|
||||
s := &Service{store: st, secrets: secrets, restic: rr, rsync: rs, mounts: mounts, workloads: workloads, notifier: notifier, log: log, config: config}
|
||||
s.queue = queue.New(s.execute, func(runs []model.Run) {
|
||||
if err := st.SaveRuns(runs); err != nil {
|
||||
log.Error("save run history", "error", err)
|
||||
@@ -112,6 +114,7 @@ func (s *Service) SaveConfig(config model.Config) error {
|
||||
s.config = config
|
||||
s.mu.Unlock()
|
||||
s.restic.Binary = config.Settings.ResticPath
|
||||
s.rsync.Binary = config.Settings.RsyncPath
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -129,7 +132,11 @@ func (s *Service) EnqueueJob(jobID string, priority int) (model.Run, error) {
|
||||
if !ok {
|
||||
return model.Run{}, errors.New("validation: unknown job")
|
||||
}
|
||||
run := model.Run{SchemaVersion: model.SchemaVersion, ID: newID(), JobID: job.ID, TaskType: "backup", Priority: priority, CreatedAt: time.Now().UTC()}
|
||||
taskType := "backup"
|
||||
if job.Type == model.JobRsync {
|
||||
taskType = "rsync"
|
||||
}
|
||||
run := model.Run{SchemaVersion: model.SchemaVersion, ID: newID(), JobID: job.ID, TaskType: taskType, Priority: priority, CreatedAt: time.Now().UTC()}
|
||||
return run, s.queue.Enqueue(run)
|
||||
}
|
||||
|
||||
@@ -169,18 +176,27 @@ func (s *Service) Cancel(runID string) bool { return s.queue.Cancel(runID) }
|
||||
func (s *Service) ClearRunHistory() int { return s.queue.ClearHistory() }
|
||||
|
||||
func (s *Service) Pause(runID string) error {
|
||||
if err := s.restic.Pause(runID); err != nil {
|
||||
runner := s.restic.Pause
|
||||
resume := s.restic.Resume
|
||||
if s.runTaskType(runID) == "rsync" {
|
||||
runner, resume = s.rsync.Pause, s.rsync.Resume
|
||||
}
|
||||
if err := runner(runID); err != nil {
|
||||
return fmt.Errorf("pause: %w", err)
|
||||
}
|
||||
if !s.queue.SetPaused(runID, true) {
|
||||
_ = s.restic.Resume(runID)
|
||||
_ = resume(runID)
|
||||
return errors.New("pause: run is not currently running")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) Resume(runID string) error {
|
||||
if err := s.restic.Resume(runID); err != nil {
|
||||
runner := s.restic.Resume
|
||||
if s.runTaskType(runID) == "rsync" {
|
||||
runner = s.rsync.Resume
|
||||
}
|
||||
if err := runner(runID); err != nil {
|
||||
return fmt.Errorf("resume: %w", err)
|
||||
}
|
||||
if !s.queue.SetPaused(runID, false) {
|
||||
@@ -189,6 +205,15 @@ func (s *Service) Resume(runID string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) runTaskType(runID string) string {
|
||||
for _, run := range s.queue.Snapshot() {
|
||||
if run.ID == runID {
|
||||
return run.TaskType
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *Service) Snapshots(ctx context.Context, repoID string) ([]model.Snapshot, error) {
|
||||
repo, ok := s.repository(repoID)
|
||||
if !ok {
|
||||
@@ -317,6 +342,9 @@ func (s *Service) execute(ctx context.Context, run model.Run) model.Run {
|
||||
if run.TaskType == "backup" {
|
||||
return s.executeBackup(ctx, run)
|
||||
}
|
||||
if run.TaskType == "rsync" {
|
||||
return s.executeRsync(ctx, run)
|
||||
}
|
||||
if run.TaskType == "restore" {
|
||||
return s.executeRestore(ctx, run)
|
||||
}
|
||||
@@ -354,6 +382,66 @@ func (s *Service) execute(ctx context.Context, run model.Run) model.Run {
|
||||
return run
|
||||
}
|
||||
|
||||
func (s *Service) executeRsync(ctx context.Context, run model.Run) (result model.Run) {
|
||||
job, ok := s.job(run.JobID)
|
||||
if !ok {
|
||||
return failed(run, "validation", "job no longer exists")
|
||||
}
|
||||
defer func() { result = s.finishJob(run, job, result) }()
|
||||
live := run
|
||||
appendLiveLog(&live, fmt.Sprintf("Rsync-Kopie wird vorbereitet: %d Quelle(n) nach %s", len(job.Sources), job.Rsync.Target))
|
||||
s.updateActive(live)
|
||||
for _, source := range job.Sources {
|
||||
if _, err := os.Stat(source.Path); err != nil {
|
||||
return failed(run, "source", "source unavailable: "+source.Path)
|
||||
}
|
||||
}
|
||||
if info, err := os.Stat(job.Rsync.Target); err != nil || !info.IsDir() {
|
||||
if err == nil {
|
||||
err = errors.New("target is not a directory")
|
||||
}
|
||||
return failed(run, "environment", "rsync target unavailable: "+err.Error())
|
||||
}
|
||||
started := time.Now()
|
||||
lastLoggedPercent := -10
|
||||
summary, err := s.rsync.Run(ctx, run.ID, job, func(progress rsync.Progress) {
|
||||
if progress.Percent > 0 || progress.Bytes > 0 {
|
||||
live.ProgressPercent = progress.Percent
|
||||
live.ProgressBytes = progress.Bytes
|
||||
if progress.Percent > 0 {
|
||||
live.ProgressTotal = int64(float64(progress.Bytes) / (progress.Percent / 100))
|
||||
}
|
||||
if elapsed := time.Since(started).Seconds(); elapsed > 0 {
|
||||
live.BytesPerSecond = float64(progress.Bytes) / elapsed
|
||||
}
|
||||
percent := int(progress.Percent)
|
||||
if percent >= lastLoggedPercent+10 {
|
||||
appendLiveLog(&live, fmt.Sprintf("Rsync-Fortschritt: %.0f%%, %s übertragen", progress.Percent, formatBytes(progress.Bytes)))
|
||||
lastLoggedPercent = percent - percent%10
|
||||
}
|
||||
}
|
||||
if progress.CurrentFile != "" {
|
||||
live.CurrentFile = progress.CurrentFile
|
||||
}
|
||||
s.updateActive(live)
|
||||
})
|
||||
if err != nil {
|
||||
appendLiveLog(&live, "Rsync-Kopie fehlgeschlagen: "+err.Error())
|
||||
failedRun := failedError(run, err)
|
||||
failedRun.LiveLog = live.LiveLog
|
||||
return failedRun
|
||||
}
|
||||
run.Status, run.Message = "success", "rsync completed"
|
||||
run.BytesAdded, run.BytesProcessed = summary.Bytes, summary.Bytes
|
||||
run.FilesNew, run.FilesProcessed = summary.Files, summary.Files
|
||||
run.ProgressPercent, run.ProgressBytes, run.ProgressTotal = 100, summary.Bytes, summary.Bytes
|
||||
run.ProgressFiles, run.ProgressFileTotal = summary.Files, summary.Files
|
||||
run.BytesPerSecond, run.CurrentFile = live.BytesPerSecond, live.CurrentFile
|
||||
appendLiveLog(&live, fmt.Sprintf("Rsync-Kopie abgeschlossen: %d Dateien, %s", summary.Files, formatBytes(summary.Bytes)))
|
||||
run.LiveLog = live.LiveLog
|
||||
return run
|
||||
}
|
||||
|
||||
func (s *Service) executeBackup(ctx context.Context, run model.Run) (result model.Run) {
|
||||
job, ok := s.job(run.JobID)
|
||||
if !ok {
|
||||
@@ -577,7 +665,7 @@ func notificationMessage(run model.Run, now time.Time) string {
|
||||
lines = append(lines, "Dauer: "+formatDuration(duration))
|
||||
}
|
||||
}
|
||||
if run.TaskType == "backup" && (run.Status == "success" || run.Status == "warning") {
|
||||
if (run.TaskType == "backup" || run.TaskType == "rsync") && (run.Status == "success" || run.Status == "warning") {
|
||||
lines = append(lines,
|
||||
"Neu gespeichert: "+formatBytes(run.BytesAdded),
|
||||
fmt.Sprintf("Neue Dateien: %d", run.FilesNew),
|
||||
@@ -589,7 +677,7 @@ func notificationMessage(run model.Run, now time.Time) string {
|
||||
lines = append(lines, "Snapshot: "+run.SnapshotID)
|
||||
}
|
||||
}
|
||||
if run.Message != "" && run.Message != "backup completed" {
|
||||
if run.Message != "" && run.Message != "backup completed" && run.Message != "rsync completed" {
|
||||
lines = append(lines, "Details: "+run.Message)
|
||||
}
|
||||
return strings.Join(lines, "\n")
|
||||
|
||||
Reference in New Issue
Block a user