Files
fit/cmd/server/main.go
scott faf73c8be5 Track five things a day for seventy-five days
The workout, the protein, the calories, the water, and three lines of
gratitude. One page per day, a card of seventy-five tiles that fill by
how many of the five landed, and a missed day left as a gap rather than
a reset.

Single Go binary with no dependencies. The log is one JSON file on a
mounted volume, written atomically and never silently replaced when it
fails to parse, since it is the one thing here that cannot be recreated.
Auth is Traefik basic auth at the ingress; the app has no login of its
own, so every POST checks Sec-Fetch-Site to stop another origin posting
with those credentials.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 21:59:01 -07:00

78 lines
1.6 KiB
Go

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
}