79 lines
2.7 KiB
Go
79 lines
2.7 KiB
Go
// Package config loads the server configuration from environment
|
|
// variables with sane defaults for local development.
|
|
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
// Config holds all runtime configuration for the API server.
|
|
type Config struct {
|
|
// HTTPAddr is the listen address, e.g. ":8080".
|
|
HTTPAddr string
|
|
// StaticDir points to the built frontend (React/Vite dist).
|
|
// Empty disables static file serving.
|
|
StaticDir string
|
|
// Environment is "development", "staging" or "production".
|
|
Environment string
|
|
// AllowedOrigins is a comma-separated list of CORS origins
|
|
// allowed to call the API from a browser.
|
|
AllowedOrigins []string
|
|
// DatabaseURL is the PostgreSQL DSN.
|
|
DatabaseURL string
|
|
// JWTSecret signs access tokens. Must be set in production.
|
|
JWTSecret string
|
|
// MediaDir is the local disk location for uploaded attachments.
|
|
MediaDir string
|
|
// VAPIDPublicKey and VAPIDPrivateKey enable Web Push. When either is
|
|
// empty, push notifications are disabled.
|
|
VAPIDPublicKey string
|
|
VAPIDPrivateKey string
|
|
// VAPIDSubject is the contact address used in the VAPID JWT.
|
|
VAPIDSubject string
|
|
}
|
|
|
|
// Load reads configuration from the environment, falling back to
|
|
// development-friendly defaults. Unknown values are validated.
|
|
func Load() (*Config, error) {
|
|
cfg := &Config{
|
|
HTTPAddr: fromEnv("MESSENGER_HTTP_ADDR", ":8080"),
|
|
StaticDir: os.Getenv("MESSENGER_STATIC_DIR"),
|
|
Environment: fromEnv("MESSENGER_ENV", "development"),
|
|
DatabaseURL: fromEnv("MESSENGER_DATABASE_URL", "postgres://messenger:messenger@localhost:5432/messenger?sslmode=disable"),
|
|
JWTSecret: fromEnv("MESSENGER_JWT_SECRET", "dev-secret-change-me"),
|
|
MediaDir: fromEnv("MESSENGER_MEDIA_DIR", "./media"),
|
|
VAPIDPublicKey: os.Getenv("MESSENGER_VAPID_PUBLIC_KEY"),
|
|
VAPIDPrivateKey: os.Getenv("MESSENGER_VAPID_PRIVATE_KEY"),
|
|
VAPIDSubject: os.Getenv("MESSENGER_VAPID_SUBJECT"),
|
|
}
|
|
|
|
for _, origin := range strings.Split(fromEnv("MESSENGER_ALLOWED_ORIGINS", ""), ",") {
|
|
if origin = strings.TrimSpace(origin); origin != "" {
|
|
cfg.AllowedOrigins = append(cfg.AllowedOrigins, origin)
|
|
}
|
|
}
|
|
|
|
switch cfg.Environment {
|
|
case "development", "staging", "production":
|
|
default:
|
|
return nil, fmt.Errorf("MESSENGER_ENV must be one of development|staging|production, got %q", cfg.Environment)
|
|
}
|
|
|
|
if cfg.Environment == "production" && cfg.JWTSecret == "dev-secret-change-me" {
|
|
return nil, fmt.Errorf("MESSENGER_JWT_SECRET must be set in production")
|
|
}
|
|
|
|
return cfg, nil
|
|
}
|
|
|
|
// fromEnv returns the value of the environment variable name, or
|
|
// fallback if the variable is unset or empty.
|
|
func fromEnv(name, fallback string) string {
|
|
if v := os.Getenv(name); v != "" {
|
|
return v
|
|
}
|
|
return fallback
|
|
}
|