// Package cli runs netwatch-server from its command line: the // check-cidr and prepare-data-dir subcommands, which bin/entrypoint.sh // runs before it starts the server, and otherwise the server, with the // fx wiring. package cli import ( "fmt" "io" "os" "os/user" "sneak.berlin/go/netwatch/internal/config" "sneak.berlin/go/netwatch/internal/globals" "sneak.berlin/go/netwatch/internal/handlers" "sneak.berlin/go/netwatch/internal/healthcheck" "sneak.berlin/go/netwatch/internal/logger" "sneak.berlin/go/netwatch/internal/middleware" "sneak.berlin/go/netwatch/internal/reportbuf" "sneak.berlin/go/netwatch/internal/server" "go.uber.org/fx" "go.uber.org/fx/fxevent" ) // Main runs netwatch-server with args, its command line after the // program's name, and returns the exit code. appname and version are // the name and version the server logs and reports. func Main(appname, version string, args []string) int { // "netwatch-server check-cidr CIDR" exits 1, with the error, if // this server would refuse CIDR in its TRUSTED_PROXIES. // bin/entrypoint.sh runs it on each entry it gives nginx. if len(args) == 2 && args[0] == "check-cidr" { return checkCIDR(os.Stderr, args[1]) } // "netwatch-server prepare-data-dir DATA_DIR" gets DATA_DIR ready // for the netwatch user, or exits 1 with the error; see // reportbuf.PrepareDataDir. bin/entrypoint.sh runs it as root // before it starts this server as that user. if len(args) == 2 && args[0] == "prepare-data-dir" { return prepareDataDir(os.Stderr, "/data", args[1], "netwatch") } runServer(appname, version) return 0 } // checkCIDR returns 0 if the server would take cidr in its // TRUSTED_PROXIES, and otherwise writes the error to stderr and // returns 1. func checkCIDR(stderr io.Writer, cidr string) int { _, err := middleware.ParseTrustedProxies([]string{cidr}) if err != nil { _, _ = fmt.Fprintln(stderr, err) return 1 } return 0 } // prepareDataDir gets dataDir ready for the user named username with // reportbuf.PrepareDataDir, volume being where the data volume is // mounted, and returns 0. Otherwise it writes the error to stderr and // returns 1. func prepareDataDir(stderr io.Writer, volume, dataDir, username string) int { owner, err := user.Lookup(username) if err != nil { _, _ = fmt.Fprintln(stderr, err) return 1 } err = reportbuf.PrepareDataDir(volume, dataDir, owner) if err != nil { _, _ = fmt.Fprintf(stderr, "DATA_DIR '%s': %v\n", dataDir, err) return 1 } return 0 } // runServer runs the server until it is told to stop. If the server // fails to start or to stop, fx's Run exits the process with status 1 // itself. func runServer(appname, version string) { globals.Appname = appname globals.Version = version fx.New( // fx logs each step of starting and stopping through the // server's own logger, so off a terminal those lines are // JSON like every other line. fx.WithLogger(func(log *logger.Logger) fxevent.Logger { return &fxevent.SlogLogger{Logger: log.Get()} }), fx.Provide( config.New, globals.New, handlers.New, healthcheck.New, logger.New, middleware.New, reportbuf.New, server.New, ), fx.Invoke( // First, so the name and version are logged even when // a setting stops the start. func(log *logger.Logger) { log.Identify() }, func(*server.Server) {}, ), ).Run() }