Files
garage-ui/backend/internal/config/config.go
T

253 lines
9.7 KiB
Go

package config
import (
"fmt"
"os"
"strings"
"github.com/spf13/viper"
)
// Config represents the application configuration
type Config struct {
Server ServerConfig `mapstructure:"server"`
Garage GarageConfig `mapstructure:"garage"`
Auth AuthConfig `mapstructure:"auth"`
CORS CORSConfig `mapstructure:"cors"`
Logging LoggingConfig `mapstructure:"logging"`
}
// ServerConfig contains server-related configuration
type ServerConfig struct {
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
Environment string `mapstructure:"environment"`
FrontendPath string `mapstructure:"frontend_path"` // Path to frontend dist directory
}
// GarageConfig contains Garage S3 connection settings
type GarageConfig struct {
Endpoint string `mapstructure:"endpoint"`
Region string `mapstructure:"region"`
UseSSL bool `mapstructure:"use_ssl"`
ForcePathStyle bool `mapstructure:"force_path_style"`
AdminEndpoint string `mapstructure:"admin_endpoint"`
AdminToken string `mapstructure:"admin_token"`
}
// AuthConfig contains authentication configuration
type AuthConfig struct {
Mode string `mapstructure:"mode"` // "none", "basic", or "oidc"
Basic BasicAuthConfig `mapstructure:"basic"`
OIDC OIDCConfig `mapstructure:"oidc"`
}
// BasicAuthConfig contains basic authentication settings
type BasicAuthConfig struct {
Username string `mapstructure:"username"`
Password string `mapstructure:"password"`
}
// OIDCConfig contains OIDC authentication settings
type OIDCConfig struct {
Enabled bool `mapstructure:"enabled"`
ProviderName string `mapstructure:"provider_name"`
ClientID string `mapstructure:"client_id"`
ClientSecret string `mapstructure:"client_secret"`
Scopes []string `mapstructure:"scopes"`
IssuerURL string `mapstructure:"issuer_url"`
AuthURL string `mapstructure:"auth_url"`
TokenURL string `mapstructure:"token_url"`
UserinfoURL string `mapstructure:"userinfo_url"`
SkipIssuerCheck bool `mapstructure:"skip_issuer_check"`
SkipExpiryCheck bool `mapstructure:"skip_expiry_check"`
EmailAttribute string `mapstructure:"email_attribute"`
UsernameAttribute string `mapstructure:"username_attribute"`
NameAttribute string `mapstructure:"name_attribute"`
RoleAttributePath string `mapstructure:"role_attribute_path"`
AdminRole string `mapstructure:"admin_role"`
TLSSkipVerify bool `mapstructure:"tls_skip_verify"`
SessionMaxAge int `mapstructure:"session_max_age"`
CookieName string `mapstructure:"cookie_name"`
CookieSecure bool `mapstructure:"cookie_secure"`
CookieHTTPOnly bool `mapstructure:"cookie_http_only"`
CookieSameSite string `mapstructure:"cookie_same_site"`
}
// CORSConfig contains CORS settings for frontend communication
type CORSConfig struct {
Enabled bool `mapstructure:"enabled"`
AllowedOrigins []string `mapstructure:"allowed_origins"`
AllowedMethods []string `mapstructure:"allowed_methods"`
AllowedHeaders []string `mapstructure:"allowed_headers"`
AllowCredentials bool `mapstructure:"allow_credentials"`
MaxAge int `mapstructure:"max_age"`
}
// LoggingConfig contains logging configuration
type LoggingConfig struct {
Level string `mapstructure:"level"`
Format string `mapstructure:"format"`
}
// Load reads the configuration from the specified file
func Load(configPath string) (*Config, error) {
// Set default config file name if not specified
if configPath == "" {
configPath = "config.yaml"
}
// Configure viper to read the config file
viper.SetConfigFile(configPath)
viper.SetConfigType("yaml")
// Allow environment variables to override config values
// Environment variables take precedence over config file
viper.AutomaticEnv()
viper.SetEnvPrefix("GARAGE_UI")
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
// Bind environment variables to config keys
// This ensures env vars override config file values
bindEnvVars()
// Read the config file (optional - will use defaults and env vars if not found)
if _, err := os.Stat(configPath); err == nil {
if err := viper.ReadInConfig(); err != nil {
return nil, fmt.Errorf("error reading config file: %w", err)
}
}
// Unmarshal the config into the Config struct
var cfg Config
if err := viper.Unmarshal(&cfg); err != nil {
return nil, fmt.Errorf("error unmarshaling config: %w", err)
}
// Validate the configuration
if err := cfg.Validate(); err != nil {
return nil, fmt.Errorf("invalid configuration: %w", err)
}
return &cfg, nil
}
// bindEnvVars binds all environment variables to their corresponding config keys
func bindEnvVars() {
// Server config
viper.BindEnv("server.host", "GARAGE_UI_SERVER_HOST")
viper.BindEnv("server.port", "GARAGE_UI_SERVER_PORT")
viper.BindEnv("server.environment", "GARAGE_UI_SERVER_ENVIRONMENT")
viper.BindEnv("server.frontend_path", "GARAGE_UI_SERVER_FRONTEND_PATH")
// Garage config
viper.BindEnv("garage.endpoint", "GARAGE_UI_GARAGE_ENDPOINT")
viper.BindEnv("garage.region", "GARAGE_UI_GARAGE_REGION")
viper.BindEnv("garage.use_ssl", "GARAGE_UI_GARAGE_USE_SSL")
viper.BindEnv("garage.force_path_style", "GARAGE_UI_GARAGE_FORCE_PATH_STYLE")
viper.BindEnv("garage.admin_endpoint", "GARAGE_UI_GARAGE_ADMIN_ENDPOINT")
viper.BindEnv("garage.admin_token", "GARAGE_UI_GARAGE_ADMIN_TOKEN")
// Auth config
viper.BindEnv("auth.mode", "GARAGE_UI_AUTH_MODE")
viper.BindEnv("auth.basic.username", "GARAGE_UI_AUTH_BASIC_USERNAME")
viper.BindEnv("auth.basic.password", "GARAGE_UI_AUTH_BASIC_PASSWORD")
// OIDC config
viper.BindEnv("auth.oidc.enabled", "GARAGE_UI_AUTH_OIDC_ENABLED")
viper.BindEnv("auth.oidc.provider_name", "GARAGE_UI_AUTH_OIDC_PROVIDER_NAME")
viper.BindEnv("auth.oidc.client_id", "GARAGE_UI_AUTH_OIDC_CLIENT_ID")
viper.BindEnv("auth.oidc.client_secret", "GARAGE_UI_AUTH_OIDC_CLIENT_SECRET")
viper.BindEnv("auth.oidc.scopes", "GARAGE_UI_AUTH_OIDC_SCOPES")
viper.BindEnv("auth.oidc.issuer_url", "GARAGE_UI_AUTH_OIDC_ISSUER_URL")
viper.BindEnv("auth.oidc.auth_url", "GARAGE_UI_AUTH_OIDC_AUTH_URL")
viper.BindEnv("auth.oidc.token_url", "GARAGE_UI_AUTH_OIDC_TOKEN_URL")
viper.BindEnv("auth.oidc.userinfo_url", "GARAGE_UI_AUTH_OIDC_USERINFO_URL")
viper.BindEnv("auth.oidc.skip_issuer_check", "GARAGE_UI_AUTH_OIDC_SKIP_ISSUER_CHECK")
viper.BindEnv("auth.oidc.skip_expiry_check", "GARAGE_UI_AUTH_OIDC_SKIP_EXPIRY_CHECK")
viper.BindEnv("auth.oidc.email_attribute", "GARAGE_UI_AUTH_OIDC_EMAIL_ATTRIBUTE")
viper.BindEnv("auth.oidc.username_attribute", "GARAGE_UI_AUTH_OIDC_USERNAME_ATTRIBUTE")
viper.BindEnv("auth.oidc.name_attribute", "GARAGE_UI_AUTH_OIDC_NAME_ATTRIBUTE")
viper.BindEnv("auth.oidc.role_attribute_path", "GARAGE_UI_AUTH_OIDC_ROLE_ATTRIBUTE_PATH")
viper.BindEnv("auth.oidc.admin_role", "GARAGE_UI_AUTH_OIDC_ADMIN_ROLE")
viper.BindEnv("auth.oidc.tls_skip_verify", "GARAGE_UI_AUTH_OIDC_TLS_SKIP_VERIFY")
viper.BindEnv("auth.oidc.session_max_age", "GARAGE_UI_AUTH_OIDC_SESSION_MAX_AGE")
viper.BindEnv("auth.oidc.cookie_name", "GARAGE_UI_AUTH_OIDC_COOKIE_NAME")
viper.BindEnv("auth.oidc.cookie_secure", "GARAGE_UI_AUTH_OIDC_COOKIE_SECURE")
viper.BindEnv("auth.oidc.cookie_http_only", "GARAGE_UI_AUTH_OIDC_COOKIE_HTTP_ONLY")
viper.BindEnv("auth.oidc.cookie_same_site", "GARAGE_UI_AUTH_OIDC_COOKIE_SAME_SITE")
// CORS config
viper.BindEnv("cors.enabled", "GARAGE_UI_CORS_ENABLED")
viper.BindEnv("cors.allowed_origins", "GARAGE_UI_CORS_ALLOWED_ORIGINS")
viper.BindEnv("cors.allowed_methods", "GARAGE_UI_CORS_ALLOWED_METHODS")
viper.BindEnv("cors.allowed_headers", "GARAGE_UI_CORS_ALLOWED_HEADERS")
viper.BindEnv("cors.allow_credentials", "GARAGE_UI_CORS_ALLOW_CREDENTIALS")
viper.BindEnv("cors.max_age", "GARAGE_UI_CORS_MAX_AGE")
// Logging config
viper.BindEnv("logging.level", "GARAGE_UI_LOGGING_LEVEL")
viper.BindEnv("logging.format", "GARAGE_UI_LOGGING_FORMAT")
}
// Validate checks if the configuration is valid
func (c *Config) Validate() error {
// Validate server config
if c.Server.Port <= 0 || c.Server.Port > 65535 {
return fmt.Errorf("invalid server port: %d", c.Server.Port)
}
// Validate Garage config
if c.Garage.Endpoint == "" {
return fmt.Errorf("garage endpoint is required")
}
if c.Garage.AdminEndpoint == "" {
return fmt.Errorf("garage admin_endpoint is required")
}
if c.Garage.AdminToken == "" {
return fmt.Errorf("garage admin_token is required")
}
// Validate auth mode
if c.Auth.Mode != "none" && c.Auth.Mode != "basic" && c.Auth.Mode != "oidc" {
return fmt.Errorf("auth mode must be 'none', 'basic', or 'oidc', got: %s", c.Auth.Mode)
}
// Validate basic auth if enabled
if c.Auth.Mode == "basic" {
if c.Auth.Basic.Username == "" || c.Auth.Basic.Password == "" {
return fmt.Errorf("basic auth username and password are required when auth mode is 'basic'")
}
}
// Validate OIDC config if enabled
if c.Auth.Mode == "oidc" {
if c.Auth.OIDC.ClientID == "" {
return fmt.Errorf("oidc client_id is required when auth mode is 'oidc'")
}
if c.Auth.OIDC.IssuerURL == "" {
return fmt.Errorf("oidc issuer_url is required when auth mode is 'oidc'")
}
if len(c.Auth.OIDC.Scopes) == 0 {
return fmt.Errorf("oidc scopes are required when auth mode is 'oidc'")
}
}
return nil
}
// GetAddress returns the full server address (host:port)
func (c *Config) GetAddress() string {
return fmt.Sprintf("%s:%d", c.Server.Host, c.Server.Port)
}
// IsDevelopment returns true if running in development mode
func (c *Config) IsDevelopment() bool {
return c.Server.Environment == "development"
}
// IsProduction returns true if running in production mode
func (c *Config) IsProduction() bool {
return c.Server.Environment == "production"
}