135 lines
3.5 KiB
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
|
|
}
|