diff --git a/README.md b/README.md index 2ab577b..b5ef9f6 100644 --- a/README.md +++ b/README.md @@ -354,10 +354,10 @@ pixa finds: `/etc/pixa/config.yml`, `/etc/pixa/config.yaml`, `~/.config/pixa/config.yml`, `~/.config/pixa/config.yaml`, then `config.yml` and `config.yaml` in the working directory. A named file that does not exist, cannot be read or does not parse aborts startup. Of the files pixa looks for on -its own, one it finds but cannot read or parse aborts startup; one it cannot -find, for any reason, is passed over without a message, even when the file is -there in a directory pixa may not enter. With no file, pixa uses the environment -and the defaults. +its own, only one that does not exist is passed over, without a message. One +that pixa cannot read or parse aborts startup, naming the file. So does one in a +directory pixa may not enter, whether or not it is there, since pixa cannot +tell. With no file, pixa uses the environment and the defaults. | Variable | Config key | Meaning | | ------------------------------------ | ------------------------------- | ---------------------------------------------------------------------------- | diff --git a/TODO.md b/TODO.md index 224b2da..a000929 100644 --- a/TODO.md +++ b/TODO.md @@ -29,6 +29,11 @@ P2: security: referer blacklist # Completed Steps +- 2026-10-04 a config file pixa cannot read aborts startup (closes #176): of the + places pixa looks for its config file on its own, only one where the file does + not exist is passed over; any other error, such as a directory on the path + that pixa may not enter, aborts startup naming the file, as a file that does + not parse already did. - 2026-10-04 the metrics basic auth, CORS preflight, request logging and metrics recording have tests (closes #79): `MetricsAuth` on its own answers 401 with a challenge without credentials or with a wrong username or password diff --git a/internal/config/config.go b/internal/config/config.go index b8e041b..35254b3 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -4,6 +4,7 @@ package config import ( "errors" "fmt" + "io/fs" "log/slog" "math" "net/netip" @@ -778,19 +779,25 @@ func loadConfigFile(log *slog.Logger, appName string) (*smartconfig.Config, erro for _, path := range configPaths { cleanPath := filepath.Clean(path) + // Only a config file that does not exist is skipped. One that + // cannot be read or does not parse is a fatal startup error. _, statErr := os.Stat(cleanPath) - if statErr == nil { - // A config file that exists but does not parse is a fatal - // startup error, never something to skip over. - sc, err := smartconfig.NewFromConfigPath(path) - if err != nil { - return nil, fmt.Errorf("failed to parse config file %s: %w", path, err) - } - - log.Info("loaded config file", "path", path) - - return sc, nil + if errors.Is(statErr, fs.ErrNotExist) { + continue } + + if statErr != nil { + return nil, fmt.Errorf("failed to read config file %s: %w", path, statErr) + } + + sc, err := smartconfig.NewFromConfigPath(path) + if err != nil { + return nil, fmt.Errorf("failed to parse config file %s: %w", path, err) + } + + log.Info("loaded config file", "path", path) + + return sc, nil } return nil, nil //nolint:nilnil // nil config is valid (use defaults)