// Package app reads the pixad command line and runs the server. package app import ( "context" "fmt" "os" "os/signal" "syscall" "github.com/prometheus/client_golang/prometheus" "github.com/spf13/cobra" "go.uber.org/dig" "go.uber.org/fx" "sneak.berlin/go/pixa/internal/config" "sneak.berlin/go/pixa/internal/database" "sneak.berlin/go/pixa/internal/globals" "sneak.berlin/go/pixa/internal/handlers" "sneak.berlin/go/pixa/internal/healthcheck" "sneak.berlin/go/pixa/internal/logger" "sneak.berlin/go/pixa/internal/middleware" "sneak.berlin/go/pixa/internal/providers" "sneak.berlin/go/pixa/internal/server" ) var configPath string //nolint:gochecknoglobals // cobra flag // Run reads the command line and runs the server until it stops, with // version as the version pixad logs and reports. It exits the process // with status 1 when the command line is not valid. func Run(version string) { globals.Version = version rootCmd := &cobra.Command{ Use: "pixad", Short: "Pixa image caching proxy server", Run: run, } rootCmd.Flags().StringVarP(&configPath, "config", "c", "", "path to config file") err := rootCmd.Execute() if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } } func run(_ *cobra.Command, _ []string) { // Set config path in environment if specified via flag if configPath != "" { _ = os.Setenv("PIXA_CONFIG_PATH", configPath) } // A write to a closed stdout or stderr must not end the process. signal.Ignore(syscall.SIGPIPE) app := fx.New( // pixad logs with slog only; runApp prints a startup or shutdown // error itself. fx.NopLogger, fx.Provide( config.New, database.New, globals.New, handlers.New, logger.New, server.New, middleware.New, healthcheck.New, // The registry /metrics serves func() prometheus.Registerer { return prometheus.DefaultRegisterer }, ), fx.Provide(providers.Constructors()...), fx.Invoke( func(log *logger.Logger) { log.Identify() }, func(*server.Server) {}, ), ) exitCode := runApp(app) if exitCode != 0 { os.Exit(exitCode) } } // runApp starts app, waits for a signal or a shutdown request, stops app, // and returns the status pixad exits with, as fx.App.Run does. It prints // a start or stop error to stderr, once, as the error that caused it: // dig.RootCause drops the frames fx and dig wrap around it. func runApp(app *fx.App) int { startCtx, cancelStart := context.WithTimeout(context.Background(), app.StartTimeout()) defer cancelStart() err := app.Start(startCtx) if err != nil { fmt.Fprintln(os.Stderr, dig.RootCause(err)) return 1 } shutdown := <-app.Wait() stopCtx, cancelStop := context.WithTimeout(context.Background(), app.StopTimeout()) defer cancelStop() err = app.Stop(stopCtx) if err != nil { fmt.Fprintln(os.Stderr, dig.RootCause(err)) return 1 } return shutdown.ExitCode }