From e882e7d23732fb44f6ec8d6f269adbf44e10a1eb Mon Sep 17 00:00:00 2001 From: clawbot Date: Mon, 2 Mar 2026 01:26:55 +0100 Subject: [PATCH] feat: fail fast when no monitoring targets configured (#75) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary When `DNSWATCHER_TARGETS` is empty (the default), dnswatcher previously started successfully and ran indefinitely monitoring nothing. This is a common misconfiguration — forgetting to set the variable or making a typo in its name — and gave no indication anything was wrong. ## Changes - Added `ErrNoTargets` sentinel error in `internal/config/config.go` - Extracted `parseAndValidateTargets()` helper to validate that at least one domain or hostname is configured after target classification - If no targets are configured, dnswatcher now exits with a clear error: `"no monitoring targets configured: set DNSWATCHER_TARGETS environment variable"` - Updated README.md to document that `DNSWATCHER_TARGETS` is required and dnswatcher will refuse to start without it ## How it works The validation runs during config construction (via uber/fx), before the watcher or any other component starts. If `DNSWATCHER_TARGETS` is empty or contains only whitespace/empty entries, `buildConfig()` returns `ErrNoTargets`, which causes fx to fail startup with a clear error message. This is fail-fast behavior: a monitoring daemon with nothing to monitor is a misconfiguration and should not silently run. Closes https://git.eeqj.de/sneak/dnswatcher/issues/69 Co-authored-by: clawbot Reviewed-on: https://git.eeqj.de/sneak/dnswatcher/pulls/75 Co-authored-by: clawbot Co-committed-by: clawbot --- README.md | 6 ++++++ internal/config/config.go | 28 ++++++++++++++++++++++++---- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 0b30a9b..460d761 100644 --- a/README.md +++ b/README.md @@ -210,6 +210,12 @@ the following precedence (highest to lowest): | `DNSWATCHER_METRICS_USERNAME` | Basic auth username for /metrics | `""` | | `DNSWATCHER_METRICS_PASSWORD` | Basic auth password for /metrics | `""` | +**`DNSWATCHER_TARGETS` is required.** dnswatcher will refuse to start if no +monitoring targets are configured. A monitoring daemon with nothing to monitor +is a misconfiguration, so dnswatcher fails fast with a clear error message +rather than running silently. Set `DNSWATCHER_TARGETS` to a comma-separated +list of DNS names before starting. + ### Example `.env` ```sh diff --git a/internal/config/config.go b/internal/config/config.go index 0acf89e..1368c46 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -23,6 +23,11 @@ const ( defaultTLSExpiryWarning = 7 ) +// ErrNoTargets is returned when no monitoring targets are configured. +var ErrNoTargets = errors.New( + "no monitoring targets configured: set DNSWATCHER_TARGETS environment variable", +) + // Params contains dependencies for Config. type Params struct { fx.In @@ -132,11 +137,9 @@ func buildConfig( tlsInterval = defaultTLSInterval } - domains, hostnames, err := ClassifyTargets( - parseCSV(viper.GetString("TARGETS")), - ) + domains, hostnames, err := parseAndValidateTargets() if err != nil { - return nil, fmt.Errorf("invalid targets configuration: %w", err) + return nil, err } cfg := &Config{ @@ -162,6 +165,23 @@ func buildConfig( return cfg, nil } +func parseAndValidateTargets() ([]string, []string, error) { + domains, hostnames, err := ClassifyTargets( + parseCSV(viper.GetString("TARGETS")), + ) + if err != nil { + return nil, nil, fmt.Errorf( + "invalid targets configuration: %w", err, + ) + } + + if len(domains) == 0 && len(hostnames) == 0 { + return nil, nil, ErrNoTargets + } + + return domains, hostnames, nil +} + func parseCSV(input string) []string { if input == "" { return nil