Post-repair hardware validation pipeline for Proxmox cluster hosts. Go orchestrator + in-image agent + mkosi live image + bundled dnsmasq PXE + SQLite + HTMX/SSE UI + notify registry + janitor + full docs.
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
package notify
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// NtfyNotifier posts to ntfy.sh (or a self-hosted ntfy server). Message
|
||||
// body is the plain text body; title and URL are passed via X-Title and
|
||||
// X-Click headers so ntfy renders them as the push title + deep link.
|
||||
type NtfyNotifier struct {
|
||||
NameStr string
|
||||
Server string // e.g. "https://ntfy.sh" or self-hosted
|
||||
Topic string
|
||||
HTTP *http.Client
|
||||
}
|
||||
|
||||
func NewNtfy(name, server, topic string) *NtfyNotifier {
|
||||
if server == "" {
|
||||
server = "https://ntfy.sh"
|
||||
}
|
||||
return &NtfyNotifier{
|
||||
NameStr: name,
|
||||
Server: strings.TrimRight(server, "/"),
|
||||
Topic: topic,
|
||||
HTTP: &http.Client{Timeout: 10 * time.Second},
|
||||
}
|
||||
}
|
||||
|
||||
func (n *NtfyNotifier) Name() string { return n.NameStr }
|
||||
|
||||
func (n *NtfyNotifier) Send(ctx context.Context, ev Event) error {
|
||||
if n.Topic == "" {
|
||||
return fmt.Errorf("ntfy: no topic configured")
|
||||
}
|
||||
url := n.Server + "/" + n.Topic
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, strings.NewReader(ev.Body))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ev.Title != "" {
|
||||
req.Header.Set("X-Title", ev.Title)
|
||||
}
|
||||
if ev.URL != "" {
|
||||
req.Header.Set("X-Click", ev.URL)
|
||||
}
|
||||
req.Header.Set("X-Priority", priorityForSeverity(ev.Severity))
|
||||
req.Header.Set("X-Tags", ntfyTag(ev.Kind, ev.Severity))
|
||||
|
||||
resp, err := n.HTTP.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
if resp.StatusCode >= 300 {
|
||||
b, _ := io.ReadAll(resp.Body)
|
||||
return fmt.Errorf("ntfy: %d: %s", resp.StatusCode, strings.TrimSpace(string(b)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// priorityForSeverity maps our severities to ntfy's 1–5 scale. "info"
|
||||
// → 3 (default), warning → 4, critical → 5.
|
||||
func priorityForSeverity(s Severity) string {
|
||||
switch s {
|
||||
case SeverityCritical:
|
||||
return "5"
|
||||
case SeverityWarning:
|
||||
return "4"
|
||||
default:
|
||||
return "3"
|
||||
}
|
||||
}
|
||||
|
||||
func ntfyTag(k Kind, s Severity) string {
|
||||
switch {
|
||||
case s == SeverityCritical:
|
||||
return "rotating_light," + string(k)
|
||||
case k == KindRunCompleted:
|
||||
return "white_check_mark," + string(k)
|
||||
case k == KindHoldingOpened:
|
||||
return "construction," + string(k)
|
||||
default:
|
||||
return string(k)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user