Add per-route details to status, move socket to /srv/mc-proxy/

mcproxyctl status now shows individual routes per listener with
hostname, backend, mode, and re-encrypt indicator. Proto, gRPC
server, client library, and CLI all updated.

Default gRPC socket path moved from /var/run/mc-proxy.sock to
/srv/mc-proxy/mc-proxy.sock to match the service data directory
convention.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-26 12:52:37 -07:00
parent 57adbbf05e
commit 6dc3e18925
12 changed files with 95 additions and 41 deletions

View File

@@ -487,7 +487,7 @@ addr = ":8443"
# gRPC admin API. Optional -- omit or leave addr empty to disable. # gRPC admin API. Optional -- omit or leave addr empty to disable.
# Listens on a Unix socket; access controlled via filesystem permissions. # Listens on a Unix socket; access controlled via filesystem permissions.
[grpc] [grpc]
addr = "/var/run/mc-proxy.sock" addr = "/srv/mc-proxy/mc-proxy.sock"
# Firewall. Global blocklist, evaluated before routing. Default allow. # Firewall. Global blocklist, evaluated before routing. Default allow.
[firewall] [firewall]

View File

@@ -162,6 +162,15 @@ func (c *Client) RemoveFirewallRule(ctx context.Context, ruleType FirewallRuleTy
return err return err
} }
// RouteStatus contains status information for a single route.
type RouteStatus struct {
Hostname string
Backend string
Mode string // "l4" or "l7"
BackendTLS bool
SendProxyProtocol bool
}
// ListenerStatus contains status information for a single listener. // ListenerStatus contains status information for a single listener.
type ListenerStatus struct { type ListenerStatus struct {
Addr string Addr string
@@ -169,6 +178,7 @@ type ListenerStatus struct {
ActiveConnections int64 ActiveConnections int64
ProxyProtocol bool ProxyProtocol bool
MaxConnections int64 MaxConnections int64
Routes []RouteStatus
} }
// Status contains the server's current status. // Status contains the server's current status.
@@ -196,12 +206,23 @@ func (c *Client) GetStatus(ctx context.Context) (*Status, error) {
status.Listeners = make([]ListenerStatus, len(resp.Listeners)) status.Listeners = make([]ListenerStatus, len(resp.Listeners))
for i, ls := range resp.Listeners { for i, ls := range resp.Listeners {
routes := make([]RouteStatus, len(ls.Routes))
for j, r := range ls.Routes {
routes[j] = RouteStatus{
Hostname: r.Hostname,
Backend: r.Backend,
Mode: r.Mode,
BackendTLS: r.BackendTls,
SendProxyProtocol: r.SendProxyProtocol,
}
}
status.Listeners[i] = ListenerStatus{ status.Listeners[i] = ListenerStatus{
Addr: ls.Addr, Addr: ls.Addr,
RouteCount: int(ls.RouteCount), RouteCount: int(ls.RouteCount),
ActiveConnections: ls.ActiveConnections, ActiveConnections: ls.ActiveConnections,
ProxyProtocol: ls.ProxyProtocol, ProxyProtocol: ls.ProxyProtocol,
MaxConnections: ls.MaxConnections, MaxConnections: ls.MaxConnections,
Routes: routes,
} }
} }

View File

@@ -5,7 +5,7 @@
// //
// # Basic Usage // # Basic Usage
// //
// client, err := mcproxy.Dial("/var/run/mc-proxy.sock") // client, err := mcproxy.Dial("/srv/mc-proxy/mc-proxy.sock")
// if err != nil { // if err != nil {
// log.Fatal(err) // log.Fatal(err)
// } // }

View File

@@ -9,7 +9,7 @@ import (
"git.wntrmute.dev/kyle/mc-proxy/client/mcproxy" "git.wntrmute.dev/kyle/mc-proxy/client/mcproxy"
) )
const defaultSocketPath = "/var/run/mc-proxy.sock" const defaultSocketPath = "/srv/mc-proxy/mc-proxy.sock"
type contextKey string type contextKey string

View File

@@ -34,6 +34,17 @@ func statusCmd() *cobra.Command {
for _, ls := range status.Listeners { for _, ls := range status.Listeners {
fmt.Printf(" %s routes=%d active=%d\n", ls.Addr, ls.RouteCount, ls.ActiveConnections) fmt.Printf(" %s routes=%d active=%d\n", ls.Addr, ls.RouteCount, ls.ActiveConnections)
for _, r := range ls.Routes {
mode := r.Mode
if mode == "" {
mode = "l4"
}
extra := ""
if r.BackendTLS {
extra = " (re-encrypt)"
}
fmt.Printf(" %s %s → %s%s\n", mode, r.Hostname, r.Backend, extra)
}
} }
return nil return nil

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.11 // protoc-gen-go v1.36.11
// protoc v5.29.5 // protoc v6.32.1
// source: proto/mc_proxy/v1/admin.proto // source: proto/mc_proxy/v1/admin.proto
package mcproxyv1 package mcproxyv1
@@ -1173,6 +1173,7 @@ type ListenerStatus struct {
ActiveConnections int64 `protobuf:"varint,3,opt,name=active_connections,json=activeConnections,proto3" json:"active_connections,omitempty"` ActiveConnections int64 `protobuf:"varint,3,opt,name=active_connections,json=activeConnections,proto3" json:"active_connections,omitempty"`
ProxyProtocol bool `protobuf:"varint,4,opt,name=proxy_protocol,json=proxyProtocol,proto3" json:"proxy_protocol,omitempty"` ProxyProtocol bool `protobuf:"varint,4,opt,name=proxy_protocol,json=proxyProtocol,proto3" json:"proxy_protocol,omitempty"`
MaxConnections int64 `protobuf:"varint,5,opt,name=max_connections,json=maxConnections,proto3" json:"max_connections,omitempty"` MaxConnections int64 `protobuf:"varint,5,opt,name=max_connections,json=maxConnections,proto3" json:"max_connections,omitempty"`
Routes []*Route `protobuf:"bytes,6,rep,name=routes,proto3" json:"routes,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
} }
@@ -1242,6 +1243,13 @@ func (x *ListenerStatus) GetMaxConnections() int64 {
return 0 return 0
} }
func (x *ListenerStatus) GetRoutes() []*Route {
if x != nil {
return x.Routes
}
return nil
}
type GetStatusRequest struct { type GetStatusRequest struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
@@ -1408,14 +1416,15 @@ const file_proto_mc_proxy_v1_admin_proto_rawDesc = "" +
" SetListenerMaxConnectionsRequest\x12#\n" + " SetListenerMaxConnectionsRequest\x12#\n" +
"\rlistener_addr\x18\x01 \x01(\tR\flistenerAddr\x12'\n" + "\rlistener_addr\x18\x01 \x01(\tR\flistenerAddr\x12'\n" +
"\x0fmax_connections\x18\x02 \x01(\x03R\x0emaxConnections\"#\n" + "\x0fmax_connections\x18\x02 \x01(\x03R\x0emaxConnections\"#\n" +
"!SetListenerMaxConnectionsResponse\"\xc4\x01\n" + "!SetListenerMaxConnectionsResponse\"\xf0\x01\n" +
"\x0eListenerStatus\x12\x12\n" + "\x0eListenerStatus\x12\x12\n" +
"\x04addr\x18\x01 \x01(\tR\x04addr\x12\x1f\n" + "\x04addr\x18\x01 \x01(\tR\x04addr\x12\x1f\n" +
"\vroute_count\x18\x02 \x01(\x05R\n" + "\vroute_count\x18\x02 \x01(\x05R\n" +
"routeCount\x12-\n" + "routeCount\x12-\n" +
"\x12active_connections\x18\x03 \x01(\x03R\x11activeConnections\x12%\n" + "\x12active_connections\x18\x03 \x01(\x03R\x11activeConnections\x12%\n" +
"\x0eproxy_protocol\x18\x04 \x01(\bR\rproxyProtocol\x12'\n" + "\x0eproxy_protocol\x18\x04 \x01(\bR\rproxyProtocol\x12'\n" +
"\x0fmax_connections\x18\x05 \x01(\x03R\x0emaxConnections\"\x12\n" + "\x0fmax_connections\x18\x05 \x01(\x03R\x0emaxConnections\x12*\n" +
"\x06routes\x18\x06 \x03(\v2\x12.mc_proxy.v1.RouteR\x06routes\"\x12\n" +
"\x10GetStatusRequest\"\xd0\x01\n" + "\x10GetStatusRequest\"\xd0\x01\n" +
"\x11GetStatusResponse\x12\x18\n" + "\x11GetStatusResponse\x12\x18\n" +
"\aversion\x18\x01 \x01(\tR\aversion\x129\n" + "\aversion\x18\x01 \x01(\tR\aversion\x129\n" +
@@ -1497,35 +1506,36 @@ var file_proto_mc_proxy_v1_admin_proto_depIdxs = []int32{
15, // 7: mc_proxy.v1.GetFirewallRulesResponse.rules:type_name -> mc_proxy.v1.FirewallRule 15, // 7: mc_proxy.v1.GetFirewallRulesResponse.rules:type_name -> mc_proxy.v1.FirewallRule
15, // 8: mc_proxy.v1.AddFirewallRuleRequest.rule:type_name -> mc_proxy.v1.FirewallRule 15, // 8: mc_proxy.v1.AddFirewallRuleRequest.rule:type_name -> mc_proxy.v1.FirewallRule
15, // 9: mc_proxy.v1.RemoveFirewallRuleRequest.rule:type_name -> mc_proxy.v1.FirewallRule 15, // 9: mc_proxy.v1.RemoveFirewallRuleRequest.rule:type_name -> mc_proxy.v1.FirewallRule
27, // 10: mc_proxy.v1.GetStatusResponse.started_at:type_name -> google.protobuf.Timestamp 2, // 10: mc_proxy.v1.ListenerStatus.routes:type_name -> mc_proxy.v1.Route
24, // 11: mc_proxy.v1.GetStatusResponse.listeners:type_name -> mc_proxy.v1.ListenerStatus 27, // 11: mc_proxy.v1.GetStatusResponse.started_at:type_name -> google.protobuf.Timestamp
3, // 12: mc_proxy.v1.ProxyAdminService.ListRoutes:input_type -> mc_proxy.v1.ListRoutesRequest 24, // 12: mc_proxy.v1.GetStatusResponse.listeners:type_name -> mc_proxy.v1.ListenerStatus
5, // 13: mc_proxy.v1.ProxyAdminService.AddRoute:input_type -> mc_proxy.v1.AddRouteRequest 3, // 13: mc_proxy.v1.ProxyAdminService.ListRoutes:input_type -> mc_proxy.v1.ListRoutesRequest
7, // 14: mc_proxy.v1.ProxyAdminService.RemoveRoute:input_type -> mc_proxy.v1.RemoveRouteRequest 5, // 14: mc_proxy.v1.ProxyAdminService.AddRoute:input_type -> mc_proxy.v1.AddRouteRequest
16, // 15: mc_proxy.v1.ProxyAdminService.GetFirewallRules:input_type -> mc_proxy.v1.GetFirewallRulesRequest 7, // 15: mc_proxy.v1.ProxyAdminService.RemoveRoute:input_type -> mc_proxy.v1.RemoveRouteRequest
18, // 16: mc_proxy.v1.ProxyAdminService.AddFirewallRule:input_type -> mc_proxy.v1.AddFirewallRuleRequest 16, // 16: mc_proxy.v1.ProxyAdminService.GetFirewallRules:input_type -> mc_proxy.v1.GetFirewallRulesRequest
20, // 17: mc_proxy.v1.ProxyAdminService.RemoveFirewallRule:input_type -> mc_proxy.v1.RemoveFirewallRuleRequest 18, // 17: mc_proxy.v1.ProxyAdminService.AddFirewallRule:input_type -> mc_proxy.v1.AddFirewallRuleRequest
22, // 18: mc_proxy.v1.ProxyAdminService.SetListenerMaxConnections:input_type -> mc_proxy.v1.SetListenerMaxConnectionsRequest 20, // 18: mc_proxy.v1.ProxyAdminService.RemoveFirewallRule:input_type -> mc_proxy.v1.RemoveFirewallRuleRequest
9, // 19: mc_proxy.v1.ProxyAdminService.ListL7Policies:input_type -> mc_proxy.v1.ListL7PoliciesRequest 22, // 19: mc_proxy.v1.ProxyAdminService.SetListenerMaxConnections:input_type -> mc_proxy.v1.SetListenerMaxConnectionsRequest
11, // 20: mc_proxy.v1.ProxyAdminService.AddL7Policy:input_type -> mc_proxy.v1.AddL7PolicyRequest 9, // 20: mc_proxy.v1.ProxyAdminService.ListL7Policies:input_type -> mc_proxy.v1.ListL7PoliciesRequest
13, // 21: mc_proxy.v1.ProxyAdminService.RemoveL7Policy:input_type -> mc_proxy.v1.RemoveL7PolicyRequest 11, // 21: mc_proxy.v1.ProxyAdminService.AddL7Policy:input_type -> mc_proxy.v1.AddL7PolicyRequest
25, // 22: mc_proxy.v1.ProxyAdminService.GetStatus:input_type -> mc_proxy.v1.GetStatusRequest 13, // 22: mc_proxy.v1.ProxyAdminService.RemoveL7Policy:input_type -> mc_proxy.v1.RemoveL7PolicyRequest
4, // 23: mc_proxy.v1.ProxyAdminService.ListRoutes:output_type -> mc_proxy.v1.ListRoutesResponse 25, // 23: mc_proxy.v1.ProxyAdminService.GetStatus:input_type -> mc_proxy.v1.GetStatusRequest
6, // 24: mc_proxy.v1.ProxyAdminService.AddRoute:output_type -> mc_proxy.v1.AddRouteResponse 4, // 24: mc_proxy.v1.ProxyAdminService.ListRoutes:output_type -> mc_proxy.v1.ListRoutesResponse
8, // 25: mc_proxy.v1.ProxyAdminService.RemoveRoute:output_type -> mc_proxy.v1.RemoveRouteResponse 6, // 25: mc_proxy.v1.ProxyAdminService.AddRoute:output_type -> mc_proxy.v1.AddRouteResponse
17, // 26: mc_proxy.v1.ProxyAdminService.GetFirewallRules:output_type -> mc_proxy.v1.GetFirewallRulesResponse 8, // 26: mc_proxy.v1.ProxyAdminService.RemoveRoute:output_type -> mc_proxy.v1.RemoveRouteResponse
19, // 27: mc_proxy.v1.ProxyAdminService.AddFirewallRule:output_type -> mc_proxy.v1.AddFirewallRuleResponse 17, // 27: mc_proxy.v1.ProxyAdminService.GetFirewallRules:output_type -> mc_proxy.v1.GetFirewallRulesResponse
21, // 28: mc_proxy.v1.ProxyAdminService.RemoveFirewallRule:output_type -> mc_proxy.v1.RemoveFirewallRuleResponse 19, // 28: mc_proxy.v1.ProxyAdminService.AddFirewallRule:output_type -> mc_proxy.v1.AddFirewallRuleResponse
23, // 29: mc_proxy.v1.ProxyAdminService.SetListenerMaxConnections:output_type -> mc_proxy.v1.SetListenerMaxConnectionsResponse 21, // 29: mc_proxy.v1.ProxyAdminService.RemoveFirewallRule:output_type -> mc_proxy.v1.RemoveFirewallRuleResponse
10, // 30: mc_proxy.v1.ProxyAdminService.ListL7Policies:output_type -> mc_proxy.v1.ListL7PoliciesResponse 23, // 30: mc_proxy.v1.ProxyAdminService.SetListenerMaxConnections:output_type -> mc_proxy.v1.SetListenerMaxConnectionsResponse
12, // 31: mc_proxy.v1.ProxyAdminService.AddL7Policy:output_type -> mc_proxy.v1.AddL7PolicyResponse 10, // 31: mc_proxy.v1.ProxyAdminService.ListL7Policies:output_type -> mc_proxy.v1.ListL7PoliciesResponse
14, // 32: mc_proxy.v1.ProxyAdminService.RemoveL7Policy:output_type -> mc_proxy.v1.RemoveL7PolicyResponse 12, // 32: mc_proxy.v1.ProxyAdminService.AddL7Policy:output_type -> mc_proxy.v1.AddL7PolicyResponse
26, // 33: mc_proxy.v1.ProxyAdminService.GetStatus:output_type -> mc_proxy.v1.GetStatusResponse 14, // 33: mc_proxy.v1.ProxyAdminService.RemoveL7Policy:output_type -> mc_proxy.v1.RemoveL7PolicyResponse
23, // [23:34] is the sub-list for method output_type 26, // 34: mc_proxy.v1.ProxyAdminService.GetStatus:output_type -> mc_proxy.v1.GetStatusResponse
12, // [12:23] is the sub-list for method input_type 24, // [24:35] is the sub-list for method output_type
12, // [12:12] is the sub-list for extension type_name 13, // [13:24] is the sub-list for method input_type
12, // [12:12] is the sub-list for extension extendee 13, // [13:13] is the sub-list for extension type_name
0, // [0:12] is the sub-list for field type_name 13, // [13:13] is the sub-list for extension extendee
0, // [0:13] is the sub-list for field type_name
} }
func init() { file_proto_mc_proxy_v1_admin_proto_init() } func init() { file_proto_mc_proxy_v1_admin_proto_init() }

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions: // versions:
// - protoc-gen-go-grpc v1.6.1 // - protoc-gen-go-grpc v1.6.1
// - protoc v5.29.5 // - protoc v6.32.1
// source: proto/mc_proxy/v1/admin.proto // source: proto/mc_proxy/v1/admin.proto
package mcproxyv1 package mcproxyv1

View File

@@ -39,7 +39,7 @@ type Database struct {
// GRPC holds the gRPC admin API configuration. // GRPC holds the gRPC admin API configuration.
type GRPC struct { type GRPC struct {
Addr string `toml:"addr"` // Unix socket path (e.g., "/var/run/mc-proxy.sock") Addr string `toml:"addr"` // Unix socket path (e.g., "/srv/mc-proxy/mc-proxy.sock")
} }
// Listener is a proxy listener with its routes. // Listener is a proxy listener with its routes.
@@ -218,7 +218,7 @@ func (c *Config) validate() error {
if c.GRPC.Addr != "" { if c.GRPC.Addr != "" {
socketPath := c.GRPC.SocketPath() socketPath := c.GRPC.SocketPath()
if !strings.Contains(socketPath, "/") { if !strings.Contains(socketPath, "/") {
return fmt.Errorf("grpc.addr must be a Unix socket path (e.g., /var/run/mc-proxy.sock)") return fmt.Errorf("grpc.addr must be a Unix socket path (e.g., /srv/mc-proxy/mc-proxy.sock)")
} }
} }

View File

@@ -200,8 +200,8 @@ func TestGRPCSocketPath(t *testing.T) {
addr string addr string
want string want string
}{ }{
{"/var/run/mc-proxy.sock", "/var/run/mc-proxy.sock"}, {"/srv/mc-proxy/mc-proxy.sock", "/srv/mc-proxy/mc-proxy.sock"},
{"unix:/var/run/mc-proxy.sock", "/var/run/mc-proxy.sock"}, {"unix:/srv/mc-proxy/mc-proxy.sock", "/srv/mc-proxy/mc-proxy.sock"},
} }
for _, tt := range tests { for _, tt := range tests {
g := GRPC{Addr: tt.addr} g := GRPC{Addr: tt.addr}
@@ -220,7 +220,7 @@ func TestValidateGRPCUnixSocket(t *testing.T) {
path = "/tmp/test.db" path = "/tmp/test.db"
[grpc] [grpc]
addr = "/var/run/mc-proxy.sock" addr = "/srv/mc-proxy/mc-proxy.sock"
` `
if err := os.WriteFile(path, []byte(data), 0600); err != nil { if err := os.WriteFile(path, []byte(data), 0600); err != nil {
t.Fatalf("write config: %v", err) t.Fatalf("write config: %v", err)

View File

@@ -434,12 +434,23 @@ func (a *AdminServer) GetStatus(_ context.Context, _ *pb.GetStatusRequest) (*pb.
var listeners []*pb.ListenerStatus var listeners []*pb.ListenerStatus
for _, ls := range a.srv.Listeners() { for _, ls := range a.srv.Listeners() {
routes := ls.Routes() routes := ls.Routes()
var pbRoutes []*pb.Route
for hostname, route := range routes {
pbRoutes = append(pbRoutes, &pb.Route{
Hostname: hostname,
Backend: route.Backend,
Mode: route.Mode,
BackendTls: route.BackendTLS,
SendProxyProtocol: route.SendProxyProtocol,
})
}
listeners = append(listeners, &pb.ListenerStatus{ listeners = append(listeners, &pb.ListenerStatus{
Addr: ls.Addr, Addr: ls.Addr,
RouteCount: int32(len(routes)), RouteCount: int32(len(routes)),
ActiveConnections: ls.ActiveConnections.Load(), ActiveConnections: ls.ActiveConnections.Load(),
ProxyProtocol: ls.ProxyProtocol, ProxyProtocol: ls.ProxyProtocol,
MaxConnections: ls.MaxConnections, MaxConnections: ls.MaxConnections,
Routes: pbRoutes,
}) })
} }

Binary file not shown.

View File

@@ -144,6 +144,7 @@ message ListenerStatus {
int64 active_connections = 3; int64 active_connections = 3;
bool proxy_protocol = 4; bool proxy_protocol = 4;
int64 max_connections = 5; int64 max_connections = 5;
repeated Route routes = 6;
} }
message GetStatusRequest {} message GetStatusRequest {}