// Command ip-probe is the InternetPressure.io probe agent. // // ip-probe run [--config FILE] run the agent (default command) // ip-probe once --target HOST [...] run every check once against an ad-hoc target, print JSON // ip-probe check-config [--config FILE] validate the configuration and print it (key redacted) // ip-probe --version | version print the version // // Protocol: docs/PROBE-PROTOCOL.md. Configuration: probe.example.yaml / README.md. package main import ( "context" "encoding/json" "errors" "flag" "fmt" "io" "log/slog" "os" "os/signal" "runtime" "strings" "syscall" "internetpressure.io/probe-agent/internal/config" ) // version is injected at build time: -ldflags "-X main.version=$(VERSION)". var version = "dev" func main() { os.Exit(run(os.Args[1:], os.Stdout, os.Stderr)) } func run(args []string, stdout, stderr io.Writer) int { cmd := "run" if len(args) > 0 && !strings.HasPrefix(args[0], "-") { cmd, args = args[0], args[1:] } for _, a := range args { if a == "--version" || a == "-version" || a == "-v" { cmd = "version" } } switch cmd { case "version", "--version": fmt.Fprintf(stdout, "ip-probe %s (%s/%s, %s)\n", version, runtime.GOOS, runtime.GOARCH, runtime.Version()) return 0 case "check-config": return cmdCheckConfig(args, stdout, stderr) case "once": return cmdOnce(args, stdout, stderr) case "run": return cmdRun(args, stderr) case "help", "-h", "--help": usage(stdout) return 0 default: fmt.Fprintf(stderr, "ip-probe: unknown command %q\n\n", cmd) usage(stderr) return 2 } } func usage(w io.Writer) { fmt.Fprint(w, `Usage: ip-probe run [--config FILE] run the agent (default) ip-probe once --target HOST [--url URL] [--ip IP] [--port N] [--no-traceroute] [--config FILE] ip-probe check-config [--config FILE] validate configuration ip-probe --version Config file lookup: --config, $IP_PROBE_CONFIG, /etc/internetpressure/probe.yaml, ~/.internetpressure/probe.yaml. Every key can be overridden with IP_PROBE_ (e.g. IP_PROBE_PROBE_ID, IP_PROBE_KEY, IP_PROBE_INGEST_URL). `) } func cmdCheckConfig(args []string, stdout, stderr io.Writer) int { fs := flag.NewFlagSet("check-config", flag.ContinueOnError) fs.SetOutput(stderr) path := fs.String("config", "", "config file") if err := fs.Parse(args); err != nil { return 2 } p := config.ResolvePath(*path) if p == "" && *path == "" { fmt.Fprintln(stderr, "no config file found (using defaults + IP_PROBE_* env)") } cfg, err := config.Load(p, true) if err != nil { fmt.Fprintf(stderr, "config invalid: %v\n", err) return 1 } enc := json.NewEncoder(stdout) enc.SetIndent("", " ") enc.Encode(cfg.Redacted()) fmt.Fprintf(stdout, "config OK (%s)\n", orDefault(cfg.Source, "defaults/env")) return 0 } func cmdRun(args []string, stderr io.Writer) int { fs := flag.NewFlagSet("run", flag.ContinueOnError) fs.SetOutput(stderr) path := fs.String("config", "", "config file") if err := fs.Parse(args); err != nil { return 2 } cfg, err := config.Load(config.ResolvePath(*path), true) if err != nil { fmt.Fprintf(stderr, "ip-probe: %v\n", err) return 1 } log := newLogger(cfg.LogLevel, stderr) slog.SetDefault(log) ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer stop() a, err := newAgent(cfg, log) if err != nil { log.Error("start failed", "err", err) return 1 } if err := a.Run(ctx); err != nil && !errors.Is(err, context.Canceled) { log.Error("agent stopped with error", "err", err) return 1 } return 0 } // newLogger builds a slog logger: text on a terminal, JSON otherwise (launchd/systemd logs). func newLogger(level string, w io.Writer) *slog.Logger { var lvl slog.Level switch level { case "debug": lvl = slog.LevelDebug case "warn": lvl = slog.LevelWarn case "error": lvl = slog.LevelError default: lvl = slog.LevelInfo } opts := &slog.HandlerOptions{Level: lvl} if f, ok := w.(*os.File); ok { if fi, err := f.Stat(); err == nil && fi.Mode()&os.ModeCharDevice != 0 { return slog.New(slog.NewTextHandler(w, opts)) } } return slog.New(slog.NewJSONHandler(w, opts)) } func orDefault(s, d string) string { if s == "" { return d } return s }