package main import ( "context" "embed" "errors" "io/fs" "log/slog" "net/http" "os" "os/signal" "syscall" "time" "github.com/nmoniz/any2anexoj/internal/web" ) //go:embed all:static var staticFS embed.FS func main() { if err := run(); err != nil { slog.Error("any2anexoj-web: fatal error", slog.Any("err", err)) os.Exit(1) } } // run wires up the server, installs signal-driven cancellation, and blocks on // the HTTP server until it stops. It is split out from main so tests can drive // the same code paths if needed. func run() error { addr := os.Getenv("PORT") if addr == "" { addr = ":8080" } // A bare "8080" value is friendlier than the net.Listen-style ":8080". if addr[0] != ':' { addr = ":" + addr } // Sub-mount the embedded FS so the static subtree is rooted at "" inside // the web package (matches /static/...). staticSub, err := fs.Sub(staticFS, "static") if err != nil { return err } state := web.NewState() srv, err := web.NewServer(state, staticSub) if err != nil { return err } httpServer := &http.Server{ Addr: addr, Handler: srv.Handler(), ReadHeaderTimeout: 10 * time.Second, } ctx, cancel := signal.NotifyContext(context.Background(), os.Kill, os.Interrupt, syscall.SIGTERM) defer cancel() errCh := make(chan error, 1) go func() { slog.Info("any2anexoj-web: listening", slog.String("addr", addr)) if err := httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { errCh <- err return } errCh <- nil }() select { case <-ctx.Done(): slog.Info("any2anexoj-web: shutting down") shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 10*time.Second) defer shutdownCancel() if err := httpServer.Shutdown(shutdownCtx); err != nil { return err } return nil case err := <-errCh: return err } }