Files
homelab-frontend/internal/config/config.go
T
Admin Bot 14cc67833c
CI / Vet, test, build (push) Successful in 3m52s
CI / Build and push image (push) Successful in 1m17s
feat(auth): wire JWT validation into /v1/* LLM endpoints
2026-08-31 23:01:59 -07:00

144 lines
4.4 KiB
Go

package config
import (
"fmt"
"os"
"time"
"forgejo.riotpiao.com/rock/homelab-frontend/internal/serviceadapter"
)
// Config holds the gateway configuration.
type Config struct {
// ListenAddr is the address to listen on for HTTP traffic.
ListenAddr string
// ShutdownTimeout is the maximum time to wait for in-flight requests
// to complete before forcing shutdown.
ShutdownTimeout time.Duration
// Routes maps route names to their upstream configuration.
Routes map[string]*Route
// Models maps model names to their upstream configuration.
// Multiple models can point to the same upstream address.
Models map[string]*ModelUpstream
// Adapters holds service adapter definitions for X-Service routing.
Adapters []*serviceadapter.ServiceAdapter
// Auth holds JWT authentication configuration for /v1/* endpoints.
Auth AuthConfig
}
// ModelUpstream holds upstream configuration for a specific model.
type ModelUpstream struct {
// Name is the model name clients send (e.g., "reasoning", "ornith:35b").
Name string
// Address is the upstream server address (host:port).
Address string
// Path is the upstream path for this model (e.g., "/v1/chat/completions").
Path string
// AuthRequired indicates whether this model requires JWT authentication.
AuthRequired bool
}
// AuthConfig holds JWT authentication configuration.
type AuthConfig struct {
// Enabled globally enables/disables auth for /v1/* endpoints.
Enabled bool
// Issuer is the expected JWT issuer (iss claim).
Issuer string
// Audience is the expected JWT audience (aud claim).
Audience string
// JWKSURL is the URL to fetch JSON Web Key Set for signature validation.
JWKSURL string
// RequiredCapability is the permission required for LLM inference (e.g., "llm:inference").
RequiredCapability string
}
// Route represents a single route and its upstream configuration.
type Route struct {
// Name is the route identifier.
Name string
// Upstream holds the upstream server configuration.
Upstream Upstream
}
// Upstream holds upstream server configuration for a route.
type Upstream struct {
// Address is the upstream server address (host:port).
Address string
// PathRewrite is an optional path prefix rewrite. Empty string means no rewrite.
PathRewrite string
// ConnectTimeout is the maximum time to establish a connection to the upstream.
ConnectTimeout time.Duration
// ReadTimeout is the maximum time to read a response from the upstream.
ReadTimeout time.Duration
// WriteTimeout is the maximum time to write a request to the upstream.
WriteTimeout time.Duration
// MaxBodySize is the maximum request body size in bytes.
MaxBodySize int64
// AuthRequired indicates whether this route requires authentication.
AuthRequired bool
}
// LookupModel finds a model by name (case-sensitive, exact match).
func (c *Config) LookupModel(name string) *ModelUpstream {
if c == nil || c.Models == nil {
return nil
}
return c.Models[name]
}
// ModelNames returns a sorted list of all known model names.
func (c *Config) ModelNames() []string {
if c == nil || c.Models == nil {
return nil
}
names := make([]string, 0, len(c.Models))
for name := range c.Models {
names = append(names, name)
}
return names
}
// Load reads configuration from environment variables with defaults.
func Load() (*Config, error) {
listenAddr := "127.0.0.1:8080"
// Allow override via environment variable
if addr, ok := os.LookupEnv("LISTEN_ADDR"); ok {
listenAddr = addr
}
shutdownTimeout := 30 * time.Second
// Allow override via environment variable
if timeout, ok := os.LookupEnv("SHUTDOWN_TIMEOUT"); ok {
d, err := time.ParseDuration(timeout)
if err != nil {
return nil, fmt.Errorf("invalid SHUTDOWN_TIMEOUT: %w", err)
}
shutdownTimeout = d
}
// Load routes, models, adapters, and auth from config file
routes := make(map[string]*Route)
models := make(map[string]*ModelUpstream)
var adapters []*serviceadapter.ServiceAdapter
var authConfig AuthConfig
if configPath, ok := os.LookupEnv("CONFIG_PATH"); ok {
loadedRoutes, loadedModels, loadedAdapters, loadedAuth, err := LoadRoutesAndModelsFromFile(configPath)
if err != nil {
return nil, err
}
routes = loadedRoutes
models = loadedModels
adapters = loadedAdapters
authConfig = loadedAuth
}
return &Config{
ListenAddr: listenAddr,
ShutdownTimeout: shutdownTimeout,
Routes: routes,
Models: models,
Adapters: adapters,
Auth: authConfig,
}, nil
}