9bb4b09a04
CI / Lint + build + test (push) Has been cancelled
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.
91 lines
2.1 KiB
Go
91 lines
2.1 KiB
Go
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)
|
||
}
|
||
}
|