package birdfmt import ( "bytes" "context" "fmt" "os/exec" "strings" ) // BirdCtl runs bird(8) and birdc(8) for parse checks and configure reload. type BirdCtl struct { // Bird is the bird binary path (default "bird"). Bird string // Birdc is the birdc binary path (default "birdc"). Birdc string // Socket is optional birdc control socket (-s); empty uses birdc default. Socket string } func (c *BirdCtl) birdBin() string { if strings.TrimSpace(c.Bird) != "" { return c.Bird } return "bird" } func (c *BirdCtl) birdcBin() string { if strings.TrimSpace(c.Birdc) != "" { return c.Birdc } return "birdc" } // ParseCheck runs `bird -c -p` to validate syntax without starting the daemon. func (c *BirdCtl) ParseCheck(ctx context.Context, mainConfigPath string) error { mainConfigPath = strings.TrimSpace(mainConfigPath) if mainConfigPath == "" { return fmt.Errorf("birdfmt: main config path is required for parse check") } cmd := exec.CommandContext(ctx, c.birdBin(), "-c", mainConfigPath, "-p") var stderr bytes.Buffer cmd.Stderr = &stderr if err := cmd.Run(); err != nil { msg := strings.TrimSpace(stderr.String()) if msg != "" { return fmt.Errorf("bird -p: %w: %s", err, msg) } return fmt.Errorf("bird -p: %w", err) } return nil } // Configure runs `birdc configure` to load the current config from disk (BIRD 2). func (c *BirdCtl) Configure(ctx context.Context) error { args := []string{"configure"} if s := strings.TrimSpace(c.Socket); s != "" { args = append([]string{"-s", s}, args...) } cmd := exec.CommandContext(ctx, c.birdcBin(), args...) var stderr bytes.Buffer cmd.Stderr = &stderr if err := cmd.Run(); err != nil { msg := strings.TrimSpace(stderr.String()) if msg != "" { return fmt.Errorf("birdc configure: %w: %s", err, msg) } return fmt.Errorf("birdc configure: %w", err) } return nil }