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" "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, publicURL(), 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)) proxyHandler.Register(v1) replicateHandler := replicate.New(predSvc, publicURL) replicateHandler.Register(v1) 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 }