package notify import ( "bytes" "context" "fmt" "net/http" "net/url" "os/exec" "strings" "time" "github.com/backupper-unraid/backupper/internal/model" ) type SecretGetter interface{ Get(string) (string, error) } type Sender struct { Secrets SecretGetter Client *http.Client } func (s *Sender) Send(ctx context.Context, target model.NotificationTarget, title, message, severity string) error { if !target.Enabled { return nil } switch target.Type { case "unraid": importance := "normal" if severity == "error" { importance = "alert" } return exec.CommandContext(ctx, "/usr/local/emhttp/webGui/scripts/notify", "-e", "Backupper", "-s", title, "-d", message, "-i", importance).Run() case "ntfy": endpoint := strings.TrimRight(target.URL, "/") + "/" + url.PathEscape(target.Topic) req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewBufferString(message)) if err != nil { return err } req.Header.Set("Title", title) req.Header.Set("Tags", "floppy_disk") if target.TokenRef != "" { token, err := s.Secrets.Get(target.TokenRef) if err != nil { return err } req.Header.Set("Authorization", "Bearer "+token) } client := s.Client if client == nil { client = &http.Client{Timeout: 15 * time.Second} } response, err := client.Do(req) if err != nil { return err } defer response.Body.Close() if response.StatusCode < 200 || response.StatusCode >= 300 { return fmt.Errorf("ntfy returned %s", response.Status) } return nil default: return fmt.Errorf("unsupported notification type %q", target.Type) } }