Fix backup check notifications and workload recovery; release 2026.09.21.r001

This commit is contained in:
Mikei386
2026-09-21 20:36:31 +02:00
parent 8f75a04db6
commit 5009dc202b
14 changed files with 295 additions and 38 deletions
+47
View File
@@ -1,6 +1,15 @@
package service
import (
"context"
"fmt"
"git.casaderoll.de/michael/urbm/internal/notify"
"git.casaderoll.de/michael/urbm/internal/platform"
"git.casaderoll.de/michael/urbm/internal/queue"
"git.casaderoll.de/michael/urbm/internal/restic"
"log/slog"
"os"
"path/filepath"
"strings"
"testing"
"time"
@@ -42,3 +51,41 @@ func TestNotificationMessageIncludesPruneSummary(t *testing.T) {
t.Fatalf("message contains redundant prune completion details: %q", message)
}
}
type checkSecrets struct{}
func (checkSecrets) Get(string) (string, error) { return "test", nil }
func TestCheckSendsSuccessAndFailureNotifications(t *testing.T) {
for _, code := range []int{0, 1} {
t.Run(fmt.Sprint(code), func(t *testing.T) {
dir := t.TempDir()
binary := filepath.Join(dir, "restic")
if err := os.WriteFile(binary, []byte(fmt.Sprintf("#!/bin/sh\necho check-result >&2\nexit %d\n", code)), 0700); err != nil {
t.Fatal(err)
}
var sent []string
s := &Service{
config: model.Config{Repositories: []model.Repository{{ID: "repo", Name: "test", Type: model.RepositoryLocal, Location: dir}}, Notifications: []model.NotificationTarget{{Type: "unraid", Enabled: true, Events: []string{"success", "failed"}}}},
repositoryLocks: map[string]chan struct{}{},
restic: &restic.Runner{Binary: binary, RuntimeDir: dir, Secrets: checkSecrets{}},
mounts: &platform.MountManager{}, queue: queue.New(nil, nil), log: slog.Default(),
notifier: &notify.Sender{RunCommand: func(_ context.Context, _ string, args ...string) error {
sent = append(sent, strings.Join(args, " "))
return nil
}},
}
result := s.execute(context.Background(), model.Run{ID: "run", JobID: "repo", TaskType: "check"})
want := "success"
if code != 0 {
want = "failed"
}
if result.Status != want || len(sent) != 1 {
t.Fatalf("status=%s messages=%v", result.Status, sent)
}
if !strings.Contains(sent[0], "URBM") {
t.Fatal(sent)
}
})
}
}
+10 -4
View File
@@ -1052,8 +1052,12 @@ func (s *Service) execute(ctx context.Context, run model.Run) (result model.Run)
ctx = restic.WithRunID(ctx, run.ID)
maintenanceRepoName := ""
defer func() {
if result.TaskType == "prune" && (result.Status == "success" || result.Status == "warning" || result.Status == "failed") {
go s.notify("Repository-Bereinigung", maintenanceRepoName, result)
if (result.TaskType == "prune" || result.TaskType == "check") && (result.Status == "success" || result.Status == "warning" || result.Status == "failed") {
name := "Repository-Bereinigung"
if result.TaskType == "check" {
name = "Repository-Prüfung"
}
s.notify(name, maintenanceRepoName, result)
}
}()
if run.TaskType == "backup" {
@@ -1543,18 +1547,20 @@ func (s *Service) finishJob(run model.Run, job model.Job, result model.Run) mode
repoName = repo.Name
}
}
go s.notify(job.Name, repoName, result)
s.notify(job.Name, repoName, result)
}
return result
}
func (s *Service) notify(name, repository string, run model.Run) {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
title := notificationTitle(name, repository, run)
message := notificationMessage(name, repository, run, time.Now().UTC())
for _, target := range s.Config().Notifications {
for _, event := range target.Events {
if event == run.Status {
if err := s.notifier.Send(context.Background(), target, title, message, run.Status); err != nil {
if err := s.notifier.Send(ctx, target, title, message, run.Status); err != nil {
s.log.Warn("send notification failed", "target", target.ID, "type", target.Type, "error", err)
}
break