Files
josh 9bb4b09a04
CI / Lint + build + test (push) Has been cancelled
Initial commit: full Phases 1-6 implementation
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.
2026-04-17 21:32:10 -04:00

91 lines
2.1 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 15 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)
}
}