package main import ( "context" "fmt" "os" "time" "github.com/spf13/cobra" mcpv1 "git.wntrmute.dev/mc/mcp/gen/mcp/v1" "git.wntrmute.dev/mc/mcp/internal/config" ) func routeCmd() *cobra.Command { var nodeName string cmd := &cobra.Command{ Use: "route", Short: "Manage mc-proxy routes", } list := &cobra.Command{ Use: "list", Short: "List mc-proxy routes", RunE: func(_ *cobra.Command, _ []string) error { return runRouteList(nodeName) }, } var ( routeMode string backendTLS bool tlsCert string tlsKey string ) add := &cobra.Command{ Use: "add ", Short: "Add a route to mc-proxy", Long: "Add a route. Example: mcp route add -n rift :443 mcq.svc.mcp.metacircular.net 127.0.0.1:48080 --mode l7 --tls-cert /srv/mc-proxy/certs/mcq.pem --tls-key /srv/mc-proxy/certs/mcq.key", Args: cobra.ExactArgs(3), RunE: func(_ *cobra.Command, args []string) error { return runRouteAdd(nodeName, args, routeMode, backendTLS, tlsCert, tlsKey) }, } add.Flags().StringVar(&routeMode, "mode", "l4", "route mode (l4 or l7)") add.Flags().BoolVar(&backendTLS, "backend-tls", false, "re-encrypt traffic to backend") add.Flags().StringVar(&tlsCert, "tls-cert", "", "path to TLS cert on the node (required for l7)") add.Flags().StringVar(&tlsKey, "tls-key", "", "path to TLS key on the node (required for l7)") remove := &cobra.Command{ Use: "remove ", Short: "Remove a route from mc-proxy", Long: "Remove a route. Example: mcp route remove -n rift :443 mcq.metacircular.net", Args: cobra.ExactArgs(2), RunE: func(_ *cobra.Command, args []string) error { return runRouteRemove(nodeName, args) }, } cmd.PersistentFlags().StringVarP(&nodeName, "node", "n", "", "target node (required)") cmd.AddCommand(list, add, remove) return cmd } func runRouteList(nodeName string) error { if nodeName == "" { return runRouteListAll() } cfg, err := config.LoadCLIConfig(cfgPath) if err != nil { return fmt.Errorf("load config: %w", err) } address, err := findNodeAddress(cfg, nodeName) if err != nil { return err } client, conn, err := dialAgent(address, cfg) if err != nil { return fmt.Errorf("dial agent: %w", err) } defer func() { _ = conn.Close() }() ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() resp, err := client.ListProxyRoutes(ctx, &mcpv1.ListProxyRoutesRequest{}) if err != nil { return fmt.Errorf("list routes: %w", err) } printRoutes(nodeName, resp) return nil } func runRouteListAll() error { first := true return forEachNode(func(node config.NodeConfig, client mcpv1.McpAgentServiceClient) error { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() resp, err := client.ListProxyRoutes(ctx, &mcpv1.ListProxyRoutesRequest{}) if err != nil { _, _ = fmt.Fprintf(os.Stderr, "warning: %s: list routes: %v\n", node.Name, err) return nil } if !first { fmt.Println() } first = false printRoutes(node.Name, resp) return nil }) } func printRoutes(nodeName string, resp *mcpv1.ListProxyRoutesResponse) { fmt.Printf("NODE: %s\n", nodeName) fmt.Printf("mc-proxy %s\n", resp.GetVersion()) if resp.GetStartedAt() != nil { uptime := time.Since(resp.GetStartedAt().AsTime()).Truncate(time.Second) fmt.Printf("uptime: %s\n", uptime) } fmt.Printf("connections: %d\n", resp.GetTotalConnections()) fmt.Println() for _, ls := range resp.GetListeners() { fmt.Printf(" %s routes=%d active=%d\n", ls.GetAddr(), ls.GetRouteCount(), ls.GetActiveConnections()) for _, r := range ls.GetRoutes() { mode := r.GetMode() if mode == "" { mode = "l4" } extra := "" if r.GetBackendTls() { extra = " (re-encrypt)" } fmt.Printf(" %s %s → %s%s\n", mode, r.GetHostname(), r.GetBackend(), extra) } } } func runRouteAdd(nodeName string, args []string, mode string, backendTLS bool, tlsCert, tlsKey string) error { if nodeName == "" { return fmt.Errorf("--node is required") } cfg, err := config.LoadCLIConfig(cfgPath) if err != nil { return fmt.Errorf("load config: %w", err) } address, err := findNodeAddress(cfg, nodeName) if err != nil { return err } client, conn, err := dialAgent(address, cfg) if err != nil { return fmt.Errorf("dial agent: %w", err) } defer func() { _ = conn.Close() }() ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() _, err = client.AddProxyRoute(ctx, &mcpv1.AddProxyRouteRequest{ ListenerAddr: args[0], Hostname: args[1], Backend: args[2], Mode: mode, BackendTls: backendTLS, TlsCert: tlsCert, TlsKey: tlsKey, }) if err != nil { return fmt.Errorf("add route: %w", err) } fmt.Printf("Added route: %s %s → %s on %s (%s)\n", mode, args[1], args[2], args[0], nodeName) return nil } func runRouteRemove(nodeName string, args []string) error { if nodeName == "" { return fmt.Errorf("--node is required") } cfg, err := config.LoadCLIConfig(cfgPath) if err != nil { return fmt.Errorf("load config: %w", err) } address, err := findNodeAddress(cfg, nodeName) if err != nil { return err } client, conn, err := dialAgent(address, cfg) if err != nil { return fmt.Errorf("dial agent: %w", err) } defer func() { _ = conn.Close() }() ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() _, err = client.RemoveProxyRoute(ctx, &mcpv1.RemoveProxyRouteRequest{ ListenerAddr: args[0], Hostname: args[1], }) if err != nil { return fmt.Errorf("remove route: %w", err) } fmt.Printf("Removed route: %s from %s (%s)\n", args[1], args[0], nodeName) return nil }