124 lines
3.0 KiB
Go
124 lines
3.0 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/spf13/viper"
|
|
)
|
|
|
|
// Config holds all configuration for the Zwift monitor service
|
|
type Config struct {
|
|
// Authentication
|
|
Username string `mapstructure:"username"`
|
|
Password string `mapstructure:"password"`
|
|
|
|
// Storage
|
|
OutputDir string `mapstructure:"output_dir"`
|
|
StateFile string `mapstructure:"state_file"`
|
|
TokenCachePath string `mapstructure:"token_cache"`
|
|
|
|
// Monitoring
|
|
PollInterval time.Duration `mapstructure:"poll_interval"`
|
|
|
|
// API Configuration
|
|
RateLimit int `mapstructure:"rate_limit"`
|
|
MaxRetries int `mapstructure:"max_retries"`
|
|
|
|
// Logging
|
|
LogLevel string `mapstructure:"log_level"`
|
|
LogFile string `mapstructure:"log_file"`
|
|
}
|
|
|
|
// Load loads configuration from environment variables and optional config file
|
|
func Load() (*Config, error) {
|
|
v := viper.New()
|
|
|
|
// Set defaults
|
|
v.SetDefault("output_dir", "/data/activities")
|
|
v.SetDefault("state_file", "/data/zwift-state.json")
|
|
v.SetDefault("token_cache", "/data/.zwift-token.json")
|
|
v.SetDefault("poll_interval", "5m")
|
|
v.SetDefault("rate_limit", 5)
|
|
v.SetDefault("max_retries", 3)
|
|
v.SetDefault("log_level", "info")
|
|
v.SetDefault("log_file", "")
|
|
|
|
// Bind environment variables
|
|
v.SetEnvPrefix("ZWIFT")
|
|
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
|
v.AutomaticEnv()
|
|
|
|
// Try to load config file if it exists
|
|
configPaths := []string{
|
|
"config.yaml",
|
|
"/app/config.yaml",
|
|
filepath.Join(os.Getenv("HOME"), ".zwift-monitor.yaml"),
|
|
}
|
|
|
|
for _, path := range configPaths {
|
|
if _, err := os.Stat(path); err == nil {
|
|
v.SetConfigFile(path)
|
|
if err := v.ReadInConfig(); err != nil {
|
|
return nil, fmt.Errorf("failed to read config file %s: %w", path, err)
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
var cfg Config
|
|
if err := v.Unmarshal(&cfg); err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal config: %w", err)
|
|
}
|
|
|
|
// Validate required fields
|
|
if err := cfg.Validate(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Expand paths (handle ~)
|
|
cfg.OutputDir = expandPath(cfg.OutputDir)
|
|
cfg.StateFile = expandPath(cfg.StateFile)
|
|
cfg.TokenCachePath = expandPath(cfg.TokenCachePath)
|
|
if cfg.LogFile != "" {
|
|
cfg.LogFile = expandPath(cfg.LogFile)
|
|
}
|
|
|
|
return &cfg, nil
|
|
}
|
|
|
|
// Validate checks if required configuration is present
|
|
func (c *Config) Validate() error {
|
|
if c.Username == "" {
|
|
return fmt.Errorf("username is required (set ZWIFT_USERNAME environment variable)")
|
|
}
|
|
if c.Password == "" {
|
|
return fmt.Errorf("password is required (set ZWIFT_PASSWORD environment variable)")
|
|
}
|
|
if c.PollInterval < time.Minute {
|
|
return fmt.Errorf("poll_interval must be at least 1 minute")
|
|
}
|
|
if c.RateLimit < 1 {
|
|
return fmt.Errorf("rate_limit must be at least 1")
|
|
}
|
|
if c.MaxRetries < 1 {
|
|
return fmt.Errorf("max_retries must be at least 1")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// expandPath expands ~ to home directory
|
|
func expandPath(path string) string {
|
|
if strings.HasPrefix(path, "~") {
|
|
home, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return path
|
|
}
|
|
return filepath.Join(home, path[1:])
|
|
}
|
|
return path
|
|
}
|