refactor: use pinned golangci-lint Docker image for linting
All checks were successful
check / check (push) Successful in 1m37s

Refactor Dockerfile to use a separate lint stage with a pinned
golangci-lint v2.11.3 Docker image instead of installing
golangci-lint via curl in the builder stage. This follows the
pattern used by sneak/pixa.

Changes:
- Dockerfile: separate lint stage using golangci/golangci-lint:v2.11.3
  (Debian-based, pinned by sha256) with COPY --from=lint dependency
- Bump Go from 1.24 to 1.26.1 (golang:1.26.1-bookworm, pinned)
- Bump golangci-lint from v1.64.8 to v2.11.3
- Migrate .golangci.yml from v1 to v2 format (same linters, format only)
- All Docker images pinned by sha256 digest
- Fix all lint issues from the v2 linter upgrade:
  - Add package comments to all packages
  - Add doc comments to all exported types, functions, and methods
  - Fix unchecked errors (errcheck)
  - Fix unused parameters (revive)
  - Fix gosec warnings (MaxBytesReader for form parsing)
  - Fix staticcheck suggestions (fmt.Fprintf instead of WriteString)
  - Rename DeliveryTask to Task to avoid stutter (delivery.Task)
  - Rename shadowed builtin 'max' parameter
- Update README.md version requirements
This commit is contained in:
clawbot
2026-03-17 05:46:03 -07:00
parent d771fe14df
commit 32a9170428
59 changed files with 7792 additions and 4282 deletions

View File

@@ -1,6 +1,8 @@
// Package config loads application configuration from environment variables.
package config
import (
"errors"
"fmt"
"log/slog"
"os"
@@ -17,19 +19,29 @@ import (
)
const (
// EnvironmentDev represents development environment
// EnvironmentDev represents development environment.
EnvironmentDev = "dev"
// EnvironmentProd represents production environment
// EnvironmentProd represents production environment.
EnvironmentProd = "prod"
// defaultPort is the default HTTP listen port.
defaultPort = 8080
)
// nolint:revive // ConfigParams is a standard fx naming convention
// ErrInvalidEnvironment is returned when WEBHOOKER_ENVIRONMENT
// contains an unrecognised value.
var ErrInvalidEnvironment = errors.New("invalid environment")
//nolint:revive // ConfigParams is a standard fx naming convention.
type ConfigParams struct {
fx.In
Globals *globals.Globals
Logger *logger.Logger
}
// Config holds all application configuration loaded from
// environment variables.
type Config struct {
DataDir string
Debug bool
@@ -43,56 +55,67 @@ type Config struct {
log *slog.Logger
}
// IsDev returns true if running in development environment
// IsDev returns true if running in development environment.
func (c *Config) IsDev() bool {
return c.Environment == EnvironmentDev
}
// IsProd returns true if running in production environment
// IsProd returns true if running in production environment.
func (c *Config) IsProd() bool {
return c.Environment == EnvironmentProd
}
// envString returns the value of the named environment variable, or
// an empty string if not set.
// envString returns the value of the named environment variable,
// or an empty string if not set.
func envString(key string) string {
return os.Getenv(key)
}
// envBool returns the value of the named environment variable parsed as a
// boolean. Returns defaultValue if not set.
// envBool returns the value of the named environment variable
// parsed as a boolean. Returns defaultValue if not set.
func envBool(key string, defaultValue bool) bool {
if v := os.Getenv(key); v != "" {
return strings.EqualFold(v, "true") || v == "1"
}
return defaultValue
}
// envInt returns the value of the named environment variable parsed as an
// integer. Returns defaultValue if not set or unparseable.
// envInt returns the value of the named environment variable
// parsed as an integer. Returns defaultValue if not set or
// unparseable.
func envInt(key string, defaultValue int) int {
if v := os.Getenv(key); v != "" {
if i, err := strconv.Atoi(v); err == nil {
i, err := strconv.Atoi(v)
if err == nil {
return i
}
}
return defaultValue
}
// nolint:revive // lc parameter is required by fx even if unused
// New creates a Config by reading environment variables.
//
//nolint:revive // lc parameter is required by fx even if unused.
func New(lc fx.Lifecycle, params ConfigParams) (*Config, error) {
log := params.Logger.Get()
// Determine environment from WEBHOOKER_ENVIRONMENT env var, default to dev
// Determine environment from WEBHOOKER_ENVIRONMENT env var,
// default to dev
environment := os.Getenv("WEBHOOKER_ENVIRONMENT")
if environment == "" {
environment = EnvironmentDev
}
// Validate environment
if environment != EnvironmentDev && environment != EnvironmentProd {
return nil, fmt.Errorf("WEBHOOKER_ENVIRONMENT must be either '%s' or '%s', got '%s'",
EnvironmentDev, EnvironmentProd, environment)
if environment != EnvironmentDev &&
environment != EnvironmentProd {
return nil, fmt.Errorf(
"%w: WEBHOOKER_ENVIRONMENT must be '%s' or '%s', got '%s'",
ErrInvalidEnvironment,
EnvironmentDev, EnvironmentProd, environment,
)
}
// Load configuration values from environment variables
@@ -103,15 +126,16 @@ func New(lc fx.Lifecycle, params ConfigParams) (*Config, error) {
Environment: environment,
MetricsUsername: envString("METRICS_USERNAME"),
MetricsPassword: envString("METRICS_PASSWORD"),
Port: envInt("PORT", 8080),
Port: envInt("PORT", defaultPort),
SentryDSN: envString("SENTRY_DSN"),
log: log,
params: &params,
}
// Set default DataDir. All SQLite databases (main application DB
// and per-webhook event DBs) live here. The same default is used
// regardless of environment; override with DATA_DIR if needed.
// Set default DataDir. All SQLite databases (main application
// DB and per-webhook event DBs) live here. The same default is
// used regardless of environment; override with DATA_DIR if
// needed.
if s.DataDir == "" {
s.DataDir = "/var/lib/webhooker"
}
@@ -128,7 +152,8 @@ func New(lc fx.Lifecycle, params ConfigParams) (*Config, error) {
"maintenanceMode", s.MaintenanceMode,
"dataDir", s.DataDir,
"hasSentryDSN", s.SentryDSN != "",
"hasMetricsAuth", s.MetricsUsername != "" && s.MetricsPassword != "",
"hasMetricsAuth",
s.MetricsUsername != "" && s.MetricsPassword != "",
)
return s, nil