Add Unraid and Gotify notifications
This commit is contained in:
+20
-1
@@ -155,7 +155,7 @@ func DefaultConfig() Config {
|
||||
SchemaVersion: SchemaVersion,
|
||||
Jobs: []Job{},
|
||||
Repositories: []Repository{},
|
||||
Notifications: []NotificationTarget{},
|
||||
Notifications: []NotificationTarget{{SchemaVersion: SchemaVersion, ID: "unraid", Name: "Unraid", Type: "unraid", Enabled: true, Events: []string{"success", "warning", "failed"}}},
|
||||
Settings: Settings{
|
||||
ResticPath: "/usr/local/libexec/backupper/restic",
|
||||
RestoreRoot: "/mnt/user/backupper-restores",
|
||||
@@ -165,3 +165,22 @@ func DefaultConfig() Config {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func NormalizeConfig(c Config) Config {
|
||||
notifications := make([]NotificationTarget, 0, len(c.Notifications)+1)
|
||||
hasUnraid := false
|
||||
for _, target := range c.Notifications {
|
||||
if target.Type == "ntfy" {
|
||||
continue
|
||||
}
|
||||
if target.Type == "unraid" {
|
||||
hasUnraid = true
|
||||
}
|
||||
notifications = append(notifications, target)
|
||||
}
|
||||
if !hasUnraid {
|
||||
notifications = append(notifications, NotificationTarget{SchemaVersion: SchemaVersion, ID: "unraid", Name: "Unraid", Type: "unraid", Enabled: true, Events: []string{"success", "warning", "failed"}})
|
||||
}
|
||||
c.Notifications = notifications
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package model
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
@@ -42,12 +43,54 @@ func ValidateConfig(c Config) error {
|
||||
}
|
||||
repositoryOwners[job.RepositoryID] = job.ID
|
||||
}
|
||||
notifications := make(map[string]struct{}, len(c.Notifications))
|
||||
for _, target := range c.Notifications {
|
||||
if err := ValidateNotification(target); err != nil {
|
||||
return fmt.Errorf("notification %q: %w", target.ID, err)
|
||||
}
|
||||
if _, exists := notifications[target.ID]; exists {
|
||||
return fmt.Errorf("duplicate notification id %q", target.ID)
|
||||
}
|
||||
notifications[target.ID] = struct{}{}
|
||||
}
|
||||
if !filepath.IsAbs(c.Settings.RestoreRoot) {
|
||||
return errors.New("restoreRoot must be absolute")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateNotification(target NotificationTarget) error {
|
||||
if target.SchemaVersion != SchemaVersion || !idPattern.MatchString(target.ID) {
|
||||
return errors.New("invalid schemaVersion or id")
|
||||
}
|
||||
if strings.TrimSpace(target.Name) == "" {
|
||||
return errors.New("name is required")
|
||||
}
|
||||
switch target.Type {
|
||||
case "unraid":
|
||||
case "gotify":
|
||||
parsed, err := url.Parse(target.URL)
|
||||
if err != nil || (parsed.Scheme != "http" && parsed.Scheme != "https") || parsed.Host == "" {
|
||||
return errors.New("Gotify URL must be an absolute HTTP or HTTPS URL")
|
||||
}
|
||||
if !idPattern.MatchString(target.TokenRef) {
|
||||
return errors.New("Gotify tokenRef must be a valid secret ID")
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unsupported notification type %q", target.Type)
|
||||
}
|
||||
allowed := map[string]bool{"success": true, "warning": true, "failed": true}
|
||||
if target.Enabled && len(target.Events) == 0 {
|
||||
return errors.New("enabled notification requires at least one event")
|
||||
}
|
||||
for _, event := range target.Events {
|
||||
if !allowed[event] {
|
||||
return fmt.Errorf("unsupported event %q", event)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateJob(j Job) error {
|
||||
if j.SchemaVersion != SchemaVersion || !idPattern.MatchString(j.ID) {
|
||||
return errors.New("invalid schemaVersion or id")
|
||||
|
||||
@@ -15,6 +15,26 @@ func TestValidateConfig(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeConfigReplacesNtfyAndAddsUnraid(t *testing.T) {
|
||||
c := DefaultConfig()
|
||||
c.Notifications = []NotificationTarget{{SchemaVersion: 1, ID: "old-ntfy", Name: "ntfy", Type: "ntfy", Enabled: true}}
|
||||
c = NormalizeConfig(c)
|
||||
if len(c.Notifications) != 1 || c.Notifications[0].Type != "unraid" || !c.Notifications[0].Enabled {
|
||||
t.Fatalf("notifications = %#v", c.Notifications)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateGotifyNotification(t *testing.T) {
|
||||
target := NotificationTarget{SchemaVersion: 1, ID: "gotify", Name: "Gotify", Type: "gotify", Enabled: true, URL: "https://gotify.example.test", TokenRef: "gotify-token", Events: []string{"failed"}}
|
||||
if err := ValidateNotification(target); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
target.URL = "javascript:alert(1)"
|
||||
if err := ValidateNotification(target); err == nil {
|
||||
t.Fatal("unsafe Gotify URL accepted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepositoryIsolation(t *testing.T) {
|
||||
c := DefaultConfig()
|
||||
c.Jobs = []Job{{SchemaVersion: 1, ID: "job", Name: "Broken", Type: JobFlash, RepositoryID: "missing", Consistency: Consistency{Mode: "live"}, Compression: "auto"}}
|
||||
|
||||
Reference in New Issue
Block a user