package config
import (
"cmp"
"encoding/json/v2"
"errors"
"log/slog"
"net/url"
"os"
"sync"
)
func die(err error, msg string) {
if err == nil {
return
}
log.Error(msg, "err", err)
os.Exit(1)
}
func unwrap[T any](t T, err error) T {
die(err, "bad value")
return t
}
var log = slog.New(slog.NewJSONHandler(os.Stdout, nil))
type DomainGroup struct {
Interval int `json:"interval"`
Retries int `json:"retries"`
Domains []string `json:"domains"`
}
type Cicada struct {
DownString string `json:"down_string"`
UpString string `json:"up_string"`
SendAlertUrl string `json:"send_alert_url"`
Key string `json:"key"`
}
type GetsSection struct {
PassIfOne bool `json:"pass_if_one"`
Interval int `json:"interval"`
Severity string `json:"severity"`
Urls []string `json:"urls"`
}
type Http struct {
Timeout int `json:"timeout"`
Ua string `json:"ua"`
}
type Config struct {
Http Http `json:"http"`
Gets map[string]GetsSection `json:"gets"`
SeeOthers map[string]string `json:"see_others"`
SeeOtherRetries int `json:"see_other_retries"`
SeeOtherInterval int `json:"see_other_interval"`
Commands []Command `json:"commands"`
Cicada Cicada `json:"cicada"`
}
type Command struct {
// arrange (name/describe)
Name string `json:"name"`
Interval int `json:"interval"`
Service string `json:"service"`
Component string `json:"component"`
Retries int `json:"retries"`
// act
Command string `json:"command"`
// Args []string `json:"args"`
// assert
ExactOutput string `json:"exact_output"`
MatchAny []string `json:"match_any"`
MatchAll []string `json:"match_all"`
}
var cfg Config
var cfgOnce sync.Once
func (c Config) MustValidate() {
errs := []error{}
if c.Cicada.Key == "" {
errs = append(errs, errors.New("need to pass NYMPH_CICADA_KEY"))
}
if len(cfg.SeeOthers)+len(cfg.Gets)+len(cfg.Commands) == 0 {
errs = append(errs, errors.New("no checks defined"))
}
_, urlParseErr := url.Parse(c.Cicada.SendAlertUrl)
if urlParseErr != nil {
errs = append(errs, urlParseErr)
}
die(errors.Join(errs...), "could not validate config")
}
func Must() Config {
cfgOnce.Do(func() {
path := cmp.Or(os.Getenv("NYMPH_CONFIG"), "/etc/nymph/nymph.json")
die(json.Unmarshal(unwrap(os.ReadFile(path)), &cfg), "could not read config file")
cicadaKey := os.Getenv("NYMPH_CICADA_KEY")
cfg.Cicada.Key = cicadaKey
if cfg.Http.Timeout == 0 {
cfg.Http.Timeout = 10
}
if cfg.Http.Ua == "" {
cfg.Http.Ua = "nymph-uptime-checker"
}
cfg.MustValidate()
})
return cfg
}