Files
rtnetmon/internal/cli/root.go
T
clawbot 7dd4ac798d
check / check (push) Failing after 0s
Adopt repo standards: scaffold, policies, lint-clean (closes #1)
Standard scaffold: script/ entrypoints with the Makefile as thin shims, a Dockerfile whose lint and test phases gate the build, a Gitea CI workflow running script/cibuild, REPO_POLICIES.md, TODO.md, .editorconfig, .dockerignore, LICENSE, wider .gitignore. .golangci.yml is byte-identical to the canonical copy in the prompts repo.

211 lint findings fixed in code: package globals became functions/fields and a cobra command constructor, magic numbers became named constants, ctx is threaded into the probes, monitor loops split. Tests moved to external _test packages with export_test.go. Behavior unchanged.

Readers will trip over: make lint/test/check now need a Docker daemon; the personal rsync copy/run targets are gone and make run runs locally.
Disclosure: four //nolint:gosec remain on the fixed-argv ping/curl calls and the operator-chosen log file, as the reference repo annotates the same class.
Disclosure: module path left as-is.

Model: opus-4-8 (implementation); fable-5-1 (merge)
2026-09-21 09:22:28 +02:00

129 lines
3.3 KiB
Go

//go:build linux
// Package cli wires command-line flags to the network monitor and runs it.
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
}
// defaultReachabilityHosts is the default reachability host list.
func defaultReachabilityHosts() []string {
return []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 is the default packet-loss host list.
func defaultPacketLossHosts() []string {
return []string{
"github.com", "google.com", "8.8.8.8",
"captive.apple.com", "62.115.190.68",
}
}
// defaultTCPHosts is the default TCP connect host:port list.
func defaultTCPHosts() []string {
return []string{
"datavi.be:443", "fast.com:443",
"console.aws.amazon.com:443", "console.cloud.google.com:443",
"captive.apple.com:80", "google.com:443",
}
}
// newRootCmd builds the cobra root command with its flags bound.
func newRootCmd() *cobra.Command {
cfg := &config{}
cmd := &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: func(_ *cobra.Command, _ []string) error {
return runMonitor(cfg)
},
}
registerFlags(cmd, cfg)
return cmd
}
// registerFlags defines and viper-binds the command's flags.
func registerFlags(cmd *cobra.Command, cfg *config) {
f := cmd.Flags()
f.StringVar(&cfg.IfaceA, "ifaceA", "gu0", "primary network interface")
f.StringVar(&cfg.LabelA, "labelA", "gu LAN - VPN outbound", "label for ifaceA")
f.StringVar(&cfg.IfaceB, "ifaceB", "backhaul0", "secondary network interface")
f.StringVar(&cfg.LabelB, "labelB", "Cox cable direct", "label for ifaceB")
f.StringSliceVar(&cfg.Hosts, "hosts", defaultReachabilityHosts(),
"comma-separated reachability hosts")
f.StringVar(&cfg.LogFile, "logfile", "/tmp/rtnetmon.log", "path to log file")
for _, name := range []string{
"ifaceA", "labelA", "ifaceB", "labelB", "hosts", "logfile",
} {
_ = viper.BindPFlag(name, f.Lookup(name))
}
}
// runMonitor constructs and runs the monitor from cfg.
func runMonitor(cfg *config) error {
monitor.Logf(cfg.LogFile, "Starting rtnetmon")
monitor.Logf(cfg.LogFile, "Monitoring interfaces %s and %s",
cfg.IfaceA, cfg.IfaceB)
mon := monitor.NewMonitor(cfg.IfaceA, cfg.LabelA, cfg.IfaceB, cfg.LabelB,
cfg.LogFile)
for _, host := range cfg.Hosts {
mon.AddReachabilityHost(host)
}
for _, host := range defaultPacketLossHosts() {
mon.AddPacketLossHost(host)
}
for _, host := range defaultTCPHosts() {
mon.AddTCPHost(host)
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
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()
}()
return mon.Run(ctx)
}
// Execute builds the root command and runs it.
func Execute() error {
return newRootCmd().Execute()
}