package main import ( "context" "log" "net/http" "os" "os/signal" "strconv" "syscall" "time" "gitea.mrixs.me/Mrixs/MrixsCraft-server/internal/admin" "gitea.mrixs.me/Mrixs/MrixsCraft-server/internal/api" "gitea.mrixs.me/Mrixs/MrixsCraft-server/internal/auth" "gitea.mrixs.me/Mrixs/MrixsCraft-server/internal/cas" "gitea.mrixs.me/Mrixs/MrixsCraft-server/internal/config" "gitea.mrixs.me/Mrixs/MrixsCraft-server/internal/database" "gitea.mrixs.me/Mrixs/MrixsCraft-server/internal/middleware" "gitea.mrixs.me/Mrixs/MrixsCraft-server/internal/session" "gitea.mrixs.me/Mrixs/MrixsCraft-server/internal/templates" ) func main() { ctx := context.Background() cfg, err := config.Load() if err != nil { log.Fatalf("Failed to load config: %v", err) } db, err := database.Open(ctx, cfg.DatabaseURL) if err != nil { log.Fatalf("Failed to connect to database: %v", err) } defer db.Close() // Start session cleanup worker (runs every hour in the background). cleanupCancel := session.StartCleanupWorker(db, 1*time.Hour) defer cleanupCancel() mux := http.NewServeMux() // Health check — no auth needed. mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte("ok")) }) // Yggdrasil API. authHandler := auth.NewHandler(db, cfg) authHandler.RegisterRoutes(mux) // Public API. apiHandler := api.NewHandler(db, cfg) apiHandler.RegisterRoutes(mux) // CAS (Content-Addressable Storage) file serving. casHandler := cas.NewHandler(db, cfg) casHandler.RegisterRoutes(mux) // Admin panel. adminHandler := admin.NewHandler(db, cfg) adminHandler.RegisterRoutes(mux) // Templates (web UI). templatesHandler := templates.NewHandler(db, cfg) templatesHandler.RegisterRoutes(mux) // Wrapper chain: Recovery → Logging → RateLimit → CORS → mux. // Recovery must be outermost so it catches panics in all inner layers. var handler http.Handler = mux handler = middleware.CORS(handler) handler = middleware.NewRateLimiter(30, time.Minute, 60).Limit(handler) handler = middleware.Logging(handler) handler = middleware.Recovery(handler) addr := ":" + itoa(cfg.Port) srv := &http.Server{ Addr: addr, Handler: handler, } // Graceful shutdown. done := make(chan os.Signal, 1) signal.Notify(done, syscall.SIGINT, syscall.SIGTERM) go func() { log.Printf("MrixsCraft Server starting on %s", addr) if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("Server error: %v", err) } }() <-done log.Println("Shutting down…") shutdownCtx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() if err := srv.Shutdown(shutdownCtx); err != nil { log.Printf("Shutdown error: %v", err) } log.Println("Stopped.") } // itoa converts int to string (stdlib alias to avoid fmt import just for this). func itoa(n int) string { return strconv.Itoa(n) }