Add retry mechanism and failure notifications for nCore/qBittorrent setup

- Implemented a retry mechanism for external calls, allowing up to 3 attempts before failing.
- Enhanced error handling to send failure notifications when setup steps fail, including detailed error messages.
- Updated RunSummary model to include status, error step, and error message fields for better tracking of run outcomes.
- Modified database schema to store failure metadata for runs.
- Updated README.md to reflect changes in error handling and notification behavior.
This commit is contained in:
Zsolt Alföldi
2026-05-07 12:03:00 +02:00
parent 696d0227a3
commit 89835b237c
6 changed files with 268 additions and 41 deletions

View File

@@ -14,9 +14,11 @@ import (
)
const manualNeededSubject = "nCore HnR manual work"
const failureSubject = "nCore HnR check failed"
type Sender interface {
SendManualNeeded(results []model.ActionResult) error
SendFailure(step string, failure error) error
}
type NotificationNTFY struct {
@@ -29,6 +31,18 @@ func (n NotificationNTFY) SendManualNeeded(results []model.ActionResult) error {
if strings.TrimSpace(n.URL) == "" || !ok {
return nil
}
return n.sendMessage(manualNeededSubject, body)
}
func (n NotificationNTFY) SendFailure(step string, failure error) error {
if strings.TrimSpace(n.URL) == "" {
return nil
}
_, body := FailureMessage(step, failure)
return n.sendMessage(failureSubject, body)
}
func (n NotificationNTFY) sendMessage(subject string, body string) error {
client := n.HTTPClient
if client == nil {
client = &http.Client{Timeout: 15 * time.Second}
@@ -39,7 +53,7 @@ func (n NotificationNTFY) SendManualNeeded(results []model.ActionResult) error {
return err
}
req.Header.Set("Content-Type", "text/plain; charset=utf-8")
req.Header.Set("Title", manualNeededSubject)
req.Header.Set("Title", subject)
resp, err := client.Do(req)
if err != nil {
@@ -66,7 +80,18 @@ func (s NotificationSMTP) SendManualNeeded(results []model.ActionResult) error {
if strings.TrimSpace(s.Host) == "" || !ok {
return nil
}
return s.sendMessage(manualNeededSubject, body)
}
func (s NotificationSMTP) SendFailure(step string, failure error) error {
if strings.TrimSpace(s.Host) == "" {
return nil
}
_, body := FailureMessage(step, failure)
return s.sendMessage(failureSubject, body)
}
func (s NotificationSMTP) sendMessage(subject string, body string) error {
host := strings.TrimSpace(s.Host)
addr := net.JoinHostPort(host, strings.TrimSpace(s.Port))
@@ -92,7 +117,7 @@ func (s NotificationSMTP) SendManualNeeded(results []model.ActionResult) error {
message := strings.Builder{}
message.WriteString(fmt.Sprintf("From: %s\r\n", from.String()))
message.WriteString(fmt.Sprintf("To: %s\r\n", formatAddressList(recipients)))
message.WriteString(fmt.Sprintf("Subject: %s\r\n", manualNeededSubject))
message.WriteString(fmt.Sprintf("Subject: %s\r\n", subject))
message.WriteString("MIME-Version: 1.0\r\n")
message.WriteString("Content-Type: text/plain; charset=UTF-8\r\n")
message.WriteString("\r\n")
@@ -109,6 +134,17 @@ func ManualNeededMessage(results []model.ActionResult) (string, string, bool) {
return manualNeededSubject, body, ok
}
func FailureMessage(step string, failure error) (string, string) {
var body strings.Builder
body.WriteString("nCore HnR check failed after retries.\n")
body.WriteString(fmt.Sprintf("Step: %s\n", strings.TrimSpace(step)))
if failure != nil {
body.WriteString(fmt.Sprintf("Error: %s\n", failure.Error()))
}
body.WriteString("Action: review the CronJob logs. Failed nCore/qBittorrent setup steps do not clear unresolved torrent state; the next cron run will retry.\n")
return failureSubject, body.String()
}
func manualNeededText(results []model.ActionResult) (string, bool) {
var body strings.Builder
manualCount := 0