mirror of
https://github.com/Noooste/garage-ui.git
synced 2026-07-26 07:48:13 +00:00
253 lines
9.7 KiB
Go
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"
|
|
}
|