package main import ( "context" "flag" "fmt" "log" "os" "sync" "time" "evobgp/internal/agentserver" "evobgp/internal/birdfmt" ) func main() { bird := flag.String("bird", "", "path to bird binary (default: bird from PATH)") birdc := flag.String("birdc", "", "path to birdc binary (default: birdc from PATH)") socket := flag.String("socket", "", "optional birdc control socket (-s)") timeout := flag.Duration("timeout", 30*time.Second, "timeout for bird/birdc") watchEvery := flag.Duration("watch-interval", 30*time.Second, "for watch: interval between birdc configure") listen := flag.String("listen", "", "for serve: listen address (default :8443 or EVOBGP_AGENT_LISTEN)") flag.Usage = func() { fmt.Fprintf(os.Stderr, "Usage: %s [flags] \n", os.Args[0]) fmt.Fprintf(os.Stderr, "Commands:\n") fmt.Fprintf(os.Stderr, " parse-check run bird -c -p (syntax check)\n") fmt.Fprintf(os.Stderr, " configure run birdc configure (reload running BIRD)\n") fmt.Fprintf(os.Stderr, " watch periodically run birdc configure (compose sidecar)\n") fmt.Fprintf(os.Stderr, " serve Panel→Node HTTP API (POST /v1/agent/sync)\n") flag.PrintDefaults() } flag.Parse() args := flag.Args() if len(args) < 1 { flag.Usage() os.Exit(2) } ctl := &birdfmt.BirdCtl{Socket: *socket} if *bird != "" { ctl.Bird = *bird } if *birdc != "" { ctl.Birdc = *birdc } switch args[0] { case "serve": runServe(*listen, *timeout) case "parse-check", "configure", "watch": ctx, cancel := context.WithTimeout(context.Background(), *timeout) defer cancel() runBirdCommand(ctx, args, ctl, *watchEvery, *timeout) default: fmt.Fprintf(os.Stderr, "unknown command: %s\n", args[0]) flag.Usage() os.Exit(2) } } func runBirdCommand(ctx context.Context, args []string, ctl *birdfmt.BirdCtl, watchEvery, timeout time.Duration) { switch args[0] { case "parse-check": if len(args) != 2 { fmt.Fprintln(os.Stderr, "parse-check requires exactly one argument: path to bird.conf") os.Exit(2) } if err := ctl.ParseCheck(ctx, args[1]); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } case "configure": if len(args) != 1 { fmt.Fprintln(os.Stderr, "configure takes no extra arguments") os.Exit(2) } if err := ctl.Configure(ctx); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } case "watch": if watchEvery <= 0 { fmt.Fprintln(os.Stderr, "watch-interval must be > 0") os.Exit(2) } log.Printf("evobgp-agent watch: birdc configure every %s (socket=%q)", watchEvery, ctl.Socket) for { cctx, cancel := context.WithTimeout(context.Background(), timeout) err := ctl.Configure(cctx) cancel() if err != nil { log.Printf("evobgp-agent watch: configure: %v", err) } time.Sleep(watchEvery) } } } func runServe(listenFlag string, syncTimeout time.Duration) { cfg, err := agentserver.ConfigFromEnv() if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(2) } if listenFlag != "" { cfg.Listen = listenFlag } if syncTimeout > 0 { cfg.SyncTimeout = syncTimeout } var mu sync.Mutex var lastRev string var lastAt time.Time cfg.LastSync = func() (string, time.Time) { mu.Lock() defer mu.Unlock() return lastRev, lastAt } cfg.OnSyncSuccess = func(rev string) { mu.Lock() lastRev = rev lastAt = time.Now().UTC() mu.Unlock() } if err := agentserver.ListenAndServe(cfg); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } }