Setting this to "https://ntfy.sh" has security implications: If the user forgets to set his server, but uses the new short form for topics, the notification will be sent to "ntfy.sh" and could expose information.
487 lines
10 KiB
Go
487 lines
10 KiB
Go
// Package config defines the configuration file.
|
|
package config
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"git.sr.ht/~emersion/go-scfg"
|
|
)
|
|
|
|
// AlertMode determines if alerts grouped by Alertmanager are being kept together.
|
|
type AlertMode int
|
|
|
|
// The different modes for AlertMode.
|
|
const (
|
|
Single AlertMode = iota
|
|
Multi
|
|
)
|
|
|
|
// Config is the configuration of the bridge.
|
|
type Config struct {
|
|
BaseURL string
|
|
HTTPAddress string
|
|
LogLevel string
|
|
LogFormat string
|
|
LogFile string
|
|
AlertMode AlertMode
|
|
User string
|
|
Password string
|
|
Ntfy ntfyConfig
|
|
Labels labels
|
|
Cache CacheConfig
|
|
Am alertmanagerConfig
|
|
Resolved resolvedConfig
|
|
}
|
|
|
|
type ntfyConfig struct {
|
|
Server string
|
|
Topic string
|
|
User string
|
|
Password string
|
|
AccessToken string
|
|
CertFingerprint string
|
|
EmailAddress string
|
|
Call string
|
|
GeneratorURLLabel string
|
|
}
|
|
|
|
type labels struct {
|
|
Order []string
|
|
Label map[string]labelConfig
|
|
}
|
|
|
|
type labelConfig struct {
|
|
Priority string
|
|
Tags []string
|
|
Icon string
|
|
EmailAddress string
|
|
Call string
|
|
Topic string
|
|
}
|
|
|
|
// CacheConfig is the configuration of the cache.
|
|
type CacheConfig struct {
|
|
// shared settings
|
|
Type string
|
|
Duration time.Duration
|
|
// memory settings
|
|
CleanupInterval time.Duration
|
|
// redis settings
|
|
RedisURL string
|
|
}
|
|
|
|
type alertmanagerConfig struct {
|
|
User string
|
|
Password string
|
|
SilenceDuration time.Duration
|
|
URL string
|
|
}
|
|
|
|
type resolvedConfig struct {
|
|
Tags []string
|
|
Icon string
|
|
Priority string
|
|
}
|
|
|
|
func parseBlock(block scfg.Block, config *Config) error {
|
|
d := block.Get("log-level")
|
|
if d != nil {
|
|
if err := d.ParseParams(&config.LogLevel); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
d = block.Get("log-format")
|
|
if d != nil {
|
|
if err := d.ParseParams(&config.LogFormat); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
d = block.Get("log-file")
|
|
if d != nil {
|
|
if err := d.ParseParams(&config.LogFile); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
d = block.Get("http-address")
|
|
if d != nil {
|
|
if err := d.ParseParams(&config.HTTPAddress); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
d = block.Get("base-url")
|
|
if d != nil {
|
|
if err := d.ParseParams(&config.BaseURL); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
d = block.Get("alert-mode")
|
|
if d != nil {
|
|
var mode string
|
|
if err := d.ParseParams(&mode); err != nil {
|
|
return err
|
|
}
|
|
|
|
switch strings.ToLower(mode) {
|
|
case "single":
|
|
config.AlertMode = Single
|
|
|
|
case "multi":
|
|
config.AlertMode = Multi
|
|
|
|
default:
|
|
return fmt.Errorf("%q directive: illegal mode %q", d.Name, mode)
|
|
}
|
|
}
|
|
|
|
d = block.Get("user")
|
|
if d != nil {
|
|
if err := d.ParseParams(&config.User); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
d = block.Get("password")
|
|
if d != nil {
|
|
if err := d.ParseParams(&config.Password); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
labelsDir := block.Get("labels")
|
|
if labelsDir != nil {
|
|
d = labelsDir.Children.Get("order")
|
|
if d != nil {
|
|
var order string
|
|
if err := d.ParseParams(&order); err != nil {
|
|
return err
|
|
}
|
|
|
|
config.Labels.Order = strings.Split(order, ",")
|
|
}
|
|
|
|
labels := make(map[string]labelConfig)
|
|
for _, labelName := range config.Labels.Order {
|
|
for _, labelDir := range labelsDir.Children.GetAll(labelName) {
|
|
labelConfig := new(labelConfig)
|
|
var name string
|
|
|
|
if err := labelDir.ParseParams(&name); err != nil {
|
|
return err
|
|
}
|
|
|
|
d = labelDir.Children.Get("priority")
|
|
if d != nil {
|
|
if err := d.ParseParams(&labelConfig.Priority); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
d = labelDir.Children.Get("tags")
|
|
if d != nil {
|
|
var tags string
|
|
if err := d.ParseParams(&tags); err != nil {
|
|
return err
|
|
}
|
|
|
|
labelConfig.Tags = strings.Split(tags, ",")
|
|
}
|
|
|
|
d = labelDir.Children.Get("icon")
|
|
if d != nil {
|
|
if err := d.ParseParams(&labelConfig.Icon); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
d = labelDir.Children.Get("email-address")
|
|
if d != nil {
|
|
if err := d.ParseParams(&labelConfig.EmailAddress); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
d = labelDir.Children.Get("call")
|
|
if d != nil {
|
|
if err := d.ParseParams(&labelConfig.Call); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
d = labelDir.Children.Get("topic")
|
|
if d != nil {
|
|
if err := d.ParseParams(&labelConfig.Topic); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
labels[fmt.Sprintf("%s:%s", labelName, name)] = *labelConfig
|
|
}
|
|
}
|
|
|
|
config.Labels.Label = labels
|
|
}
|
|
|
|
ntfyDir := block.Get("ntfy")
|
|
if ntfyDir != nil {
|
|
d = ntfyDir.Children.Get("server")
|
|
if d != nil {
|
|
if err := d.ParseParams(&config.Ntfy.Server); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
d = ntfyDir.Children.Get("topic")
|
|
if d != nil {
|
|
if err := d.ParseParams(&config.Ntfy.Topic); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
d = ntfyDir.Children.Get("user")
|
|
if d != nil {
|
|
if err := d.ParseParams(&config.Ntfy.User); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
d = ntfyDir.Children.Get("password")
|
|
if d != nil {
|
|
if err := d.ParseParams(&config.Ntfy.Password); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
d = ntfyDir.Children.Get("access-token")
|
|
if d != nil {
|
|
if err := d.ParseParams(&config.Ntfy.AccessToken); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
d = ntfyDir.Children.Get("certificate-fingerprint")
|
|
if d != nil {
|
|
if err := d.ParseParams(&config.Ntfy.CertFingerprint); err != nil {
|
|
return err
|
|
}
|
|
|
|
// hex.EncodeToString outputs a lower case string
|
|
config.Ntfy.CertFingerprint = strings.ToLower(strings.ReplaceAll(config.Ntfy.CertFingerprint, ":", ""))
|
|
}
|
|
|
|
d = ntfyDir.Children.Get("email-address")
|
|
if d != nil {
|
|
if err := d.ParseParams(&config.Ntfy.EmailAddress); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
d = ntfyDir.Children.Get("call")
|
|
if d != nil {
|
|
if err := d.ParseParams(&config.Ntfy.Call); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
d = ntfyDir.Children.Get("generator-url-label")
|
|
if d != nil {
|
|
if err := d.ParseParams(&config.Ntfy.GeneratorURLLabel); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
cacheDir := block.Get("cache")
|
|
if cacheDir != nil {
|
|
d = cacheDir.Children.Get("type")
|
|
if d != nil {
|
|
if err := d.ParseParams(&config.Cache.Type); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
var durationString string
|
|
d = cacheDir.Children.Get("duration")
|
|
if d != nil {
|
|
if err := d.ParseParams(&durationString); err != nil {
|
|
return err
|
|
}
|
|
|
|
duration, err := time.ParseDuration(durationString)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
config.Cache.Duration = duration
|
|
}
|
|
|
|
// memory
|
|
var cleanupIntervalString string
|
|
d = cacheDir.Children.Get("cleanup-interval")
|
|
if d != nil {
|
|
if err := d.ParseParams(&cleanupIntervalString); err != nil {
|
|
return err
|
|
}
|
|
|
|
interval, err := time.ParseDuration(cleanupIntervalString)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
config.Cache.CleanupInterval = interval
|
|
}
|
|
|
|
// redis
|
|
d = cacheDir.Children.Get("redis-url")
|
|
if d != nil {
|
|
if err := d.ParseParams(&config.Cache.RedisURL); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
amDir := block.Get("alertmanager")
|
|
if amDir != nil {
|
|
var durationString string
|
|
d = amDir.Children.Get("silence-duration")
|
|
if d != nil {
|
|
if err := d.ParseParams(&durationString); err != nil {
|
|
return err
|
|
}
|
|
|
|
duration, err := time.ParseDuration(durationString)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
config.Am.SilenceDuration = duration
|
|
}
|
|
|
|
d = amDir.Children.Get("user")
|
|
if d != nil {
|
|
if err := d.ParseParams(&config.Am.User); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
d = amDir.Children.Get("password")
|
|
if d != nil {
|
|
if err := d.ParseParams(&config.Am.Password); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
d = amDir.Children.Get("url")
|
|
if d != nil {
|
|
if err := d.ParseParams(&config.Am.URL); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
resolvedDir := block.Get("resolved")
|
|
if resolvedDir != nil {
|
|
d = resolvedDir.Children.Get("tags")
|
|
if d != nil {
|
|
var tags string
|
|
if err := d.ParseParams(&tags); err != nil {
|
|
return err
|
|
}
|
|
|
|
config.Resolved.Tags = strings.Split(tags, ",")
|
|
}
|
|
|
|
d = resolvedDir.Children.Get("icon")
|
|
if d != nil {
|
|
if err := d.ParseParams(&config.Resolved.Icon); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
d = resolvedDir.Children.Get("priority")
|
|
if d != nil {
|
|
if err := d.ParseParams(&config.Resolved.Priority); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Read reads an scfg formatted file and returns the configuration struct.
|
|
func Read(path string) (*Config, error) {
|
|
cfg, err := scfg.Load(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
config := new(Config)
|
|
// Set default values
|
|
config.HTTPAddress = "127.0.0.1:8080"
|
|
config.LogLevel = "info"
|
|
config.LogFormat = "text"
|
|
config.AlertMode = Multi
|
|
|
|
config.Cache.Type = "disabled"
|
|
config.Cache.Duration = time.Hour * 24
|
|
// memory
|
|
config.Cache.CleanupInterval = time.Hour
|
|
// redis
|
|
config.Cache.RedisURL = "redis://localhost:6379"
|
|
|
|
includeDirs := cfg.GetAll("include")
|
|
for _, d := range includeDirs {
|
|
var includePath string
|
|
if err := d.ParseParams(&includePath); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
block, err := scfg.Load(includePath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot load included config file %q: %v", includePath, err)
|
|
}
|
|
|
|
if err := parseBlock(block, config); err != nil {
|
|
return nil, fmt.Errorf("cannot parse included config file %q: %v", includePath, err)
|
|
}
|
|
}
|
|
|
|
err = parseBlock(cfg, config)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Check settings
|
|
if (config.Password != "" && config.User == "") ||
|
|
(config.Password == "" && config.User != "") {
|
|
return nil, errors.New("user and password have to be set together")
|
|
}
|
|
|
|
if config.Ntfy.Topic == "" {
|
|
return nil, fmt.Errorf("%q missing from %q directive", "topic", "ntfy")
|
|
}
|
|
|
|
if (config.Ntfy.Password != "" && config.Ntfy.User == "") ||
|
|
(config.Ntfy.Password == "" && config.Ntfy.User != "") {
|
|
return nil, errors.New("ntfy: user and password have to be set together")
|
|
}
|
|
|
|
if config.Ntfy.User != "" && config.Ntfy.AccessToken != "" {
|
|
return nil, errors.New("ntfy: cannot use both an access-token and a user/password at the same time")
|
|
}
|
|
|
|
if (config.Am.Password != "" && config.Am.User == "") ||
|
|
(config.Am.Password == "" && config.Am.User != "") {
|
|
return nil, errors.New("alertmanager: user and password have to be set together")
|
|
}
|
|
|
|
return config, nil
|
|
}
|