Files
Gardomatic/cmd/api/config.go
T
2026-09-14 19:58:31 +02:00

111 lines
4.6 KiB
Go

package main
import (
"errors"
"fmt"
"net/url"
"strings"
"time"
"gardomatic.kleiax.de/internal/api"
"gardomatic.kleiax.de/internal/mailer"
"gardomatic.kleiax.de/internal/platform/environment"
"gardomatic.kleiax.de/internal/platform/validate"
"gardomatic.kleiax.de/internal/storage"
)
func configFromEnvironment() (api.Config, error) {
var errs []error
required := func(name string) string {
value, err := environment.Required(name)
if err != nil {
errs = append(errs, err)
}
return value
}
integer := func(name string, fallback int) int {
value, err := environment.Int(name, fallback)
if err != nil {
errs = append(errs, err)
}
return value
}
boolean := func(name string, fallback bool) bool {
value, err := environment.Bool(name, fallback)
if err != nil {
errs = append(errs, err)
}
return value
}
duration := func(name string, fallback time.Duration) time.Duration {
value, err := environment.Duration(name, fallback)
if err != nil {
errs = append(errs, err)
}
return value
}
floating := func(name string, fallback float64) float64 {
value, err := environment.Float(name, fallback)
if err != nil {
errs = append(errs, err)
}
return value
}
cfg := api.Config{
Host: environment.String("GARDOMATIC_API_HOST", ""),
Port: integer("GARDOMATIC_API_PORT", 4000),
Env: environment.String("GARDOMATIC_ENV", "development"),
WebBaseURL: environment.String("GARDOMATIC_WEB_BASE_URL", "http://localhost:4040"),
DemoAccountEmail: strings.ToLower(strings.TrimSpace(
environment.String("GARDOMATIC_DEMO_ACCOUNT_EMAIL", ""),
)),
DB: api.DatabaseConfig{
Dsn: required("GARDOMATIC_DB_DSN"), MaxOpenConns: integer("GARDOMATIC_DB_MAX_OPEN_CONNS", 25),
MaxIdleConns: integer("GARDOMATIC_DB_MAX_IDLE_CONNS", 25), MaxIdleTime: duration("GARDOMATIC_DB_MAX_IDLE_TIME", 15*time.Minute),
},
Limiter: api.LimiterConfig{Enabled: boolean("GARDOMATIC_RATE_LIMIT_ENABLED", true), Rps: floating("GARDOMATIC_RATE_LIMIT_RPS", 10), Burst: integer("GARDOMATIC_RATE_LIMIT_BURST", 40)},
Session: api.SessionConfig{Lifetime: duration("GARDOMATIC_SESSION_LIFETIME", 12*time.Hour), IdleTimeout: duration("GARDOMATIC_SESSION_IDLE_TIMEOUT", 30*time.Minute), CookieName: environment.String("GARDOMATIC_SESSION_COOKIE_NAME", "gardomatic_session"), CookieSecure: boolean("GARDOMATIC_COOKIE_SECURE", false)},
Mail: mailer.Config{Mode: mailer.Mode(environment.String("GARDOMATIC_SMTP_MODE", string(mailer.ModeFile))), Host: environment.String("GARDOMATIC_SMTP_HOST", ""), Port: integer("GARDOMATIC_SMTP_PORT", 25), Username: environment.String("GARDOMATIC_SMTP_USERNAME", ""), Password: environment.String("GARDOMATIC_SMTP_PASSWORD", ""), Sender: environment.String("GARDOMATIC_SMTP_SENDER", "gardomatic@localhost"), FilePath: environment.String("GARDOMATIC_SMTP_FILE_PATH", "/tmp/gardomatic-mails.log")},
Cors: api.CORSConfig{TrustedOrigins: environment.CSV("GARDOMATIC_CORS_TRUSTED_ORIGINS")},
}
if cfg.Port < 1 || cfg.Port > 65535 {
errs = append(errs, errors.New("GARDOMATIC_API_PORT must be between 1 and 65535"))
}
if cfg.Env != "development" && cfg.Env != "test" && cfg.Env != "production" {
errs = append(errs, fmt.Errorf("GARDOMATIC_ENV has unsupported value %q", cfg.Env))
}
if cfg.DB.MaxOpenConns < 1 || cfg.DB.MaxIdleConns < 0 || cfg.DB.MaxIdleConns > cfg.DB.MaxOpenConns {
errs = append(errs, errors.New("database pool limits are invalid"))
}
if cfg.Limiter.Rps <= 0 || cfg.Limiter.Burst < 1 {
errs = append(errs, errors.New("rate limit values must be positive"))
}
if strings.TrimSpace(cfg.Session.CookieName) == "" {
errs = append(errs, errors.New("GARDOMATIC_SESSION_COOKIE_NAME must not be empty"))
}
for _, origin := range cfg.Cors.TrustedOrigins {
parsed, err := url.ParseRequestURI(origin)
if err != nil || parsed.Scheme == "" || parsed.Host == "" {
errs = append(errs, fmt.Errorf("invalid trusted origin %q", origin))
}
}
if parsed, err := url.ParseRequestURI(cfg.WebBaseURL); err != nil || parsed.Scheme == "" || parsed.Host == "" {
errs = append(errs, errors.New("GARDOMATIC_WEB_BASE_URL must be an absolute URL"))
}
if cfg.Env == "production" && !cfg.Session.CookieSecure {
errs = append(errs, errors.New("GARDOMATIC_COOKIE_SECURE must be true in production"))
}
if cfg.DemoAccountEmail != "" {
v := validate.New()
if storage.ValidateEmail(v, cfg.DemoAccountEmail); !v.Valid() {
errs = append(errs, errors.New("GARDOMATIC_DEMO_ACCOUNT_EMAIL must be a valid email address"))
}
}
if _, err := mailer.New(cfg.Mail); err != nil {
errs = append(errs, fmt.Errorf("mail configuration: %w", err))
}
return cfg, errors.Join(errs...)
}