package main import ( "context" "log" "net/http" "os" "os/signal" "sync" "syscall" "time" ) var ( shutdownMu sync.Mutex shutdownFns []func() hooksOnce sync.Once bgHandlerOnce sync.Once ) // registerShutdown runs fn on SIGINT/SIGTERM (LIFO order). func registerShutdown(fn func()) { shutdownMu.Lock() shutdownFns = append(shutdownFns, fn) shutdownMu.Unlock() } func runShutdownHooks() { hooksOnce.Do(func() { shutdownMu.Lock() fns := shutdownFns shutdownMu.Unlock() for i := len(fns) - 1; i >= 0; i-- { fns[i]() } }) } // enableShutdownOnInterrupt listens for SIGINT/SIGTERM in the background and exits // after running shutdown hooks. Use for one-shot CLI commands (OTA, etc.). func enableShutdownOnInterrupt() { bgHandlerOnce.Do(func() { ch := make(chan os.Signal, 1) signal.Notify(ch, os.Interrupt, syscall.SIGTERM) go func() { sig := <-ch signal.Stop(ch) log.Printf("received %v, shutting down…", sig) runShutdownHooks() os.Exit(0) }() }) } // waitForShutdown blocks until SIGINT/SIGTERM, runs hooks, and returns. // Use for long-running servers (serve/dashboard). func waitForShutdown() { ch := make(chan os.Signal, 1) signal.Notify(ch, os.Interrupt, syscall.SIGTERM) sig := <-ch signal.Stop(ch) log.Printf("received %v, shutting down…", sig) runShutdownHooks() } func shutdownHTTPServer(srv *http.Server) { if srv == nil { return } ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil { log.Printf("HTTP shutdown: %v", err) } }