package main import ( "context" "errors" "log/slog" "net/http" "os" "os/signal" "syscall" "time" // Embeds the timezone database in the binary so the local day rolls over // correctly inside a scratch image, which has no /usr/share/zoneinfo. _ "time/tzdata" "github.com/scottyah/tracker/internal/config" "github.com/scottyah/tracker/internal/store" "github.com/scottyah/tracker/internal/web" ) func main() { slog.SetDefault(slog.New(slog.NewTextHandler(os.Stdout, nil))) if err := run(); err != nil { slog.Error("fatal", "err", err) os.Exit(1) } } func run() error { cfg, err := config.Load() if err != nil { return err } ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer stop() st, err := store.Open(cfg.DataPath) if err != nil { return err } slog.Info("data ready", "path", cfg.DataPath, "tz", cfg.TZ.String()) srv, err := web.New(cfg, st) if err != nil { return err } httpServer := &http.Server{ Addr: cfg.Addr, Handler: srv, ReadHeaderTimeout: 10 * time.Second, ReadTimeout: 30 * time.Second, WriteTimeout: 60 * time.Second, IdleTimeout: 90 * time.Second, } go func() { <-ctx.Done() slog.Info("shutting down") shutdownCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() if err := httpServer.Shutdown(shutdownCtx); err != nil { slog.Error("shutdown", "err", err) } }() slog.Info("listening", "addr", cfg.Addr, "base_url", cfg.BaseURL) if err := httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { return err } return nil }