check / check (push) Waiting to run
cmd/netwatch-server/main.go parsed the command line, ran the check-cidr and prepare-data-dir subcommands and built the fx application itself; the Go style guide puts all of that in internal/ or pkg/. The new package internal/cli does it, with the same arguments, messages and exit codes. main() is now os.Exit(cli.Main(Appname, Version, os.Args[1:], cli.RunServer)): Main takes the function that runs the server, so its tests call it with one that fails them. Appname and Version stay in main, where the build sets Version. Each subcommand writes its error to a writer it is given, and prepare-data-dir also takes the volume and the user's name, so its own tests give it a temporary directory and the user they run as. Model: opus-5-5
129 lines
3.8 KiB
Go
129 lines
3.8 KiB
Go
// 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. Unless args are
|
|
// "check-cidr CIDR" or "prepare-data-dir DATA_DIR", Main runs the
|
|
// server by calling serve with appname and version, the name and
|
|
// version the server logs and reports. main passes RunServer; the
|
|
// tests pass a function that fails them, so that no test starts the
|
|
// server. When serve is RunServer and the server fails to start or to
|
|
// stop, fx's Run exits the process with status 1 and Main never
|
|
// returns.
|
|
func Main(
|
|
appname, version string,
|
|
args []string,
|
|
serve func(appname, version 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")
|
|
}
|
|
|
|
serve(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, with appname and
|
|
// version as the name and version it logs and reports. 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()
|
|
}
|