Add mcproxyctl CLI for gRPC admin API

Introduces a new command-line tool for managing mc-proxy via the gRPC
admin API over Unix socket. Commands include route and firewall rule
CRUD operations, health checks, and status queries.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-03-19 08:09:13 -07:00
parent f24fa2a2b0
commit 666d55018c
8 changed files with 410 additions and 1 deletions

131
cmd/mcproxyctl/firewall.go Normal file
View File

@@ -0,0 +1,131 @@
package main
import (
"context"
"fmt"
"time"
"github.com/spf13/cobra"
"git.wntrmute.dev/kyle/mc-proxy/client/mcproxy"
)
func firewallCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "firewall",
Short: "Manage firewall rules",
Long: "Manage firewall rules for mc-proxy.",
}
cmd.AddCommand(firewallListCmd())
cmd.AddCommand(firewallAddCmd())
cmd.AddCommand(firewallRemoveCmd())
return cmd
}
func firewallListCmd() *cobra.Command {
return &cobra.Command{
Use: "list",
Short: "List all firewall rules",
Long: "List all configured firewall rules.",
RunE: func(cmd *cobra.Command, args []string) error {
client := clientFromContext(cmd.Context())
ctx, cancel := context.WithTimeout(cmd.Context(), 5*time.Second)
defer cancel()
rules, err := client.GetFirewallRules(ctx)
if err != nil {
return fmt.Errorf("listing firewall rules: %w", err)
}
if len(rules) == 0 {
fmt.Println("No firewall rules configured")
return nil
}
// Find max type length for alignment
maxTypeLen := 0
for _, r := range rules {
if len(r.Type) > maxTypeLen {
maxTypeLen = len(string(r.Type))
}
}
fmt.Println("Firewall rules:")
for _, r := range rules {
fmt.Printf(" %-*s %s\n", maxTypeLen, r.Type, r.Value)
}
return nil
},
}
}
func firewallAddCmd() *cobra.Command {
return &cobra.Command{
Use: "add TYPE VALUE",
Short: "Add a firewall rule",
Long: "Add a firewall rule. TYPE must be one of: ip, cidr, country.",
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
ruleType, err := parseRuleType(args[0])
if err != nil {
return err
}
value := args[1]
client := clientFromContext(cmd.Context())
ctx, cancel := context.WithTimeout(cmd.Context(), 5*time.Second)
defer cancel()
if err := client.AddFirewallRule(ctx, ruleType, value); err != nil {
return fmt.Errorf("adding firewall rule: %w", err)
}
fmt.Printf("Added firewall rule: %s %s\n", ruleType, value)
return nil
},
}
}
func firewallRemoveCmd() *cobra.Command {
return &cobra.Command{
Use: "remove TYPE VALUE",
Short: "Remove a firewall rule",
Long: "Remove a firewall rule. TYPE must be one of: ip, cidr, country.",
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
ruleType, err := parseRuleType(args[0])
if err != nil {
return err
}
value := args[1]
client := clientFromContext(cmd.Context())
ctx, cancel := context.WithTimeout(cmd.Context(), 5*time.Second)
defer cancel()
if err := client.RemoveFirewallRule(ctx, ruleType, value); err != nil {
return fmt.Errorf("removing firewall rule: %w", err)
}
fmt.Printf("Removed firewall rule: %s %s\n", ruleType, value)
return nil
},
}
}
func parseRuleType(s string) (mcproxy.FirewallRuleType, error) {
switch s {
case "ip":
return mcproxy.FirewallRuleIP, nil
case "cidr":
return mcproxy.FirewallRuleCIDR, nil
case "country":
return mcproxy.FirewallRuleCountry, nil
default:
return "", fmt.Errorf("invalid rule type %q: must be one of: ip, cidr, country", s)
}
}