mirror of
https://github.com/cole-maxwell1/todo-hetzner.git
synced 2026-10-27 19:02:49 -05:00
Implement the server-rendered Go todo application with PostgreSQL persistence, Tailwind styling, health checks, and test coverage. Provision production and staging infrastructure with OpenTofu, including private networking, static IPs, Object Storage backups, and load balancing. Configure Rocky Linux hosts with Ansible, rootless Podman, PostgreSQL, WAL-G backup automation, staging restores, and generated inventory.
78 lines
1.6 KiB
Go
78 lines
1.6 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"log/slog"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
"time"
|
|
|
|
"todo-hetzner/app/internal/db"
|
|
"todo-hetzner/app/internal/handler"
|
|
)
|
|
|
|
func main() {
|
|
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
|
|
if err := run(logger); err != nil {
|
|
logger.Error("server stopped", "error", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func run(logger *slog.Logger) error {
|
|
databaseURL := os.Getenv("DATABASE_URL")
|
|
if databaseURL == "" {
|
|
return errors.New("DATABASE_URL is required")
|
|
}
|
|
addr := os.Getenv("APP_ADDR")
|
|
if addr == "" {
|
|
addr = ":8080"
|
|
}
|
|
|
|
store, err := db.Open(databaseURL)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer store.Close()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
if err := store.Ping(ctx); err != nil {
|
|
return err
|
|
}
|
|
|
|
h, err := handler.New(store, logger)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
server := &http.Server{
|
|
Addr: addr,
|
|
Handler: h.Routes(),
|
|
ReadHeaderTimeout: 5 * time.Second,
|
|
ReadTimeout: 10 * time.Second,
|
|
WriteTimeout: 15 * time.Second,
|
|
IdleTimeout: 60 * time.Second,
|
|
MaxHeaderBytes: 1 << 20,
|
|
}
|
|
|
|
shutdownCtx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
|
defer stop()
|
|
go func() {
|
|
<-shutdownCtx.Done()
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
if err := server.Shutdown(ctx); err != nil {
|
|
logger.Error("graceful shutdown failed", "error", err)
|
|
}
|
|
}()
|
|
|
|
logger.Info("server listening", "address", addr)
|
|
if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|