Ctrl K
ComunidadGuías…

cmd/api/main.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
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 {}
}