refactor: config consolidation + Server type extraction #108
@@ -0,0 +1,133 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gitea.lerkolabs.com/lerkolabs/uptop/internal/server"
|
||||||
|
)
|
||||||
|
|
||||||
|
type appConfig struct {
|
||||||
|
Port int
|
||||||
|
SSHHostKey string
|
||||||
|
|
||||||
|
DBType string
|
||||||
|
DBDSN string
|
||||||
|
|
||||||
|
HTTPPort int
|
||||||
|
TLSCert string
|
||||||
|
TLSKey string
|
||||||
|
|
||||||
|
StatusEnabled bool
|
||||||
|
StatusTitle string
|
||||||
|
|
||||||
|
ClusterMode string
|
||||||
|
ClusterSecret string
|
||||||
|
PeerURL string
|
||||||
|
NodeID string
|
||||||
|
NodeName string
|
||||||
|
NodeRegion string
|
||||||
|
|
||||||
|
AggStrategy string
|
||||||
|
AllowPrivateTargets bool
|
||||||
|
InsecureSkipVerify bool
|
||||||
|
MaintRetention time.Duration
|
||||||
|
EncryptionKey string
|
||||||
|
|
||||||
|
MetricsPublic bool
|
||||||
|
CORSOrigin string
|
||||||
|
TrustedProxies []*net.IPNet
|
||||||
|
|
||||||
|
AdminKey string
|
||||||
|
KeysFile string
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseConfig() appConfig {
|
||||||
|
cfg := appConfig{
|
||||||
|
Port: 23234,
|
||||||
|
SSHHostKey: ".ssh/id_ed25519",
|
||||||
|
DBType: "sqlite",
|
||||||
|
DBDSN: "uptop.db",
|
||||||
|
HTTPPort: 8080,
|
||||||
|
StatusTitle: "System Status",
|
||||||
|
ClusterMode: "leader",
|
||||||
|
MaintRetention: 7 * 24 * time.Hour,
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := os.Getenv("UPTOP_PORT"); v != "" {
|
||||||
|
if n, err := strconv.Atoi(v); err == nil {
|
||||||
|
cfg.Port = n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v := os.Getenv("UPTOP_DB_TYPE"); v != "" {
|
||||||
|
cfg.DBType = v
|
||||||
|
}
|
||||||
|
if v := os.Getenv("UPTOP_DB_DSN"); v != "" {
|
||||||
|
cfg.DBDSN = v
|
||||||
|
}
|
||||||
|
if v := os.Getenv("UPTOP_HTTP_PORT"); v != "" {
|
||||||
|
if n, err := strconv.Atoi(v); err == nil {
|
||||||
|
cfg.HTTPPort = n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if os.Getenv("UPTOP_STATUS_ENABLED") == "true" {
|
||||||
|
cfg.StatusEnabled = true
|
||||||
|
}
|
||||||
|
if v := os.Getenv("UPTOP_STATUS_TITLE"); v != "" {
|
||||||
|
cfg.StatusTitle = v
|
||||||
|
}
|
||||||
|
if v := os.Getenv("UPTOP_CLUSTER_MODE"); v != "" {
|
||||||
|
cfg.ClusterMode = v
|
||||||
|
}
|
||||||
|
if v := os.Getenv("UPTOP_PEER_URL"); v != "" {
|
||||||
|
cfg.PeerURL = v
|
||||||
|
}
|
||||||
|
if v := os.Getenv("UPTOP_CLUSTER_SECRET"); v != "" {
|
||||||
|
cfg.ClusterSecret = v
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.NodeID = os.Getenv("UPTOP_NODE_ID")
|
||||||
|
cfg.NodeName = os.Getenv("UPTOP_NODE_NAME")
|
||||||
|
cfg.NodeRegion = os.Getenv("UPTOP_NODE_REGION")
|
||||||
|
cfg.AggStrategy = os.Getenv("UPTOP_AGG_STRATEGY")
|
||||||
|
|
||||||
|
cfg.AllowPrivateTargets = os.Getenv("UPTOP_ALLOW_PRIVATE_TARGETS") == "true"
|
||||||
|
cfg.InsecureSkipVerify = os.Getenv("UPTOP_INSECURE_SKIP_VERIFY") == "true"
|
||||||
|
cfg.MetricsPublic = os.Getenv("UPTOP_METRICS_PUBLIC") == "true"
|
||||||
|
|
||||||
|
cfg.EncryptionKey = os.Getenv("UPTOP_ENCRYPTION_KEY")
|
||||||
|
cfg.TLSCert = os.Getenv("UPTOP_TLS_CERT")
|
||||||
|
cfg.TLSKey = os.Getenv("UPTOP_TLS_KEY")
|
||||||
|
cfg.CORSOrigin = os.Getenv("UPTOP_CORS_ORIGIN")
|
||||||
|
cfg.TrustedProxies = parseTrustedProxies(os.Getenv("UPTOP_TRUSTED_PROXIES"))
|
||||||
|
|
||||||
|
cfg.SSHHostKey = envOrDefault("UPTOP_SSH_HOST_KEY", cfg.SSHHostKey)
|
||||||
|
cfg.AdminKey = os.Getenv("UPTOP_ADMIN_KEY")
|
||||||
|
cfg.KeysFile = os.Getenv("UPTOP_KEYS")
|
||||||
|
|
||||||
|
if v := os.Getenv("UPTOP_MAINT_RETENTION"); v != "" {
|
||||||
|
if d, err := time.ParseDuration(v); err == nil && d > 0 {
|
||||||
|
cfg.MaintRetention = d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c appConfig) serverConfig(quietHTTPLog bool) server.ServerConfig {
|
||||||
|
return server.ServerConfig{
|
||||||
|
Port: c.HTTPPort,
|
||||||
|
EnableStatus: c.StatusEnabled,
|
||||||
|
Title: c.StatusTitle,
|
||||||
|
ClusterKey: c.ClusterSecret,
|
||||||
|
TLSCert: c.TLSCert,
|
||||||
|
TLSKey: c.TLSKey,
|
||||||
|
ClusterMode: c.ClusterMode,
|
||||||
|
MetricsPublic: c.MetricsPublic,
|
||||||
|
CORSOrigin: c.CORSOrigin,
|
||||||
|
TrustedProxies: c.TrustedProxies,
|
||||||
|
QuietHTTPLog: quietHTTPLog,
|
||||||
|
}
|
||||||
|
}
|
||||||
+27
-98
@@ -12,7 +12,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
@@ -255,64 +254,19 @@ func runMigrateSecrets(args []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func runServe(args []string) {
|
func runServe(args []string) {
|
||||||
portVal := 23234
|
cfg := parseConfig()
|
||||||
dbType := "sqlite"
|
|
||||||
dbDSN := "uptop.db"
|
|
||||||
httpPort := 8080
|
|
||||||
enableStatus := false
|
|
||||||
statusTitle := "System Status"
|
|
||||||
clusterMode := "leader"
|
|
||||||
clusterPeer := ""
|
|
||||||
clusterKey := ""
|
|
||||||
|
|
||||||
if v := os.Getenv("UPTOP_PORT"); v != "" {
|
if cfg.ClusterMode == "probe" {
|
||||||
if p, err := strconv.Atoi(v); err == nil {
|
if cfg.NodeID == "" {
|
||||||
portVal = p
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if v := os.Getenv("UPTOP_DB_TYPE"); v != "" {
|
|
||||||
dbType = v
|
|
||||||
}
|
|
||||||
if v := os.Getenv("UPTOP_DB_DSN"); v != "" {
|
|
||||||
dbDSN = v
|
|
||||||
}
|
|
||||||
if v := os.Getenv("UPTOP_HTTP_PORT"); v != "" {
|
|
||||||
if p, err := strconv.Atoi(v); err == nil {
|
|
||||||
httpPort = p
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if v := os.Getenv("UPTOP_STATUS_ENABLED"); v == "true" {
|
|
||||||
enableStatus = true
|
|
||||||
}
|
|
||||||
if v := os.Getenv("UPTOP_STATUS_TITLE"); v != "" {
|
|
||||||
statusTitle = v
|
|
||||||
}
|
|
||||||
if v := os.Getenv("UPTOP_CLUSTER_MODE"); v != "" {
|
|
||||||
clusterMode = v
|
|
||||||
}
|
|
||||||
if v := os.Getenv("UPTOP_PEER_URL"); v != "" {
|
|
||||||
clusterPeer = v
|
|
||||||
}
|
|
||||||
if v := os.Getenv("UPTOP_CLUSTER_SECRET"); v != "" {
|
|
||||||
clusterKey = v
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeID := os.Getenv("UPTOP_NODE_ID")
|
|
||||||
nodeName := os.Getenv("UPTOP_NODE_NAME")
|
|
||||||
nodeRegion := os.Getenv("UPTOP_NODE_REGION")
|
|
||||||
aggStrategy := os.Getenv("UPTOP_AGG_STRATEGY")
|
|
||||||
|
|
||||||
if clusterMode == "probe" {
|
|
||||||
if nodeID == "" {
|
|
||||||
fmt.Fprintln(os.Stderr, "UPTOP_NODE_ID is required for probe mode")
|
fmt.Fprintln(os.Stderr, "UPTOP_NODE_ID is required for probe mode")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
if clusterPeer == "" {
|
if cfg.PeerURL == "" {
|
||||||
fmt.Fprintln(os.Stderr, "UPTOP_PEER_URL is required for probe mode")
|
fmt.Fprintln(os.Stderr, "UPTOP_PEER_URL is required for probe mode")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Cluster: Running as PROBE (node=%s, region=%s)\n", nodeID, nodeRegion)
|
fmt.Printf("Cluster: Running as PROBE (node=%s, region=%s)\n", cfg.NodeID, cfg.NodeRegion)
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
@@ -323,19 +277,18 @@ func runServe(args []string) {
|
|||||||
cancel()
|
cancel()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
probeAllowPrivate := os.Getenv("UPTOP_ALLOW_PRIVATE_TARGETS") == "true"
|
if cfg.AllowPrivateTargets {
|
||||||
if probeAllowPrivate {
|
|
||||||
fmt.Println("WARNING: Private target blocking disabled. Monitor URLs can reach internal networks.")
|
fmt.Println("WARNING: Private target blocking disabled. Monitor URLs can reach internal networks.")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := cluster.RunProbe(ctx, cluster.ProbeConfig{
|
if err := cluster.RunProbe(ctx, cluster.ProbeConfig{
|
||||||
NodeID: nodeID,
|
NodeID: cfg.NodeID,
|
||||||
NodeName: nodeName,
|
NodeName: cfg.NodeName,
|
||||||
Region: nodeRegion,
|
Region: cfg.NodeRegion,
|
||||||
LeaderURL: clusterPeer,
|
LeaderURL: cfg.PeerURL,
|
||||||
SharedKey: clusterKey,
|
SharedKey: cfg.ClusterSecret,
|
||||||
Interval: 30,
|
Interval: 30,
|
||||||
AllowPrivateTargets: probeAllowPrivate,
|
AllowPrivateTargets: cfg.AllowPrivateTargets,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Probe error: %v\n", err)
|
fmt.Fprintf(os.Stderr, "Probe error: %v\n", err)
|
||||||
}
|
}
|
||||||
@@ -343,9 +296,9 @@ func runServe(args []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fs := flag.NewFlagSet("serve", flag.ExitOnError)
|
fs := flag.NewFlagSet("serve", flag.ExitOnError)
|
||||||
port := fs.Int("port", portVal, "SSH Port")
|
port := fs.Int("port", cfg.Port, "SSH Port")
|
||||||
flagDBType := fs.String("db-type", dbType, "Database type")
|
flagDBType := fs.String("db-type", cfg.DBType, "Database type")
|
||||||
flagDSN := fs.String("dsn", dbDSN, "Database DSN")
|
flagDSN := fs.String("dsn", cfg.DBDSN, "Database DSN")
|
||||||
demo := fs.Bool("demo", false, "Seed demo data")
|
demo := fs.Bool("demo", false, "Seed demo data")
|
||||||
importKuma := fs.String("import-kuma", "", "Import Uptime Kuma backup JSON file")
|
importKuma := fs.String("import-kuma", "", "Import Uptime Kuma backup JSON file")
|
||||||
_ = fs.Parse(args) // ExitOnError: parse errors exit before returning
|
_ = fs.Parse(args) // ExitOnError: parse errors exit before returning
|
||||||
@@ -365,8 +318,8 @@ func runServe(args []string) {
|
|||||||
}
|
}
|
||||||
defer ss.Close()
|
defer ss.Close()
|
||||||
|
|
||||||
if encKey := os.Getenv("UPTOP_ENCRYPTION_KEY"); encKey != "" {
|
if cfg.EncryptionKey != "" {
|
||||||
enc, err := store.NewEncryptor(encKey)
|
enc, err := store.NewEncryptor(cfg.EncryptionKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "encryption key error: %v\n", err)
|
fmt.Fprintf(os.Stderr, "encryption key error: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -402,23 +355,18 @@ func runServe(args []string) {
|
|||||||
fmt.Printf("Imported %d monitors and %d alerts from Uptime Kuma v%s\n", len(backup.Sites), len(backup.Alerts), kb.Version)
|
fmt.Printf("Imported %d monitors and %d alerts from Uptime Kuma v%s\n", len(backup.Sites), len(backup.Alerts), kb.Version)
|
||||||
}
|
}
|
||||||
|
|
||||||
allowPrivate := os.Getenv("UPTOP_ALLOW_PRIVATE_TARGETS") == "true"
|
if cfg.AllowPrivateTargets {
|
||||||
if allowPrivate {
|
|
||||||
fmt.Println("WARNING: Private target blocking disabled. Monitor URLs can reach internal networks.")
|
fmt.Println("WARNING: Private target blocking disabled. Monitor URLs can reach internal networks.")
|
||||||
}
|
}
|
||||||
|
|
||||||
eng := monitor.NewEngineWithOpts(s, allowPrivate)
|
eng := monitor.NewEngineWithOpts(s, cfg.AllowPrivateTargets)
|
||||||
if os.Getenv("UPTOP_INSECURE_SKIP_VERIFY") == "true" {
|
if cfg.InsecureSkipVerify {
|
||||||
eng.SetInsecureSkipVerify(true)
|
eng.SetInsecureSkipVerify(true)
|
||||||
}
|
}
|
||||||
if aggStrategy != "" {
|
if cfg.AggStrategy != "" {
|
||||||
eng.SetAggStrategy(monitor.AggregationStrategy(aggStrategy))
|
eng.SetAggStrategy(monitor.AggregationStrategy(cfg.AggStrategy))
|
||||||
}
|
|
||||||
if v := os.Getenv("UPTOP_MAINT_RETENTION"); v != "" {
|
|
||||||
if d, err := time.ParseDuration(v); err == nil && d > 0 {
|
|
||||||
eng.SetMaintRetention(d)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
eng.SetMaintRetention(cfg.MaintRetention)
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
@@ -428,31 +376,14 @@ func runServe(args []string) {
|
|||||||
eng.InitAlertHealth()
|
eng.InitAlertHealth()
|
||||||
eng.Start(ctx)
|
eng.Start(ctx)
|
||||||
|
|
||||||
tlsCert := os.Getenv("UPTOP_TLS_CERT")
|
|
||||||
tlsKey := os.Getenv("UPTOP_TLS_KEY")
|
|
||||||
|
|
||||||
// When the local TUI owns the terminal, per-request HTTP logs to stderr
|
|
||||||
// would scribble over the alt screen.
|
|
||||||
localTUI := isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd())
|
localTUI := isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd())
|
||||||
|
|
||||||
httpSrv := server.Start(server.ServerConfig{
|
httpSrv := server.Start(cfg.serverConfig(localTUI), s, eng)
|
||||||
Port: httpPort,
|
|
||||||
EnableStatus: enableStatus,
|
|
||||||
Title: statusTitle,
|
|
||||||
ClusterKey: clusterKey,
|
|
||||||
TLSCert: tlsCert,
|
|
||||||
TLSKey: tlsKey,
|
|
||||||
ClusterMode: clusterMode,
|
|
||||||
MetricsPublic: os.Getenv("UPTOP_METRICS_PUBLIC") == "true",
|
|
||||||
CORSOrigin: os.Getenv("UPTOP_CORS_ORIGIN"),
|
|
||||||
TrustedProxies: parseTrustedProxies(os.Getenv("UPTOP_TRUSTED_PROXIES")),
|
|
||||||
QuietHTTPLog: localTUI,
|
|
||||||
}, s, eng)
|
|
||||||
|
|
||||||
cluster.Start(ctx, cluster.Config{
|
cluster.Start(ctx, cluster.Config{
|
||||||
Mode: clusterMode,
|
Mode: cfg.ClusterMode,
|
||||||
PeerURL: clusterPeer,
|
PeerURL: cfg.PeerURL,
|
||||||
SharedKey: clusterKey,
|
SharedKey: cfg.ClusterSecret,
|
||||||
}, eng)
|
}, eng)
|
||||||
|
|
||||||
sshSrv := startSSHServer(*port, s, eng, kc)
|
sshSrv := startSSHServer(*port, s, eng, kc)
|
||||||
@@ -471,8 +402,6 @@ func runServe(args []string) {
|
|||||||
}
|
}
|
||||||
cancel()
|
cancel()
|
||||||
|
|
||||||
// Drain pending DB writes before the deferred ss.Close() runs, so no
|
|
||||||
// write races a closed database.
|
|
||||||
eng.Stop()
|
eng.Stop()
|
||||||
|
|
||||||
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 30*time.Second)
|
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
|||||||
Reference in New Issue
Block a user