Files
pixa/internal/app/app.go
T
sneak 80e81363eb
check / check (push) Canceled after 0s
Print a startup or shutdown error once, without fx's event log (closes #359)
pixad builds its fx app with fx.NopLogger and runs start, wait and stop
itself, so that it can print a start or stop error to stderr once.
dig.RootCause strips the frames fx and dig wrap around the error, which
leaves the line that names the setting. The exit status is as before.

Model: opus-5-5
2026-11-11 20:34:06 +00:00

116 lines
2.8 KiB
Go

// 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
}