4d34c6d31a
CI / test (push) Failing after 12m45s
- Add AdminUser model (bcrypt hashed passwords) and admin_users table - Add AdminJWTService for HS256 JWT sessions (24h TTL) - Add AdminSessionAuth middleware for /api/v1/admin/* routes - Add admin handlers: login, logout, me, change-password, users CRUD - Keys and model management routes now require admin JWT session - Remove ADMIN_TOKEN, add ADMIN_USERNAME, ADMIN_PASSWORD env vars - Update frontend: username/password login, admin_session storage, AdminUsers CRUD view
150 lines
4.0 KiB
Go
150 lines
4.0 KiB
Go
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
|
|
}
|