From 0824ae9c2fef9a2d7d2185c4f6b0facd53913955 Mon Sep 17 00:00:00 2001 From: clawbot <35+clawbot@noreply.example.org> Date: Fri, 2 Oct 2026 10:32:32 +0000 Subject: [PATCH] Remove the no-op MAINTENANCE_MODE setting (closes #317) MAINTENANCE_MODE only made the healthcheck report maintenanceMode: true; no request was ever served differently. It is removed from the configuration, the healthcheck JSON, the README and the Settings page, together with the unused Server.MaintenanceMode method and the healthcheck's Config dependency, which existed only to read it. A leftover MAINTENANCE_MODE in the environment is not read, so it is ignored rather than refused. Model: opus-5-5 --- README.md | 7 +++---- internal/config/config.go | 8 -------- internal/config/env_test.go | 17 ++++------------- internal/handlers/settings.go | 7 ------- internal/handlers/settings_test.go | 9 +++------ internal/healthcheck/healthcheck.go | 4 ---- internal/server/server.go | 6 ------ 7 files changed, 10 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index b4c1037..2f7abf5 100644 --- a/README.md +++ b/README.md @@ -135,7 +135,6 @@ TTY detection, and security headers are always applied. | `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. See [Bind address](#bind-address) | `127.0.0.1` (image: `0.0.0.0`) | | `DATA_DIR` | Directory for all SQLite databases | `/var/lib/webhooker` | | `DEBUG` | Enable debug logging | `false` | -| `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. Unset leaves error reporting off; a value the Sentry SDK cannot parse fails startup rather than serving with reporting silently off | `""` | @@ -515,8 +514,8 @@ 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`, +The boolean variable `DEBUG` accepts exactly the spellings Go's +`strconv.ParseBool` accepts — `1`, `t`, `T`, `TRUE`, `true`, `True`, `0`, `f`, `F`, `FALSE`, `false`, `False` — and nothing else. `yes`, `on`, and `off` are rejected rather than quietly treated as false. @@ -2775,7 +2774,7 @@ abuse limit later; they are tracked as future work. | Method | Path | Description | | ------ | --------------------------- | ----------- | | `GET` | `/` | Root redirect, 303 (authenticated → `/hooks`, unauthenticated → `/pages/login`) | -| `GET` | `/.well-known/healthcheck` | Health check (JSON: `status`, `now`, `uptimeSeconds`, `uptimeHuman`, `version`, `appname`, `maintenanceMode`) | +| `GET` | `/.well-known/healthcheck` | Health check (JSON: `status`, `now`, `uptimeSeconds`, `uptimeHuman`, `version`, `appname`) | | `GET`, `HEAD` | `/s/*` | Static file serving (embedded CSS, JS). `GET` and `HEAD` only — `POST`, `PUT`, `PATCH`, `DELETE`, `OPTIONS`, `TRACE` and `CONNECT` are answered `405 Method Not Allowed` with `Allow: GET, HEAD`. Any other method (such as `PROPFIND`) is refused by chi before it reaches this route, and gets `405` without an `Allow` header. Pinned by `TestStaticServesOnlyGetAndHead` | | `POST` | `/h/{uuid}` | Webhook receiver endpoint. `POST` only — every other method is answered `405 Method Not Allowed` with `Allow: POST`. Rate limited (see [Rate Limiting](#rate-limiting)) | diff --git a/internal/config/config.go b/internal/config/config.go index f1b74b8..386f17a 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -149,7 +149,6 @@ type ConfigParams struct { type Config struct { DataDir string Debug bool - MaintenanceMode bool Environment string MetricsPassword string MetricsUsername string @@ -658,11 +657,6 @@ func loadFromEnv() (*Config, error) { return nil, err } - maintenanceMode, err := envBool("MAINTENANCE_MODE", false) - if err != nil { - return nil, err - } - retentionSweepInterval, err := envPositiveDuration( "RETENTION_SWEEP_INTERVAL", defaultRetentionSweepInterval, @@ -712,7 +706,6 @@ func loadFromEnv() (*Config, error) { return &Config{ DataDir: DataDir(), Debug: debug, - MaintenanceMode: maintenanceMode, Environment: environment, MetricsUsername: metricsUsername, MetricsPassword: metricsPassword, @@ -799,7 +792,6 @@ func New(lc fx.Lifecycle, params ConfigParams) (*Config, error) { // host can reach the admin UI. "bindAddress", s.BindAddress, "debug", s.Debug, - "maintenanceMode", s.MaintenanceMode, "dataDir", s.DataDir, "retentionSweepInterval", s.RetentionSweepInterval.String(), // Logged because a perfectly valid non-positive value here diff --git a/internal/config/env_test.go b/internal/config/env_test.go index 29985c6..6978f0f 100644 --- a/internal/config/env_test.go +++ b/internal/config/env_test.go @@ -18,10 +18,9 @@ const testEnvKey = "WEBHOOKER_TEST_VALUE" // Real configuration variables exercised by the config.New tests. const ( - envKeyPort = "PORT" - envKeyDebug = "DEBUG" - envKeyMaintenanceMode = "MAINTENANCE_MODE" - envKeyBindAddress = "BIND_ADDRESS" + envKeyPort = "PORT" + envKeyDebug = "DEBUG" + envKeyBindAddress = "BIND_ADDRESS" ) // Sample BIND_ADDRESS values used by the tables below. @@ -604,12 +603,6 @@ func flagEnvValueCases() []badEnvValueCase { value: "ture", expectError: true, }, - { - name: "unparseable MAINTENANCE_MODE aborts startup", - key: envKeyMaintenanceMode, - value: "sometimes", - expectError: true, - }, } } @@ -656,8 +649,7 @@ func TestNewUsesDefaultsWhenUnset(t *testing.T) { t.Setenv("WEBHOOKER_ENVIRONMENT", "dev") for _, key := range []string{ - envKeyPort, envKeyDebug, envKeyMaintenanceMode, - envKeyBindAddress, envKeySentryDSN, + envKeyPort, envKeyDebug, envKeyBindAddress, envKeySentryDSN, } { require.NoError(t, os.Unsetenv(key)) } @@ -668,7 +660,6 @@ func TestNewUsesDefaultsWhenUnset(t *testing.T) { assert.Equal(t, 8080, cfg.Port) assert.False(t, cfg.Debug) - assert.False(t, cfg.MaintenanceMode) // Loopback, not the wildcard: the default must not publish the // cleartext admin UI and the unauthenticated receiver on every diff --git a/internal/handlers/settings.go b/internal/handlers/settings.go index 0ba9f78..a21f878 100644 --- a/internal/handlers/settings.go +++ b/internal/handlers/settings.go @@ -53,13 +53,6 @@ func settingRows(cfg *config.Config) []settingRow { }, {"DATA_DIR", "Directory for all SQLite databases", cfg.DataDir}, {"DEBUG", "Enable debug logging", strconv.FormatBool(cfg.Debug)}, - { - "MAINTENANCE_MODE", - "Report maintenanceMode: true in the healthcheck JSON. " + - "It does not change how any request is served — no " + - "maintenance page exists", - strconv.FormatBool(cfg.MaintenanceMode), - }, { "METRICS_USERNAME", "Basic auth username for /metrics. Must be set together " + diff --git a/internal/handlers/settings_test.go b/internal/handlers/settings_test.go index 1155bcf..f8b06a8 100644 --- a/internal/handlers/settings_test.go +++ b/internal/handlers/settings_test.go @@ -62,14 +62,12 @@ func settingsShown( func TestSettingsPageShowsLoadedConfiguration(t *testing.T) { t.Parallel() - // DEBUG and MAINTENANCE_MODE get opposite values, and each of - // METRICS_USERNAME, METRICS_PASSWORD and SENTRY_DSN is the only one - // of the three set in one of the content tests, so each row is - // checked against its own field. + // Each of METRICS_USERNAME, METRICS_PASSWORD and SENTRY_DSN is the + // only one of the three set in one of the content tests, so each + // row is checked against its own field. cfg := &config.Config{ DataDir: t.TempDir(), Debug: true, - MaintenanceMode: false, Environment: config.EnvironmentDev, MetricsUsername: "scraper", MetricsPassword: "", @@ -96,7 +94,6 @@ func TestSettingsPageShowsLoadedConfiguration(t *testing.T) { "BIND_ADDRESS": "192.0.2.10", "DATA_DIR": cfg.DataDir, "DEBUG": "true", - "MAINTENANCE_MODE": "false", "METRICS_USERNAME": "scraper", "METRICS_PASSWORD": "not set", "SENTRY_DSN": "not set", diff --git a/internal/healthcheck/healthcheck.go b/internal/healthcheck/healthcheck.go index 03c7281..cd3fcef 100644 --- a/internal/healthcheck/healthcheck.go +++ b/internal/healthcheck/healthcheck.go @@ -7,7 +7,6 @@ import ( "time" "go.uber.org/fx" - "sneak.berlin/go/webhooker/internal/config" "sneak.berlin/go/webhooker/internal/database" "sneak.berlin/go/webhooker/internal/globals" "sneak.berlin/go/webhooker/internal/logger" @@ -18,7 +17,6 @@ type HealthcheckParams struct { fx.In Globals *globals.Globals - Config *config.Config Logger *logger.Logger Database *database.Database } @@ -64,7 +62,6 @@ func (s *Healthcheck) Healthcheck() *Response { UptimeHuman: s.uptime().String(), Appname: s.params.Globals.Appname, Version: s.params.Globals.Version, - Maintenance: s.params.Config.MaintenanceMode, } return resp @@ -78,7 +75,6 @@ type Response struct { UptimeHuman string `json:"uptimeHuman"` Version string `json:"version"` Appname string `json:"appname"` - Maintenance bool `json:"maintenanceMode"` } func (s *Healthcheck) uptime() time.Duration { diff --git a/internal/server/server.go b/internal/server/server.go index 86dee1c..12521c1 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -159,12 +159,6 @@ func (s *Server) Run() { s.serve() } -// MaintenanceMode returns whether the server is in maintenance -// mode. -func (s *Server) MaintenanceMode() bool { - return s.params.Config.MaintenanceMode -} - // enableSentry initialises the Sentry SDK when error reporting is // configured, and reports the failure when it is configured and cannot // be initialised. A DSN that is not set is not a failure: reporting