//go:build linux // +build linux package cli import ( "context" "os" "os/signal" "syscall" "github.com/spf13/cobra" "github.com/spf13/viper" "git.eeqj.de/sneak/rtnetmon/internal/monitor" ) // Config holds the application configuration type Config struct { IfaceA string LabelA string IfaceB string LabelB string Hosts []string LogFile string } var ( cfg Config rootCmd = &cobra.Command{ Use: "rtnetmon", Short: "Real-time network monitoring dashboard", Long: `rtnetmon is a dual-interface network monitoring dashboard that provides real-time visibility into network health, packet loss, and latency.`, RunE: runMonitor, } ) // Default hosts for monitoring var ( defaultReachabilityHosts = []string{ "8.8.8.8", "8.8.4.4", "google.com", "github.com", "console.aws.amazon.com", "console.cloud.google.com", "fast.com", "datavi.be", "captive.apple.com", } defaultPacketLossHosts = []string{ "github.com", "google.com", "8.8.8.8", "captive.apple.com", "62.115.190.68", } defaultTCPHosts = []string{ "datavi.be:443", "fast.com:443", "console.aws.amazon.com:443", "console.cloud.google.com:443", "captive.apple.com:80", "google.com:443", } ) func init() { // Define flags rootCmd.Flags().StringVar(&cfg.IfaceA, "ifaceA", "gu0", "primary network interface") rootCmd.Flags().StringVar(&cfg.LabelA, "labelA", "gu LAN - VPN outbound", "label for ifaceA") rootCmd.Flags().StringVar(&cfg.IfaceB, "ifaceB", "backhaul0", "secondary network interface") rootCmd.Flags().StringVar(&cfg.LabelB, "labelB", "Cox cable direct", "label for ifaceB") rootCmd.Flags().StringSliceVar(&cfg.Hosts, "hosts", defaultReachabilityHosts, "comma-separated reachability hosts") rootCmd.Flags().StringVar(&cfg.LogFile, "logfile", "/tmp/rtnetmon.log", "path to log file") // Bind flags to viper _ = viper.BindPFlag("ifaceA", rootCmd.Flags().Lookup("ifaceA")) _ = viper.BindPFlag("labelA", rootCmd.Flags().Lookup("labelA")) _ = viper.BindPFlag("ifaceB", rootCmd.Flags().Lookup("ifaceB")) _ = viper.BindPFlag("labelB", rootCmd.Flags().Lookup("labelB")) _ = viper.BindPFlag("hosts", rootCmd.Flags().Lookup("hosts")) _ = viper.BindPFlag("logfile", rootCmd.Flags().Lookup("logfile")) } func runMonitor(cmd *cobra.Command, args []string) error { monitor.Logf(cfg.LogFile, "Starting rtnetmon") monitor.Logf(cfg.LogFile, "Monitoring interfaces %s and %s", cfg.IfaceA, cfg.IfaceB) // Create the monitor mon := monitor.NewMonitor(cfg.IfaceA, cfg.LabelA, cfg.IfaceB, cfg.LabelB, cfg.LogFile) // Add reachability hosts for _, host := range cfg.Hosts { mon.AddReachabilityHost(host) } // Add packet loss hosts for _, host := range defaultPacketLossHosts { mon.AddPacketLossHost(host) } // Add TCP hosts for _, host := range defaultTCPHosts { mon.AddTCPHost(host) } // Create context with signal handling ctx, cancel := context.WithCancel(context.Background()) defer cancel() // Handle signals sig := make(chan os.Signal, 1) signal.Notify(sig, os.Interrupt, syscall.SIGTERM) go func() { s := <-sig monitor.Logf(cfg.LogFile, "Signal received: %v", s) cancel() }() // Run the monitor return mon.Run(ctx) } // Execute runs the root command func Execute() error { return rootCmd.Execute() }