check / check (push) Waiting to run
The config tests unset variables without restoring them and read whatever the developer's shell exported, so results could differ from one machine to the next. config.ClearEnvForTest, in internal/config/testing.go, unsets every variable in the process environment and, when the test ends, leaves it exactly as it found it; every config test and the first-boot test call it before setting their own. TestEnvPositiveInt and TestEnvPort share one table runner, and the port errors name the bad value. A PORT of zero, below zero, or too large to parse now matches ErrInvalidPort, as one above 65535 already did. The README and the Settings page say an unparseable or non-positive RETENTION_SWEEP_INTERVAL stops startup. Model: opus-5-5
130 lines
3.8 KiB
Go
130 lines
3.8 KiB
Go
package handlers
|
|
|
|
import (
|
|
"net/http"
|
|
"net/netip"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"sneak.berlin/go/webhooker/internal/config"
|
|
)
|
|
|
|
// notSet is what the Settings page shows for a value that is empty.
|
|
const notSet = "not set"
|
|
|
|
// settingRow is one line of the Settings page: an environment
|
|
// variable, what it controls, and the value the server loaded for it.
|
|
type settingRow struct {
|
|
Name string
|
|
Description string
|
|
Value string
|
|
}
|
|
|
|
// HandleSettings returns a handler for the read-only Settings page,
|
|
// which lists the configuration the server started with.
|
|
func (h *Handlers) HandleSettings() http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
h.renderTemplate(w, r, "settings.html", map[string]any{
|
|
"Settings": settingRows(h.params.Config),
|
|
})
|
|
}
|
|
}
|
|
|
|
// settingRows lists every field of cfg under the environment variable
|
|
// it is read from, with the description the README's configuration
|
|
// table gives it (less its pointers to other README sections), in the
|
|
// table's order. METRICS_PASSWORD and SENTRY_DSN are credentials, so
|
|
// their values never reach the page: only whether they are set.
|
|
func settingRows(cfg *config.Config) []settingRow {
|
|
metricsUsername := cfg.MetricsUsername
|
|
if metricsUsername == "" {
|
|
metricsUsername = notSet
|
|
}
|
|
|
|
return []settingRow{
|
|
{"WEBHOOKER_ENVIRONMENT", "dev or prod", cfg.Environment},
|
|
{"PORT", "HTTP listen port", strconv.Itoa(cfg.Port)},
|
|
{
|
|
"BIND_ADDRESS",
|
|
"IP address the HTTP listener binds. Loopback by default, " +
|
|
"so the cleartext listener is not published on every " +
|
|
"interface. The Docker image ships 0.0.0.0 instead",
|
|
cfg.BindAddress,
|
|
},
|
|
{"DATA_DIR", "Directory for all SQLite databases", cfg.DataDir},
|
|
{"DEBUG", "Enable debug logging", strconv.FormatBool(cfg.Debug)},
|
|
{
|
|
"METRICS_USERNAME",
|
|
"Basic auth username for /metrics. Must be set together " +
|
|
"with METRICS_PASSWORD; one without the other fails " +
|
|
"startup",
|
|
metricsUsername,
|
|
},
|
|
{
|
|
"METRICS_PASSWORD",
|
|
"Basic auth password for /metrics. Must be set together " +
|
|
"with METRICS_USERNAME; one without the other fails " +
|
|
"startup",
|
|
setOrNotSet(cfg.MetricsPassword),
|
|
},
|
|
{
|
|
"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",
|
|
setOrNotSet(cfg.SentryDSN),
|
|
},
|
|
{
|
|
"RETENTION_SWEEP_INTERVAL",
|
|
"How often the retention reaper and archive sweeper run " +
|
|
"(Go duration, must be positive). A value that does " +
|
|
"not parse, or is zero or negative, fails startup",
|
|
cfg.RetentionSweepInterval.String(),
|
|
},
|
|
{
|
|
"SESSION_IDLE_TIMEOUT",
|
|
"Idle session timeout (Go duration)",
|
|
cfg.SessionIdleTimeout.String(),
|
|
},
|
|
{
|
|
"RECEIVER_RATE_LIMIT",
|
|
"Receiver requests/minute per IP per entrypoint " +
|
|
"(10x that per IP across the route)",
|
|
strconv.Itoa(cfg.ReceiverRateLimit),
|
|
},
|
|
{
|
|
"TRUSTED_PROXIES",
|
|
"CIDRs whose forwarded headers are trusted. A set value " +
|
|
"replaces the default. If any client can reach webhooker, " +
|
|
"or the proxy in front of it, from an RFC 1918 source " +
|
|
"address, set it to the proxy's address alone",
|
|
cidrList(cfg.TrustedProxies),
|
|
},
|
|
{
|
|
"ALLOWED_EGRESS_CIDRS",
|
|
"CIDRs that delivery targets may reach despite the " +
|
|
"SSRF blocklist",
|
|
cidrList(cfg.AllowedEgressCIDRs),
|
|
},
|
|
}
|
|
}
|
|
|
|
// setOrNotSet is how the Settings page shows a credential: whether it
|
|
// has a value, never the value itself.
|
|
func setOrNotSet(value string) string {
|
|
if value == "" {
|
|
return notSet
|
|
}
|
|
|
|
return "set"
|
|
}
|
|
|
|
// cidrList renders a CIDR list setting for the Settings page.
|
|
func cidrList(prefixes []netip.Prefix) string {
|
|
if len(prefixes) == 0 {
|
|
return "none"
|
|
}
|
|
|
|
return strings.Join(config.PrefixStrings(prefixes), ", ")
|
|
}
|