package config import ( "crypto/sha256" "fmt" "log" "os" "path/filepath" "runtime" "gopkg.in/yaml.v3" ) // Config holds all application configuration. type Config struct { Database DatabaseConfig `yaml:"database"` Port string `yaml:"port"` Path string `yaml:"path"` Secret string `yaml:"secret"` } // DatabaseConfig holds database-specific configuration. type DatabaseConfig struct { Type string `yaml:"type"` // "sqlite" (default) or "mysql" DSN string `yaml:"dsn"` // MySQL connection string (ignored for sqlite) } // defaultPort is the fallback web server port. const defaultPort = "8080" // getConfigPath returns the OS-aware config directory and config file path. func getConfigPath() (dir, file string) { if runtime.GOOS == "windows" { dir = filepath.Join(".", "win", "etc", "blog_go") } else { dir = filepath.Join("/", "etc", "blog_go") } file = filepath.Join(dir, "config.yaml") return } // getDefaultStoragePath returns the OS-aware default storage path. func getDefaultStoragePath() string { if runtime.GOOS == "windows" { return filepath.Join(".", "win", "srv", "blog_go") } return filepath.Join("/", "srv", "blog_go") } // generateSecret returns a random-ish hex string for the session secret. func generateSecret() string { // Use a combination of hostname + random to produce a unique secret. hostname, _ := os.Hostname() input := fmt.Sprintf("%s-%d", hostname, os.Getpid()) hash := sha256.Sum256([]byte(input)) return fmt.Sprintf("%x", hash) } // LoadConfig reads the config file or creates one with defaults. func LoadConfig() *Config { configDir, configFile := getConfigPath() defaultPath := getDefaultStoragePath() cfg := &Config{} // Check if config file exists; create with defaults if not. if _, err := os.Stat(configFile); os.IsNotExist(err) { log.Printf("Config file not found at %s, creating with defaults...", configFile) if err := os.MkdirAll(configDir, 0755); err != nil { log.Fatalf("Failed to create config directory %s: %v", configDir, err) } cfg = &Config{ Database: DatabaseConfig{ Type: "sqlite", DSN: "", }, Port: defaultPort, Path: defaultPath, Secret: generateSecret(), } data, err := yaml.Marshal(cfg) if err != nil { log.Fatalf("Failed to marshal default config: %v", err) } if err := os.WriteFile(configFile, data, 0644); err != nil { log.Fatalf("Failed to write config file %s: %v", configFile, err) } log.Printf("Default config created at %s", configFile) return cfg } // Read existing config file. data, err := os.ReadFile(configFile) if err != nil { log.Printf("Warning: could not read config file %s: %v, using defaults", configFile, err) return applyDefaults(cfg, defaultPath) } if err := yaml.Unmarshal(data, cfg); err != nil { log.Printf("Warning: malformed config file %s: %v, using defaults", configFile, err) cfg = &Config{} } return applyDefaults(cfg, defaultPath) } // applyDefaults fills zero-value fields with sensible defaults. func applyDefaults(cfg *Config, defaultPath string) *Config { if cfg.Port == "" { cfg.Port = defaultPort } if cfg.Database.Type == "" { cfg.Database.Type = "sqlite" } if cfg.Path == "" { cfg.Path = defaultPath } if cfg.Secret == "" { cfg.Secret = generateSecret() } return cfg }