package config import ( "fmt" "os" "strconv" "strings" "time" ) const ( DefaultListenAddr = "127.0.0.1:8080" DefaultDataDir = "/opt/glchat/data" DefaultLogLevel = "info" ) type Config struct { Domain string FilesDomain string InstanceName string ListenAddr string DataDir string WebRoot string DatabasePath string SQLiteCache string SQLiteMmap string LogLevel string LogFormat string ReadPoolSize int MaxUploadSize int64 Version string Commit string BuildDate string ShutdownTTL time.Duration Maintenance time.Duration } func Load() (Config, error) { cfg := Config{ Domain: env("DOMAIN", "localhost"), FilesDomain: env("FILES_DOMAIN", ""), InstanceName: env("INSTANCE_NAME", "glchat"), ListenAddr: env("LISTEN_ADDR", DefaultListenAddr), DataDir: env("DATA_DIR", DefaultDataDir), WebRoot: env("WEB_ROOT", "/app/web"), DatabasePath: env("DATABASE_PATH", ""), SQLiteCache: env("SQLITE_CACHE_SIZE", "-8000"), SQLiteMmap: env("SQLITE_MMAP_SIZE", "134217728"), LogLevel: strings.ToLower(env("LOG_LEVEL", DefaultLogLevel)), LogFormat: strings.ToLower(env("LOG_FORMAT", "json")), ReadPoolSize: envInt("SQLITE_READ_POOL", 4), MaxUploadSize: envInt64("MAX_UPLOAD_SIZE", 26214400), Version: env("APP_VERSION", "dev"), Commit: env("APP_COMMIT", "none"), BuildDate: env("APP_BUILD_DATE", "unknown"), ShutdownTTL: time.Duration(envInt("SHUTDOWN_TIMEOUT_SECONDS", 20)) * time.Second, Maintenance: time.Duration(envInt("DB_MAINTENANCE_INTERVAL_SECONDS", 21600)) * time.Second, } if cfg.FilesDomain == "" { cfg.FilesDomain = "files." + cfg.Domain } if cfg.DatabasePath == "" { cfg.DatabasePath = cfg.DataDir + "/db/glchat.db" } if cfg.ReadPoolSize < 1 { return Config{}, fmt.Errorf("SQLITE_READ_POOL must be >= 1, got %d", cfg.ReadPoolSize) } if _, err := parseLogLevel(cfg.LogLevel); err != nil { return Config{}, err } if cfg.LogFormat != "json" && cfg.LogFormat != "text" { return Config{}, fmt.Errorf("LOG_FORMAT must be json or text, got %q", cfg.LogFormat) } return cfg, nil } func (c Config) BaseURL() string { return "https://" + c.Domain } func (c Config) FilesURL() string { return "https://" + c.FilesDomain } func (c Config) LogLevelNumeric() int { level, err := parseLogLevel(c.LogLevel) if err != nil { return 0 } return level } func parseLogLevel(level string) (int, error) { switch level { case "debug": return -4, nil case "info": return 0, nil case "warn", "warning": return 4, nil case "error": return 8, nil default: return 0, fmt.Errorf("unknown LOG_LEVEL %q", level) } } func env(key, fallback string) string { if v, ok := os.LookupEnv(key); ok && v != "" { return v } return fallback } func envInt(key string, fallback int) int { v, ok := os.LookupEnv(key) if !ok || v == "" { return fallback } n, err := strconv.Atoi(v) if err != nil { return fallback } return n } func envInt64(key string, fallback int64) int64 { v, ok := os.LookupEnv(key) if !ok || v == "" { return fallback } n, err := strconv.ParseInt(v, 10, 64) if err != nil { return fallback } return n }