156 lines
4.3 KiB
Go
156 lines
4.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"`
|
|
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 "/run/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 the entire web block is empty (old config without "web" key),
|
|
// fill default port so the app still starts on 8080.
|
|
if cfg.Web.Port == "" && cfg.Web.Socket == "" {
|
|
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
|
|
}
|