8e82a23555
CI / docker (push) Successful in 2m49s
Add model input defaults (including prompt), workflow model editing, and ingress API docs. Co-authored-by: Cursor <cursoragent@cursor.com>
135 lines
4.1 KiB
Go
135 lines
4.1 KiB
Go
package main
|
|
|
|
import (
|
|
"flag"
|
|
"log"
|
|
"os"
|
|
"os/signal"
|
|
"path/filepath"
|
|
"strings"
|
|
"syscall"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
webembed "github.com/rose_cat707/luminary/web"
|
|
"github.com/rose_cat707/luminary/internal/gateway"
|
|
"github.com/rose_cat707/luminary/internal/handler/admin"
|
|
"github.com/rose_cat707/luminary/internal/handler/files"
|
|
v1handler "github.com/rose_cat707/luminary/internal/handler/v1"
|
|
"github.com/rose_cat707/luminary/internal/handler/proxy"
|
|
"github.com/rose_cat707/luminary/internal/handler/replicate"
|
|
"github.com/rose_cat707/luminary/internal/handler/static"
|
|
"github.com/rose_cat707/luminary/internal/middleware"
|
|
"github.com/rose_cat707/luminary/internal/model"
|
|
"github.com/rose_cat707/luminary/internal/monitor"
|
|
"github.com/rose_cat707/luminary/internal/prediction"
|
|
"github.com/rose_cat707/luminary/internal/settings"
|
|
"github.com/rose_cat707/luminary/internal/storage/imagestore"
|
|
"github.com/rose_cat707/luminary/internal/store/cache"
|
|
sqlitestore "github.com/rose_cat707/luminary/internal/store/sqlite"
|
|
)
|
|
|
|
func main() {
|
|
dataDir := flag.String("data-dir", envOr("LUMINARY_DATA", "./data"), "directory for SQLite database")
|
|
addr := flag.String("addr", envOr("LUMINARY_ADDR", ":8293"), "HTTP listen address")
|
|
deprecatedConfig := flag.String("config", "", "deprecated: ignored; settings are stored in the database")
|
|
flag.Parse()
|
|
if *deprecatedConfig != "" {
|
|
log.Printf("warning: -config is deprecated and ignored; use -data-dir / LUMINARY_DATA and admin UI settings")
|
|
}
|
|
|
|
if err := os.MkdirAll(*dataDir, 0o755); err != nil {
|
|
log.Fatalf("data dir: %v", err)
|
|
}
|
|
dbPath := filepath.Join(*dataDir, "luminary.db")
|
|
|
|
db, err := sqlitestore.New(dbPath)
|
|
if err != nil {
|
|
log.Fatalf("database: %v", err)
|
|
}
|
|
|
|
rt := settings.NewRuntime(db.DB())
|
|
if err := rt.Load(); err != nil {
|
|
log.Fatalf("settings: %v", err)
|
|
}
|
|
|
|
memCache := cache.New()
|
|
repo := sqlitestore.NewRepository(db, memCache)
|
|
if err := repo.EnsureAdmin(); err != nil {
|
|
log.Fatalf("admin bootstrap: %v", err)
|
|
}
|
|
if err := repo.BootstrapFromDB(); err != nil {
|
|
log.Fatalf("cache bootstrap: %v", err)
|
|
}
|
|
rt.AfterLoad()
|
|
|
|
imageDir := filepath.Join(*dataDir, rt.ImageStoragePath())
|
|
publicURL := func() string {
|
|
if u := rt.PublicBaseURL(); u != "" {
|
|
return u
|
|
}
|
|
a := *addr
|
|
if strings.HasPrefix(a, ":") {
|
|
a = "localhost" + a
|
|
}
|
|
return "http://" + a
|
|
}
|
|
imgStore := imagestore.New(db.DB(), imageDir, publicURL, rt.EncryptionKey(), rt.SignedURLTTL())
|
|
_ = imgStore.EnsureDir()
|
|
|
|
predSvc := prediction.NewService(db.DB(), memCache, imgStore, rt.PredictionWaitSeconds())
|
|
|
|
gw := gateway.New(memCache, rt)
|
|
mon := monitor.New(gw, memCache, db, rt)
|
|
mon.Start(repo.FlushUsage)
|
|
|
|
gin.SetMode(gin.ReleaseMode)
|
|
r := gin.New()
|
|
r.Use(middleware.Logger(), gin.Recovery())
|
|
|
|
r.GET("/health", func(c *gin.Context) {
|
|
c.JSON(200, gin.H{"status": "ok"})
|
|
})
|
|
|
|
adminHandler := admin.New(repo, db, memCache, gw, rt, mon, predSvc, imgStore, publicURL)
|
|
adminGroup := r.Group("/api/admin", middleware.IPFilter(memCache, model.IPScopeAdmin))
|
|
adminHandler.Register(adminGroup)
|
|
|
|
proxyHandler := proxy.New(gw)
|
|
v1 := r.Group("/v1", middleware.IPFilter(memCache, model.IPScopeProxy), middleware.IngressAuth(gw))
|
|
v1.GET("/models", v1handler.NewModelsHandler(gw, predSvc, publicURL).List)
|
|
|
|
textAPI := v1.Group("", middleware.RequireIngressProtocol(model.IngressProtocolText))
|
|
proxyHandler.Register(textAPI)
|
|
|
|
replicateHandler := replicate.New(predSvc, publicURL)
|
|
imageAPI := v1.Group("", middleware.RequireIngressProtocol(model.IngressProtocolImage))
|
|
replicateHandler.Register(imageAPI)
|
|
|
|
filesHandler := files.New(imgStore)
|
|
filesHandler.Register(r)
|
|
|
|
staticHandler := static.New(webembed.WebFS)
|
|
staticHandler.Register(r)
|
|
|
|
go func() {
|
|
log.Printf("Luminary listening on %s (data=%s)", *addr, *dataDir)
|
|
if err := r.Run(*addr); err != nil {
|
|
log.Fatalf("server: %v", err)
|
|
}
|
|
}()
|
|
|
|
quit := make(chan os.Signal, 1)
|
|
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
|
<-quit
|
|
log.Println("shutting down...")
|
|
mon.Stop()
|
|
repo.FlushUsage(memCache.DrainUsageDeltas())
|
|
}
|
|
|
|
func envOr(key, fallback string) string {
|
|
if v := os.Getenv(key); v != "" {
|
|
return v
|
|
}
|
|
return fallback
|
|
}
|