package main import ( "context" "log" "net" "net/http" "sync/atomic" "time" "xprem/config" "xprem/internal/bucketmigration" "xprem/internal/metrics" infrastructure "xprem/internal/router" _ "xprem/internal/bucketmigrations" ) func init() { config.LoadConfig() metrics.InitMetrics() } // bootHandler answers while the bucket migrations run, and splits the two probes // on purpose. /hc (liveness) is registered below and answers 200 throughout, so // the orchestrator does not kill a pod in the middle of a long migration. // /ready (readiness) is deliberately NOT registered: it falls into the catch-all // and answers 503 + Retry-After, which drops the pod from the Service endpoints // without killing it, so nothing is served from a half-migrated bucket. Every // other request gets that same 503. Once main swaps in the real router, /ready // answers 200 like /hc (see internal/router/router.go). func bootHandler() http.Handler { mux := http.NewServeMux() mux.HandleFunc("/hc", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }) mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Retry-After", "15") http.Error(w, "storage migration in progress, updates are on hold", http.StatusServiceUnavailable) }) return mux } func main() { var handler atomic.Pointer[http.Handler] boot := bootHandler() handler.Store(&boot) server := &http.Server{ Addr: net.JoinHostPort(config.GetBindAddress(), config.GetPort()), Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { (*handler.Load()).ServeHTTP(w, r) }), ReadHeaderTimeout: 10 * time.Second, IdleTimeout: 120 * time.Second, } // Bind the port before running migrations so /hc answers from the very // first probe; ListenAndServe only returns on failure. go func() { log.Fatalf("Server failed to start: %v", server.ListenAndServe()) }() log.Println("Server is running on " + server.Addr) // The lock is released on failure, so a crash-looping pod retries the // migration on every boot instead of skipping it while the lock expires. if err := bucketmigration.EnsureMigrations(); err != nil { log.Fatalf("🚨 [BUCKET] %v", err) } container, cleanup := infrastructure.InitDependencies(context.Background()) defer cleanup() router := infrastructure.NewRouter(container) // No global CORS: each surface declares its own where its routes are // registered (dashboard subrouters, OAuth endpoints, /mcp). ready := http.Handler(router) handler.Store(&ready) log.Println("✅ Server is ready to serve traffic.") select {} }