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 1c40faff6e
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.
# Listens on a Unix socket; access controlled via filesystem permissions.
[grpc]
addr = "/var/run/mc-proxy.sock"
addr = "/srv/mc-proxy/mc-proxy.sock"
# Firewall. Global blocklist, evaluated before routing. Default allow.
[firewall]

View File

@@ -162,6 +162,15 @@ func (c *Client) RemoveFirewallRule(ctx context.Context, ruleType FirewallRuleTy
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.
type ListenerStatus struct {
Addr string
@@ -169,6 +178,7 @@ type ListenerStatus struct {
ActiveConnections int64
ProxyProtocol bool
MaxConnections int64
Routes []RouteStatus
}
// 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))
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{
Addr: ls.Addr,
RouteCount: int(ls.RouteCount),
ActiveConnections: ls.ActiveConnections,
ProxyProtocol: ls.ProxyProtocol,
MaxConnections: ls.MaxConnections,
Routes: routes,
}
}

View File

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

View File

@@ -9,7 +9,7 @@ import (
"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

View File

@@ -34,6 +34,17 @@ func statusCmd() *cobra.Command {
for _, ls := range status.Listeners {
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

View File

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

View File

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

View File

@@ -39,7 +39,7 @@ type Database struct {
// GRPC holds the gRPC admin API configuration.
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.
@@ -218,7 +218,7 @@ func (c *Config) validate() error {
if c.GRPC.Addr != "" {
socketPath := c.GRPC.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
want string
}{
{"/var/run/mc-proxy.sock", "/var/run/mc-proxy.sock"},
{"unix:/var/run/mc-proxy.sock", "/var/run/mc-proxy.sock"},
{"/srv/mc-proxy/mc-proxy.sock", "/srv/mc-proxy/mc-proxy.sock"},
{"unix:/srv/mc-proxy/mc-proxy.sock", "/srv/mc-proxy/mc-proxy.sock"},
}
for _, tt := range tests {
g := GRPC{Addr: tt.addr}
@@ -220,7 +220,7 @@ func TestValidateGRPCUnixSocket(t *testing.T) {
path = "/tmp/test.db"
[grpc]
addr = "/var/run/mc-proxy.sock"
addr = "/srv/mc-proxy/mc-proxy.sock"
`
if err := os.WriteFile(path, []byte(data), 0600); err != nil {
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
for _, ls := range a.srv.Listeners() {
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{
Addr: ls.Addr,
RouteCount: int32(len(routes)),
ActiveConnections: ls.ActiveConnections.Load(),
ProxyProtocol: ls.ProxyProtocol,
MaxConnections: ls.MaxConnections,
Routes: pbRoutes,
})
}

Binary file not shown.

View File

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