Files
dynagate/main.go
T

139 lines
3.9 KiB
Go

/*
Dynagate LLM Gateway: dynamically change models on the fly
Created by Luxferre in 2026, released into the public domain
*/
package main
import (
"context"
"flag"
"fmt"
"log"
"net/http"
"os"
"os/exec"
"os/signal"
"strings"
"syscall"
"time"
)
func main() {
port := flag.Int("port", 8080, "Port to listen on")
csvPath := flag.String("csv", "models.csv", "Path to the CSV configuration file")
keyPath := flag.String("key", "", "Path to plaintext file containing expected auth token")
csvUpdater := flag.String("csv-updater", "", "Command to run to update the configuration CSV file")
csvUpdateInterval := flag.Int("csv-update-interval", 10, "Interval in minutes with which to run the CSV updater command")
flag.Parse()
var authTokens []string
if *keyPath != "" {
content, err := os.ReadFile(*keyPath)
if err != nil {
log.Fatalf("Failed to read auth token file: %v", err)
}
var tokens []string
for _, line := range strings.Split(string(content), "\n") {
token := strings.TrimSpace(line)
if token != "" {
tokens = append(tokens, token)
}
}
if len(tokens) > 0 {
authTokens = tokens
log.Printf("Starting Dynagate LLM Gateway. Config: %s, Port: %d, Auth: Required (%d tokens loaded)", *csvPath, *port, len(authTokens))
} else {
log.Printf("Starting Dynagate LLM Gateway. Config: %s, Port: %d, Auth: None (empty key file)", *csvPath, *port)
}
} else {
log.Printf("Starting Dynagate LLM Gateway. Config: %s, Port: %d, Auth: None", *csvPath, *port)
}
// Initialize configuration manager
cm := NewConfigManager(*csvPath)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Start background config watcher
go cm.Watch(ctx)
// Start background CSV updater if specified
if *csvUpdater != "" {
mins := *csvUpdateInterval
if mins <= 0 {
log.Printf("Warning: csv-update-interval must be positive, defaulting to 10 minutes")
mins = 10
}
interval := time.Duration(mins) * time.Minute
log.Printf("Starting background CSV updater: command=%q, interval=%v", *csvUpdater, interval)
go startCSVUpdater(ctx, *csvUpdater, interval)
}
// Register routes
mux := http.NewServeMux()
mux.HandleFunc("/v1/models", handleModels(cm, authTokens))
mux.HandleFunc("/v1/chat/completions", handleChatCompletions(cm, authTokens))
mux.HandleFunc("/v1/images/generations", handleImageGenerations(cm, authTokens))
server := &http.Server{
Addr: fmt.Sprintf(":%d", *port),
Handler: mux,
}
// Graceful shutdown setup
shutdownSig := make(chan os.Signal, 1)
signal.Notify(shutdownSig, os.Interrupt, syscall.SIGTERM)
go func() {
log.Printf("Server listening on port %d", *port)
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("Server listen failed: %v", err)
}
}()
<-shutdownSig
log.Println("Shutting down Dynagate server...")
cancel() // stop config watcher
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 10*time.Second)
defer shutdownCancel()
if err := server.Shutdown(shutdownCtx); err != nil {
log.Fatalf("Server shutdown failed: %v", err)
}
log.Println("Server exited cleanly")
}
func startCSVUpdater(ctx context.Context, command string, interval time.Duration) {
runUpdater := func() {
log.Printf("Running CSV updater command: %s", command)
cmd := exec.Command("/bin/sh", "-c", command)
output, err := cmd.CombinedOutput()
if err != nil {
log.Printf("CSV updater command failed: %v, output: %q", err, strings.TrimSpace(string(output)))
} else {
log.Printf("CSV updater command completed successfully")
}
}
// Run once immediately at startup
runUpdater()
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
runUpdater()
}
}
}