Files
go_blog/config/config.go
T
kevinandClaude Opus 4.8 c82d4482d4 feat: initial blog engine with multi-language, profile, and role system
- Gin + GORM + Tailwind CSS blog engine
- OS-aware config (Linux /etc/blog_go/, Windows ./win/etc/blog_go/)
- SQLite by default, MySQL support via config
- Auto-migrate database + seed admin user on first run
- Session-based auth (login/logout)
- i18n: Chinese/English with auto-detect + manual switch
- Avatar dropdown nav with click-to-toggle menu
- Profile page: avatar upload, edit info, change password
- Role system: admin (full access) and author (profile only)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 01:49:04 +08:00

126 lines
3.3 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"`
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
}