kereru-forms/backend/cmd/kereru-forms/main.go

135 lines
3.5 KiB
Go

// Command kereru-forms serves the Kererū Forms HTTP API.
package main
import (
"context"
"errors"
"fmt"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"kereru.forms/internal/api"
"kereru.forms/internal/auth"
"kereru.forms/internal/config"
"kereru.forms/internal/database"
"kereru.forms/internal/logging"
"kereru.forms/internal/telemetry"
)
// version is overridden at build time with -ldflags "-X main.version=...".
var version = "1.0.0"
func main() {
if err := run(); err != nil {
fmt.Fprintf(os.Stderr, "kereru-forms: %v\n", err)
os.Exit(1)
}
}
func run() error {
cfg, err := config.Load(os.Args[1:])
if err != nil {
return err
}
logger := logging.New(cfg.LogLevel, cfg.LogFormat, cfg.Environment)
logger.Info().
Str("version", version).
Str("environment", cfg.Environment).
Strs("listen", cfg.Addresses()).
Str("database_driver", cfg.DBDriver).
Msg("starting kereru-forms")
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
tel, err := telemetry.Setup(ctx, telemetry.Options{
Enabled: cfg.TelemetryEnabled,
Endpoint: cfg.OTLPEndpoint,
ServiceName: "kereru-forms",
Environment: cfg.Environment,
Version: version,
})
if err != nil {
return fmt.Errorf("setup telemetry: %w", err)
}
defer func() {
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := tel.Shutdown(shutdownCtx); err != nil {
logger.Warn().Err(err).Msg("telemetry shutdown")
}
}()
store, err := database.Open(cfg.DBDriver, cfg.DBDSN)
if err != nil {
return fmt.Errorf("open database: %w", err)
}
defer func() {
if err := store.Close(); err != nil {
logger.Warn().Err(err).Msg("database close")
}
}()
authService, err := auth.New(ctx, cfg.Auth)
if err != nil {
return fmt.Errorf("setup authentication: %w", err)
}
logger.Info().
Bool("oidc", cfg.Auth.OIDCEnabled()).
Bool("submission_auth_required", cfg.Auth.RequireSubmissionAuth).
Msg("authentication configured")
if !cfg.Auth.OIDCEnabled() && cfg.Auth.LocalPassword == "admin" {
logger.Warn().Msg("using the default local password; set KERERU_AUTH_LOCAL_PASSWORD or auth.localPassword")
}
server := api.NewServer(store, logger, version, authService)
router := api.NewRouter(server, logger, api.RouterOptions{
CORSOrigins: cfg.CORSOrigins,
ServiceName: "kereru-forms",
StaticDir: cfg.StaticDir,
})
addrs := cfg.Addresses()
servers := make([]*http.Server, 0, len(addrs))
errCh := make(chan error, len(addrs))
for _, addr := range addrs {
srv := &http.Server{
Addr: addr,
Handler: router,
ReadHeaderTimeout: 10 * time.Second,
ReadTimeout: 30 * time.Second,
WriteTimeout: 60 * time.Second,
IdleTimeout: 120 * time.Second,
}
servers = append(servers, srv)
go func(srv *http.Server) {
logger.Info().Str("addr", srv.Addr).Msg("http server listening")
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
errCh <- fmt.Errorf("listen on %s: %w", srv.Addr, err)
}
}(srv)
}
select {
case <-ctx.Done():
logger.Info().Msg("shutdown signal received")
case err := <-errCh:
logger.Error().Err(err).Msg("http server failed")
stop()
}
shutdownCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
for _, srv := range servers {
if err := srv.Shutdown(shutdownCtx); err != nil {
logger.Warn().Err(err).Str("addr", srv.Addr).Msg("http server shutdown")
}
}
logger.Info().Msg("kereru-forms stopped")
return nil
}