Files
go_blog/config/config.go
T
2026-06-23 14:57:36 +08:00

154 lines
4.1 KiB
Go

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"`
Web WebConfig `yaml:"web"`
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 (required when type is "mysql")
}
// WebConfig holds web-server listening configuration.
type WebConfig struct {
Port string `yaml:"port"` // TCP port, "" or "0" to disable
Socket string `yaml:"socket"` // Unix socket path, "" to disable
}
const defaultPort = "8080"
// mysqlExampleDSN is written into new config files as a reference.
const mysqlExampleDSN = "user:password@tcp(127.0.0.1:3306)/blog_go?charset=utf8mb4&parseTime=True&loc=Local"
// getConfigPath returns the OS-aware config directory and config file path.
func getConfigPath() (dir, file string) {
switch runtime.GOOS {
case "windows":
dir = filepath.Join(".", "win", "etc", "blog_go")
case "darwin":
dir = filepath.Join(".", "mac", "etc", "blog_go")
default:
dir = filepath.Join("/", "etc", "blog_go")
}
file = filepath.Join(dir, "config.yaml")
return
}
// getDefaultStoragePath returns the OS-aware default storage path.
func getDefaultStoragePath() string {
switch runtime.GOOS {
case "windows":
return filepath.Join(".", "win", "srv", "blog_go")
case "darwin":
return filepath.Join(".", "mac", "srv", "blog_go")
default:
return filepath.Join("/", "srv", "blog_go")
}
}
// generateSecret returns a random-ish hex string for the session secret.
func generateSecret() string {
hostname, _ := os.Hostname()
input := fmt.Sprintf("%s-%d", hostname, os.Getpid())
hash := sha256.Sum256([]byte(input))
return fmt.Sprintf("%x", hash)
}
// getDefaultSocketPath returns the OS-aware default unix socket path.
func getDefaultSocketPath() string {
if runtime.GOOS == "linux" {
return "/opt/blog_go/web.sock"
}
return ""
}
// LoadConfig reads the config file or creates one with defaults.
// If customPath is non-empty, it overrides the OS-aware config file path.
func LoadConfig(customPath string) *Config {
configDir, configFile := getConfigPath()
if customPath != "" {
configFile = customPath
configDir = filepath.Dir(configFile)
}
defaultPath := getDefaultStoragePath()
// 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: mysqlExampleDSN,
},
Web: WebConfig{
Port: defaultPort,
Socket: getDefaultSocketPath(),
},
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)
cfg := &Config{}
return applyDefaults(cfg, defaultPath)
}
cfg := &Config{}
if err := yaml.Unmarshal(data, cfg); err != nil {
log.Printf("Warning: malformed config file %s: %v, using defaults", configFile, err)
}
return applyDefaults(cfg, defaultPath)
}
// applyDefaults fills zero-value fields with sensible defaults.
func applyDefaults(cfg *Config, defaultPath string) *Config {
if cfg.Web.Port == "" {
cfg.Web.Port = defaultPort
}
if cfg.Database.Type == "" {
cfg.Database.Type = "sqlite"
}
if cfg.Path == "" {
cfg.Path = defaultPath
}
if cfg.Secret == "" {
cfg.Secret = generateSecret()
}
return cfg
}