Remove the no-op MAINTENANCE_MODE setting (closes #317) #430
@@ -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`) |
|
| `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` |
|
| `DATA_DIR` | Directory for all SQLite databases | `/var/lib/webhooker` |
|
||||||
| `DEBUG` | Enable debug logging | `false` |
|
| `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_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 | `""` |
|
| `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 | `""` |
|
| `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:
|
with reporting off. Leaving it unset is not a mistake and not affected:
|
||||||
error reporting is simply off and startup is normal.
|
error reporting is simply off and startup is normal.
|
||||||
|
|
||||||
Boolean variables (`DEBUG`, `MAINTENANCE_MODE`) accept exactly the
|
The boolean variable `DEBUG` accepts exactly the spellings Go's
|
||||||
spellings Go's `strconv.ParseBool` accepts — `1`, `t`, `T`, `TRUE`,
|
`strconv.ParseBool` accepts — `1`, `t`, `T`, `TRUE`,
|
||||||
`true`, `True`, `0`, `f`, `F`, `FALSE`, `false`, `False` — and nothing
|
`true`, `True`, `0`, `f`, `F`, `FALSE`, `false`, `False` — and nothing
|
||||||
else. `yes`, `on`, and `off` are rejected rather than quietly treated
|
else. `yes`, `on`, and `off` are rejected rather than quietly treated
|
||||||
as false.
|
as false.
|
||||||
@@ -2775,7 +2774,7 @@ abuse limit later; they are tracked as future work.
|
|||||||
| Method | Path | Description |
|
| Method | Path | Description |
|
||||||
| ------ | --------------------------- | ----------- |
|
| ------ | --------------------------- | ----------- |
|
||||||
| `GET` | `/` | Root redirect, 303 (authenticated → `/hooks`, unauthenticated → `/pages/login`) |
|
| `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` |
|
| `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)) |
|
| `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)) |
|
||||||
|
|
||||||
|
|||||||
@@ -149,7 +149,6 @@ type ConfigParams struct {
|
|||||||
type Config struct {
|
type Config struct {
|
||||||
DataDir string
|
DataDir string
|
||||||
Debug bool
|
Debug bool
|
||||||
MaintenanceMode bool
|
|
||||||
Environment string
|
Environment string
|
||||||
MetricsPassword string
|
MetricsPassword string
|
||||||
MetricsUsername string
|
MetricsUsername string
|
||||||
@@ -658,11 +657,6 @@ func loadFromEnv() (*Config, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
maintenanceMode, err := envBool("MAINTENANCE_MODE", false)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
retentionSweepInterval, err := envPositiveDuration(
|
retentionSweepInterval, err := envPositiveDuration(
|
||||||
"RETENTION_SWEEP_INTERVAL",
|
"RETENTION_SWEEP_INTERVAL",
|
||||||
defaultRetentionSweepInterval,
|
defaultRetentionSweepInterval,
|
||||||
@@ -712,7 +706,6 @@ func loadFromEnv() (*Config, error) {
|
|||||||
return &Config{
|
return &Config{
|
||||||
DataDir: DataDir(),
|
DataDir: DataDir(),
|
||||||
Debug: debug,
|
Debug: debug,
|
||||||
MaintenanceMode: maintenanceMode,
|
|
||||||
Environment: environment,
|
Environment: environment,
|
||||||
MetricsUsername: metricsUsername,
|
MetricsUsername: metricsUsername,
|
||||||
MetricsPassword: metricsPassword,
|
MetricsPassword: metricsPassword,
|
||||||
@@ -799,7 +792,6 @@ func New(lc fx.Lifecycle, params ConfigParams) (*Config, error) {
|
|||||||
// host can reach the admin UI.
|
// host can reach the admin UI.
|
||||||
"bindAddress", s.BindAddress,
|
"bindAddress", s.BindAddress,
|
||||||
"debug", s.Debug,
|
"debug", s.Debug,
|
||||||
"maintenanceMode", s.MaintenanceMode,
|
|
||||||
"dataDir", s.DataDir,
|
"dataDir", s.DataDir,
|
||||||
"retentionSweepInterval", s.RetentionSweepInterval.String(),
|
"retentionSweepInterval", s.RetentionSweepInterval.String(),
|
||||||
// Logged because a perfectly valid non-positive value here
|
// Logged because a perfectly valid non-positive value here
|
||||||
|
|||||||
@@ -18,10 +18,9 @@ const testEnvKey = "WEBHOOKER_TEST_VALUE"
|
|||||||
|
|
||||||
// Real configuration variables exercised by the config.New tests.
|
// Real configuration variables exercised by the config.New tests.
|
||||||
const (
|
const (
|
||||||
envKeyPort = "PORT"
|
envKeyPort = "PORT"
|
||||||
envKeyDebug = "DEBUG"
|
envKeyDebug = "DEBUG"
|
||||||
envKeyMaintenanceMode = "MAINTENANCE_MODE"
|
envKeyBindAddress = "BIND_ADDRESS"
|
||||||
envKeyBindAddress = "BIND_ADDRESS"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Sample BIND_ADDRESS values used by the tables below.
|
// Sample BIND_ADDRESS values used by the tables below.
|
||||||
@@ -604,12 +603,6 @@ func flagEnvValueCases() []badEnvValueCase {
|
|||||||
value: "ture",
|
value: "ture",
|
||||||
expectError: true,
|
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")
|
t.Setenv("WEBHOOKER_ENVIRONMENT", "dev")
|
||||||
|
|
||||||
for _, key := range []string{
|
for _, key := range []string{
|
||||||
envKeyPort, envKeyDebug, envKeyMaintenanceMode,
|
envKeyPort, envKeyDebug, envKeyBindAddress, envKeySentryDSN,
|
||||||
envKeyBindAddress, envKeySentryDSN,
|
|
||||||
} {
|
} {
|
||||||
require.NoError(t, os.Unsetenv(key))
|
require.NoError(t, os.Unsetenv(key))
|
||||||
}
|
}
|
||||||
@@ -668,7 +660,6 @@ func TestNewUsesDefaultsWhenUnset(t *testing.T) {
|
|||||||
|
|
||||||
assert.Equal(t, 8080, cfg.Port)
|
assert.Equal(t, 8080, cfg.Port)
|
||||||
assert.False(t, cfg.Debug)
|
assert.False(t, cfg.Debug)
|
||||||
assert.False(t, cfg.MaintenanceMode)
|
|
||||||
|
|
||||||
// Loopback, not the wildcard: the default must not publish the
|
// Loopback, not the wildcard: the default must not publish the
|
||||||
// cleartext admin UI and the unauthenticated receiver on every
|
// cleartext admin UI and the unauthenticated receiver on every
|
||||||
|
|||||||
@@ -53,13 +53,6 @@ func settingRows(cfg *config.Config) []settingRow {
|
|||||||
},
|
},
|
||||||
{"DATA_DIR", "Directory for all SQLite databases", cfg.DataDir},
|
{"DATA_DIR", "Directory for all SQLite databases", cfg.DataDir},
|
||||||
{"DEBUG", "Enable debug logging", strconv.FormatBool(cfg.Debug)},
|
{"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",
|
"METRICS_USERNAME",
|
||||||
"Basic auth username for /metrics. Must be set together " +
|
"Basic auth username for /metrics. Must be set together " +
|
||||||
|
|||||||
@@ -62,14 +62,12 @@ func settingsShown(
|
|||||||
func TestSettingsPageShowsLoadedConfiguration(t *testing.T) {
|
func TestSettingsPageShowsLoadedConfiguration(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
// DEBUG and MAINTENANCE_MODE get opposite values, and each of
|
// Each of METRICS_USERNAME, METRICS_PASSWORD and SENTRY_DSN is the
|
||||||
// METRICS_USERNAME, METRICS_PASSWORD and SENTRY_DSN is the only one
|
// only one of the three set in one of the content tests, so each
|
||||||
// of the three set in one of the content tests, so each row is
|
// row is checked against its own field.
|
||||||
// checked against its own field.
|
|
||||||
cfg := &config.Config{
|
cfg := &config.Config{
|
||||||
DataDir: t.TempDir(),
|
DataDir: t.TempDir(),
|
||||||
Debug: true,
|
Debug: true,
|
||||||
MaintenanceMode: false,
|
|
||||||
Environment: config.EnvironmentDev,
|
Environment: config.EnvironmentDev,
|
||||||
MetricsUsername: "scraper",
|
MetricsUsername: "scraper",
|
||||||
MetricsPassword: "",
|
MetricsPassword: "",
|
||||||
@@ -96,7 +94,6 @@ func TestSettingsPageShowsLoadedConfiguration(t *testing.T) {
|
|||||||
"BIND_ADDRESS": "192.0.2.10",
|
"BIND_ADDRESS": "192.0.2.10",
|
||||||
"DATA_DIR": cfg.DataDir,
|
"DATA_DIR": cfg.DataDir,
|
||||||
"DEBUG": "true",
|
"DEBUG": "true",
|
||||||
"MAINTENANCE_MODE": "false",
|
|
||||||
"METRICS_USERNAME": "scraper",
|
"METRICS_USERNAME": "scraper",
|
||||||
"METRICS_PASSWORD": "not set",
|
"METRICS_PASSWORD": "not set",
|
||||||
"SENTRY_DSN": "not set",
|
"SENTRY_DSN": "not set",
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.uber.org/fx"
|
"go.uber.org/fx"
|
||||||
"sneak.berlin/go/webhooker/internal/config"
|
|
||||||
"sneak.berlin/go/webhooker/internal/database"
|
"sneak.berlin/go/webhooker/internal/database"
|
||||||
"sneak.berlin/go/webhooker/internal/globals"
|
"sneak.berlin/go/webhooker/internal/globals"
|
||||||
"sneak.berlin/go/webhooker/internal/logger"
|
"sneak.berlin/go/webhooker/internal/logger"
|
||||||
@@ -18,7 +17,6 @@ type HealthcheckParams struct {
|
|||||||
fx.In
|
fx.In
|
||||||
|
|
||||||
Globals *globals.Globals
|
Globals *globals.Globals
|
||||||
Config *config.Config
|
|
||||||
Logger *logger.Logger
|
Logger *logger.Logger
|
||||||
Database *database.Database
|
Database *database.Database
|
||||||
}
|
}
|
||||||
@@ -64,7 +62,6 @@ func (s *Healthcheck) Healthcheck() *Response {
|
|||||||
UptimeHuman: s.uptime().String(),
|
UptimeHuman: s.uptime().String(),
|
||||||
Appname: s.params.Globals.Appname,
|
Appname: s.params.Globals.Appname,
|
||||||
Version: s.params.Globals.Version,
|
Version: s.params.Globals.Version,
|
||||||
Maintenance: s.params.Config.MaintenanceMode,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp
|
return resp
|
||||||
@@ -78,7 +75,6 @@ type Response struct {
|
|||||||
UptimeHuman string `json:"uptimeHuman"`
|
UptimeHuman string `json:"uptimeHuman"`
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
Appname string `json:"appname"`
|
Appname string `json:"appname"`
|
||||||
Maintenance bool `json:"maintenanceMode"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Healthcheck) uptime() time.Duration {
|
func (s *Healthcheck) uptime() time.Duration {
|
||||||
|
|||||||
@@ -159,12 +159,6 @@ func (s *Server) Run() {
|
|||||||
s.serve()
|
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
|
// enableSentry initialises the Sentry SDK when error reporting is
|
||||||
// configured, and reports the failure when it is configured and cannot
|
// configured, and reports the failure when it is configured and cannot
|
||||||
// be initialised. A DSN that is not set is not a failure: reporting
|
// be initialised. A DSN that is not set is not a failure: reporting
|
||||||
|
|||||||
Reference in New Issue
Block a user