package config import ( "log/slog" "os" "strconv" "time" ) type Config struct { // Server LlamalinkEnv string LlamalinkHost string LlamalinkPort int // Database DatabaseURL string DatabaseMaxOpenConns int DatabaseMaxIdleConns int DatabaseConnMaxLifetime int // seconds // Llama server management ManageLlamaServer bool LlamaServerBin string LlamaServerHost string LlamaServerPort int LlamaServerStartupTimeout int // seconds LlamaServerStopTimeout int // seconds ModelSwapCooldown int // seconds // Rate limiting RateLimitPerMinute int RateLimitStorage string // "memory" or "redis" // Auth AdminUsername string AdminPassword string JWTSecret []byte AdminSessionTTL int // seconds // Logging LogLevel string LogFormat string // "json" or "text" } func Load() *Config { c := &Config{ LlamalinkEnv: getEnv("LLAMALINK_ENV", "development"), LlamalinkHost: getEnv("LLAMALINK_HOST", "0.0.0.0"), LlamalinkPort: intEnv("LLAMALINK_PORT", 8000), DatabaseURL: getEnv("DATABASE_URL", "sqlite:///./llamalink.db"), DatabaseMaxOpenConns: intEnv("DATABASE_MAX_OPEN_CONNS", 25), DatabaseMaxIdleConns: intEnv("DATABASE_MAX_IDLE_CONNS", 5), DatabaseConnMaxLifetime: intEnv("DATABASE_CONN_MAX_LIFETIME", 300), ManageLlamaServer: boolEnv("MANAGE_LLAMA_SERVER", true), LlamaServerBin: getEnv("LLAMA_SERVER_BIN", "/usr/local/bin/llama-server"), LlamaServerHost: getEnv("LLAMA_SERVER_HOST", "127.0.0.1"), LlamaServerPort: intEnv("LLAMA_SERVER_PORT", 8080), LlamaServerStartupTimeout: intEnv("LLAMA_SERVER_STARTUP_TIMEOUT", 120), LlamaServerStopTimeout: intEnv("LLAMA_SERVER_STOP_TIMEOUT", 10), ModelSwapCooldown: intEnv("MODEL_SWAP_COOLDOWN", 2), RateLimitPerMinute: intEnv("RATE_LIMIT_PER_MINUTE", 60), RateLimitStorage: getEnv("RATE_LIMIT_STORAGE", "memory"), AdminUsername: getEnv("ADMIN_USERNAME", "admin"), AdminPassword: getEnv("ADMIN_PASSWORD", ""), AdminSessionTTL: intEnv("ADMIN_SESSION_TTL", 86400), LogLevel: getEnv("LOG_LEVEL", "info"), LogFormat: getEnv("LOG_FORMAT", "json"), } secretKey := getEnv("LLAMALINK_SECRET_KEY", "change-me-in-production") if secretKey == "change-me-in-production" { slog.Warn("LLAMALINK_SECRET_KEY is using the default value — set a secure random string in production") } c.JWTSecret = []byte(secretKey) return c } func (c *Config) LlamaServerURL() string { return "http://" + c.LlamaServerHost + ":" + strconv.Itoa(c.LlamaServerPort) } func (c *Config) LlamaServerStartupTimeoutDuration() time.Duration { return time.Duration(c.LlamaServerStartupTimeout) * time.Second } func (c *Config) LlamaServerStopTimeoutDuration() time.Duration { return time.Duration(c.LlamaServerStopTimeout) * time.Second } func (c *Config) ModelSwapCooldownDuration() time.Duration { return time.Duration(c.ModelSwapCooldown) * time.Second } func (c *Config) Logger() *slog.Logger { var level slog.Level switch c.LogLevel { case "debug": level = slog.LevelDebug case "warn": level = slog.LevelWarn case "error": level = slog.LevelError default: level = slog.LevelInfo } opts := &slog.HandlerOptions{ Level: level, } var handler slog.Handler if c.LogFormat == "text" { handler = slog.NewTextHandler(os.Stdout, opts) } else { handler = slog.NewJSONHandler(os.Stdout, opts) } return slog.New(handler) } func getEnv(key, defaultValue string) string { if v := os.Getenv(key); v != "" { return v } return defaultValue } func intEnv(key string, defaultValue int) int { if v := os.Getenv(key); v != "" { if i, err := strconv.Atoi(v); err == nil { return i } } return defaultValue } func boolEnv(key string, defaultValue bool) bool { if v := os.Getenv(key); v != "" { if v == "true" || v == "1" || v == "yes" { return true } if v == "false" || v == "0" || v == "no" { return false } } return defaultValue }