Files
ServerStatus/server/main.go
T
2026-07-10 15:35:10 +08:00

139 lines
4.1 KiB
Go

package main
import (
"context"
"errors"
"flag"
"fmt"
"net"
"net/http"
"os"
"os/signal"
"path/filepath"
"strconv"
"strings"
"syscall"
"time"
)
func main() {
var opts Options
var legacyBind string
var legacyPort int
var printVersion bool
flag.StringVar(&opts.ConfigPath, "config", envOr("CONFIG_PATH", "config.json"), "configuration file")
flag.StringVar(&opts.ConfigPath, "c", envOr("CONFIG_PATH", "config.json"), "configuration file (shorthand)")
flag.StringVar(&opts.StatsPath, "stats", os.Getenv("STATS_PATH"), "persistent stats JSON file")
flag.StringVar(&opts.WebDir, "web-dir", envOr("WEB_DIR", "../web"), "WebUI directory")
flag.StringVar(&opts.WebDir, "d", envOr("WEB_DIR", "../web"), "WebUI directory (shorthand)")
flag.StringVar(&opts.HTTPAddr, "http", envOr("HTTP_ADDR", ":8080"), "HTTP listen address")
flag.StringVar(&opts.AgentAddr, "agent", envOr("AGENT_ADDR", ":35601"), "agent TCP listen address")
flag.StringVar(&legacyBind, "bind", "", "agent bind address (legacy compatibility)")
flag.StringVar(&legacyBind, "b", "", "agent bind address (legacy shorthand)")
flag.IntVar(&legacyPort, "port", 0, "agent TCP port (legacy compatibility)")
flag.IntVar(&legacyPort, "p", 0, "agent TCP port (legacy shorthand)")
flag.BoolVar(&opts.Verbose, "verbose", envBool("VERBOSE", false), "verbose HTTP logging")
flag.BoolVar(&opts.Verbose, "v", envBool("VERBOSE", false), "verbose HTTP logging (shorthand)")
flag.BoolVar(&printVersion, "version", false, "print version and exit")
flag.Parse()
if printVersion {
fmt.Printf("serverstatus %s commit=%s built=%s\n", version, commit, buildTime)
return
}
if opts.StatsPath == "" {
opts.StatsPath = filepath.Join(opts.WebDir, "json", "stats.json")
}
if legacyPort != 0 || legacyBind != "" {
if legacyPort == 0 {
legacyPort = 35601
}
opts.AgentAddr = net.JoinHostPort(legacyBind, strconv.Itoa(legacyPort))
}
opts.AdminToken = os.Getenv("ADMIN_TOKEN")
opts.CORSOrigin = os.Getenv("ADMIN_CORS_ORIGIN")
opts.InsecureCallbackTLS = envBool("INSECURE_CALLBACK_TLS", false)
if err := os.MkdirAll(filepath.Dir(opts.StatsPath), 0o755); err != nil {
fatalf("create stats directory: %v", err)
}
app, err := NewApp(opts)
if err != nil {
fatalf("start: %v", err)
}
app.StartBackground()
httpServer := app.HTTPServer()
agentServer := NewAgentServer(app)
errorsChannel := make(chan error, 2)
go func() {
app.logger.Printf("HTTP listening on %s", opts.HTTPAddr)
if err := httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
errorsChannel <- fmt.Errorf("HTTP server: %w", err)
}
}()
go func() {
if err := agentServer.ListenAndServe(); err != nil {
errorsChannel <- fmt.Errorf("agent server: %w", err)
}
}()
reloadSignals := make(chan os.Signal, 1)
stopSignals := make(chan os.Signal, 1)
signal.Notify(reloadSignals, syscall.SIGHUP)
signal.Notify(stopSignals, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
defer signal.Stop(reloadSignals)
defer signal.Stop(stopSignals)
go func() {
for range reloadSignals {
if apiErr := app.ReloadConfig(); apiErr != nil {
app.logger.Printf("reload config: %s", apiErr.Message)
continue
}
app.logger.Printf("configuration reloaded; generation=%d", app.generation.Load())
}
}()
var fatalErr error
select {
case signalValue := <-stopSignals:
app.logger.Printf("received %s; shutting down", signalValue)
case fatalErr = <-errorsChannel:
app.logger.Printf("fatal: %v", fatalErr)
}
app.cancel()
shutdownContext, cancel := context.WithTimeout(context.Background(), 10*time.Second)
_ = httpServer.Shutdown(shutdownContext)
cancel()
app.Close()
if fatalErr != nil {
os.Exit(1)
}
}
func envOr(name, fallback string) string {
if value := strings.TrimSpace(os.Getenv(name)); value != "" {
return value
}
return fallback
}
func envBool(name string, fallback bool) bool {
value := strings.TrimSpace(os.Getenv(name))
if value == "" {
return fallback
}
parsed, err := strconv.ParseBool(value)
if err != nil {
return fallback
}
return parsed
}
func fatalf(format string, args ...any) {
fmt.Fprintf(os.Stderr, "serverstatus: "+format+"\n", args...)
os.Exit(1)
}