Fail loudly on an unparseable SENTRY_DSN and a malformed .env (closes #283)
All checks were successful
check / check (push) Successful in 3m32s
All checks were successful
check / check (push) Successful in 3m32s
Two configuration paths still failed silently, against the rule every other variable follows: a value that is set but cannot be parsed must abort startup rather than substitute a default. SENTRY_DSN is now parsed in loadFromEnv, with sentry.NewDsn — the same call sentry.Init makes on the DSN it is handed, so configuration and initialisation cannot disagree about what a valid DSN is. That costs internal/config an import of the Sentry SDK, which is already a module dependency already linked into the binary, and buys a single definition of validity rather than a hand-rolled second one free to drift. A typo in a DSN used to log one error line and leave the process serving with error reporting off forever, which nothing downstream can notice: the variable is still set, so every later signal reports it as on. hasSentryDSN is replaced by Config.SentryEnabled(), following MetricsAuthEnabled(): one method read by the startup log field, by the SDK initialisation and by the sentryhttp middleware, so the log cannot report reporting as on while nothing is sending. enableSentry's error branch is now fatal, and Run gives up before it listens rather than binding a port it is about to release. Fatal there means what a listen failure already meant — Shutdowner.Shutdown(fx.ExitCode(1)), through fx's normal stop sequence — so shutdownOnListenFailure is now shutdownWithFailure and ListenFailureExitCode is StartupFailureExitCode. The godotenv/autoload blank import is replaced by config.LoadDotEnv, called at the top of dispatch. autoload discarded Load's error, and godotenv applies nothing at all when a file will not parse, so one mistyped line reverted every variable in the file to its default and started the server with no log line naming the file. A missing file stays fine — it is optional and most deployments have none. The call sits in dispatch rather than in loadFromEnv because autoload ran in an init(), ahead of config.DataDir(), which both the DATA_DIR lock and resetpw call outside the fx graph; loading any later would let a .env that sets DATA_DIR lock one directory while the config opened databases in another. Both defects were reproduced against the previous build first: an unparseable DSN served traffic while logging "hasSentryDSN":true, and a malformed .env started on the default port with the file unmentioned.
This commit is contained in:
28
README.md
28
README.md
@@ -71,8 +71,18 @@ make clean # Remove bin/
|
||||
### Configuration
|
||||
|
||||
All configuration is via environment variables. For local development,
|
||||
you can place variables in a `.env` file in the project root (loaded
|
||||
automatically via `godotenv/autoload`).
|
||||
you can place variables in a `.env` file in the process working
|
||||
directory, read once at startup before anything else looks at the
|
||||
environment.
|
||||
|
||||
The file is optional and having none is the normal case for a
|
||||
deployment. A file that is there but cannot be parsed aborts startup
|
||||
with a message naming it, because a single malformed line makes none
|
||||
of the file apply: every variable in it silently reverts to its
|
||||
default, which is exactly the failure [Invalid values abort
|
||||
startup](#invalid-values-abort-startup) exists to prevent, for all of
|
||||
them at once. A variable already present in the real environment wins
|
||||
over the file's value for the same name.
|
||||
|
||||
The environment is selected by setting `WEBHOOKER_ENVIRONMENT` to `dev`
|
||||
or `prod` (default: `dev`). The setting controls exactly one behavior:
|
||||
@@ -125,7 +135,7 @@ TTY detection, and security headers are always applied.
|
||||
| `MAINTENANCE_MODE` | Report `maintenanceMode: true` in the healthcheck JSON. It does not change how any request is served — no maintenance page exists | `false` |
|
||||
| `METRICS_USERNAME` | Basic auth username for `/metrics`. Must be set together with `METRICS_PASSWORD`; one without the other fails startup | `""` |
|
||||
| `METRICS_PASSWORD` | Basic auth password for `/metrics`. Must be set together with `METRICS_USERNAME`; one without the other fails startup | `""` |
|
||||
| `SENTRY_DSN` | Sentry error reporting DSN | `""` |
|
||||
| `SENTRY_DSN` | Sentry error reporting DSN. Unset leaves error reporting off; a value the Sentry SDK cannot parse fails startup rather than serving with reporting silently off | `""` |
|
||||
| `RETENTION_SWEEP_INTERVAL` | How often the retention reaper and archive sweeper run (Go duration, must be positive) | `1h` |
|
||||
| `SESSION_IDLE_TIMEOUT` | Idle session timeout (Go duration) | `24h` |
|
||||
| `RECEIVER_RATE_LIMIT` | Receiver requests/minute per IP per entrypoint (10x that per IP across the route) | `120` |
|
||||
@@ -464,10 +474,20 @@ startup), every entry in `TRUSTED_PROXIES` and
|
||||
`ALLOWED_EGRESS_CIDRS` must be a CIDR block or a bare IP address, and
|
||||
`BIND_ADDRESS` must be an IP address literal — `localhost`,
|
||||
`127.0.0.1:8080` and `10.0.0.0/8` are each rejected rather than
|
||||
resolved, split, or narrowed to something they do not say.
|
||||
resolved, split, or narrowed to something they do not say — and
|
||||
`SENTRY_DSN` must parse as a Sentry DSN.
|
||||
`SESSION_IDLE_TIMEOUT` is the exception: a
|
||||
non-positive value there means idle expiry is disabled, not invalid.
|
||||
|
||||
`SENTRY_DSN` is checked with the Sentry SDK's own parser, the same call
|
||||
the SDK makes on the DSN it is later handed, so what configuration
|
||||
accepts is exactly what will initialise. A typo in it is the one
|
||||
configuration mistake nothing downstream can ever notice — the variable
|
||||
is still set, so every later signal reports error reporting as on while
|
||||
no report is being sent — which is why it aborts rather than starting
|
||||
with reporting off. Leaving it unset is not a mistake and not affected:
|
||||
error reporting is simply off and startup is normal.
|
||||
|
||||
Boolean variables (`DEBUG`, `MAINTENANCE_MODE`) accept exactly the
|
||||
spellings Go's `strconv.ParseBool` accepts — `1`, `t`, `T`, `TRUE`,
|
||||
`true`, `True`, `0`, `f`, `F`, `FALSE`, `false`, `False` — and nothing
|
||||
|
||||
Reference in New Issue
Block a user