diff --git a/config.scfg b/config.scfg index 22d9b89..aed3fe6 100644 --- a/config.scfg +++ b/config.scfg @@ -1,3 +1,8 @@ +# Absolute path to another scfg configuration file which will be included. +# This directive can be specified multiple times in the main configuration, +# but only the last occurrence of a setting will be used. Settings from +# the main configuration will take precedence. +include /etc/ntfy-alertmanager/ntfy.scfg # Public facing base URL of the service (e.g. https://ntfy-alertmanager.xenrox.net) # This setting is required for the "Silence" feature. base-url https://ntfy-alertmanager.xenrox.net diff --git a/config/config.go b/config/config.go index 7e917f7..02da796 100644 --- a/config/config.go +++ b/config/config.go @@ -21,6 +21,7 @@ const ( // Config is the configuration of the bridge. type Config struct { + Include string BaseURL string HTTPAddress string LogLevel string @@ -82,6 +83,308 @@ type resolvedConfig struct { 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("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 + } + } + + labels[fmt.Sprintf("%s:%s", labelName, name)] = *labelConfig + } + } + + config.Labels.Label = labels + } + + ntfyDir := block.Get("ntfy") + if ntfyDir != nil { + 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 + } + } + } + + 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 +} + // ReadConfig reads an scfg formatted file and returns the configuration struct. func ReadConfig(path string) (*Config, error) { cfg, err := scfg.Load(path) @@ -103,326 +406,50 @@ func ReadConfig(path string) (*Config, error) { // redis config.Cache.RedisURL = "redis://localhost:6379" - d := cfg.Get("log-level") - if d != nil { - if err := d.ParseParams(&config.LogLevel); err != nil { - return nil, err - } - } - - d = cfg.Get("log-format") - if d != nil { - if err := d.ParseParams(&config.LogFormat); err != nil { - return nil, err - } - } - - d = cfg.Get("http-address") - if d != nil { - if err := d.ParseParams(&config.HTTPAddress); err != nil { - return nil, err - } - } - - d = cfg.Get("base-url") - if d != nil { - if err := d.ParseParams(&config.BaseURL); err != nil { - return nil, err - } - } - - d = cfg.Get("alert-mode") - if d != nil { - var mode string - if err := d.ParseParams(&mode); err != nil { + includeDirs := cfg.GetAll("include") + for _, d := range includeDirs { + var includePath string + if err := d.ParseParams(&includePath); err != nil { return nil, err } - switch strings.ToLower(mode) { - case "single": - config.AlertMode = Single + block, err := scfg.Load(includePath) + if err != nil { + return nil, fmt.Errorf("cannot load included config file %q: %v", includePath, err) + } - case "multi": - config.AlertMode = Multi - - default: - return nil, fmt.Errorf("%q directive: illegal mode %q", d.Name, mode) + if err := parseBlock(block, config); err != nil { + return nil, fmt.Errorf("cannot parse included config file %q: %v", includePath, err) } } - d = cfg.Get("user") - if d != nil { - if err := d.ParseParams(&config.User); err != nil { - return nil, err - } - } - - d = cfg.Get("password") - if d != nil { - if err := d.ParseParams(&config.Password); err != nil { - return nil, 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") } - labelsDir := cfg.Get("labels") - if labelsDir != nil { - d = labelsDir.Children.Get("order") - if d != nil { - var order string - if err := d.ParseParams(&order); err != nil { - return nil, 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 nil, err - } - - d = labelDir.Children.Get("priority") - if d != nil { - if err := d.ParseParams(&labelConfig.Priority); err != nil { - return nil, err - } - } - - d = labelDir.Children.Get("tags") - if d != nil { - var tags string - if err := d.ParseParams(&tags); err != nil { - return nil, err - } - - labelConfig.Tags = strings.Split(tags, ",") - } - - d = labelDir.Children.Get("icon") - if d != nil { - if err := d.ParseParams(&labelConfig.Icon); err != nil { - return nil, err - } - } - - d = labelDir.Children.Get("email-address") - if d != nil { - if err := d.ParseParams(&labelConfig.EmailAddress); err != nil { - return nil, err - } - } - - d = labelDir.Children.Get("call") - if d != nil { - if err := d.ParseParams(&labelConfig.Call); err != nil { - return nil, err - } - } - - labels[fmt.Sprintf("%s:%s", labelName, name)] = *labelConfig - } - } - - config.Labels.Label = labels - } - - ntfyDir := cfg.Get("ntfy") - if ntfyDir == nil { - return nil, fmt.Errorf("%q directive missing", "ntfy") - } - - d = ntfyDir.Children.Get("topic") - if d == nil { + if config.Ntfy.Topic == "" { return nil, fmt.Errorf("%q missing from %q directive", "topic", "ntfy") } - if err := d.ParseParams(&config.Ntfy.Topic); err != nil { - return nil, err - } - - d = ntfyDir.Children.Get("user") - if d != nil { - if err := d.ParseParams(&config.Ntfy.User); err != nil { - return nil, err - } - } - - d = ntfyDir.Children.Get("password") - if d != nil { - if err := d.ParseParams(&config.Ntfy.Password); err != nil { - return nil, err - } - } 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") } - d = ntfyDir.Children.Get("access-token") - if d != nil { - if err := d.ParseParams(&config.Ntfy.AccessToken); err != nil { - return nil, err - } - } - 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") } - d = ntfyDir.Children.Get("certificate-fingerprint") - if d != nil { - if err := d.ParseParams(&config.Ntfy.CertFingerprint); err != nil { - return nil, 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 nil, err - } - } - - d = ntfyDir.Children.Get("call") - if d != nil { - if err := d.ParseParams(&config.Ntfy.Call); err != nil { - return nil, err - } - } - - cacheDir := cfg.Get("cache") - - if cacheDir != nil { - d = cacheDir.Children.Get("type") - if d != nil { - if err := d.ParseParams(&config.Cache.Type); err != nil { - return nil, err - } - } - - var durationString string - d = cacheDir.Children.Get("duration") - if d != nil { - if err := d.ParseParams(&durationString); err != nil { - return nil, err - } - - duration, err := time.ParseDuration(durationString) - if err != nil { - return nil, 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 nil, err - } - - interval, err := time.ParseDuration(cleanupIntervalString) - if err != nil { - return nil, 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 nil, err - } - } - } - - amDir := cfg.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 nil, err - } - - duration, err := time.ParseDuration(durationString) - if err != nil { - return nil, err - } - - config.Am.SilenceDuration = duration - } - - d = amDir.Children.Get("user") - if d != nil { - if err := d.ParseParams(&config.Am.User); err != nil { - return nil, err - } - } - - d = amDir.Children.Get("password") - if d != nil { - if err := d.ParseParams(&config.Am.Password); err != nil { - return nil, err - } - } - - 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") - } - - d = amDir.Children.Get("url") - if d != nil { - if err := d.ParseParams(&config.Am.URL); err != nil { - return nil, err - } - } - } - - resolvedDir := cfg.Get("resolved") - if resolvedDir != nil { - d = resolvedDir.Children.Get("tags") - if d != nil { - var tags string - if err := d.ParseParams(&tags); err != nil { - return nil, 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 nil, err - } - } - - d = resolvedDir.Children.Get("priority") - if d != nil { - if err := d.ParseParams(&config.Resolved.Priority); err != nil { - return nil, err - } - } + 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